From ae895790ac652c64b40de6492f66d0987fd00e15 Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Thu, 23 Mar 2023 20:51:43 +0100 Subject: [PATCH 001/126] New Crowdin updates (#3475) * New translations automation.properties (Hebrew) * New translations validation.properties (German) * New translations validation.properties (Hebrew) * New translations scriptprofile.properties (German) * New translations scriptprofile.properties (Finnish) * New translations scriptprofile.properties (Hebrew) * New translations scriptprofile.properties (Polish) --- .../main/resources/OH-INF/i18n/scriptprofile_de.properties | 6 ++++++ .../main/resources/OH-INF/i18n/scriptprofile_fi.properties | 6 ++++++ .../main/resources/OH-INF/i18n/scriptprofile_he.properties | 6 ++++++ .../main/resources/OH-INF/i18n/scriptprofile_pl.properties | 6 ++++++ .../src/main/resources/OH-INF/i18n/automation_he.properties | 2 +- .../src/main/resources/OH-INF/i18n/validation_de.properties | 5 +++++ .../src/main/resources/OH-INF/i18n/validation_he.properties | 5 +++++ 7 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_de.properties create mode 100644 bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_fi.properties create mode 100644 bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_he.properties create mode 100644 bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_pl.properties diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_de.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_de.properties new file mode 100644 index 00000000000..7f3a1ab014b --- /dev/null +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_de.properties @@ -0,0 +1,6 @@ +profile.system.script.scriptLanguage.label = Skriptsprache +profile.system.script.scriptLanguage.description = MIME-Typ ("application/vnd.openhab.dsl.rule") der Skriptsprache. +profile.system.script.toItemScript.label = Skript Zum Item +profile.system.script.toItemScript.description = Das Skript für die Umwandlung von States und Commands vom Thing Handler zum Item. +profile.system.script.toHandlerScript.label = Skript zum Thing Handler +profile.system.script.toHandlerScript.description = Das Skript für die Umwandlung von States und Commands vom Item zum Thing Handler. diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_fi.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_fi.properties new file mode 100644 index 00000000000..b7ee809f7a0 --- /dev/null +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_fi.properties @@ -0,0 +1,6 @@ +profile.system.script.scriptLanguage.label = Skriptin kieli +profile.system.script.scriptLanguage.description = Skriptin kielen MIME-tyyppi ("application/vnd.openhab.dsl.rule") +profile.system.script.toItemScript.label = Item-skriptiksi +profile.system.script.toItemScript.description = Skripti, jota käytetään tilojen ja komentojen muuntoon käsittelijästä itemiksi. +profile.system.script.toHandlerScript.label = Käsittelijäskriptiksi +profile.system.script.toHandlerScript.description = Skripti, jota käytetään tilojen ja komentojen muuntoon itemistä käsittelijäksi. diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_he.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_he.properties new file mode 100644 index 00000000000..fc6bb5aae6c --- /dev/null +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_he.properties @@ -0,0 +1,6 @@ +profile.system.script.scriptLanguage.label = שפת תסריט +profile.system.script.scriptLanguage.description = סוג MIME ("application/vnd.openhab.dsl.rule") של שפת הסקריפט +profile.system.script.toItemScript.label = אל סקריפט הפריט +profile.system.script.toItemScript.description = הסקריפט להמרת מצבים ופקודות מהאחראי על הפריט. +profile.system.script.toHandlerScript.label = לתסריט האחראי +profile.system.script.toHandlerScript.description = הסקריפט להמרת מצבים ופקודות מפריט לאחראי. diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_pl.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_pl.properties new file mode 100644 index 00000000000..899c18a77de --- /dev/null +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_pl.properties @@ -0,0 +1,6 @@ +profile.system.script.scriptLanguage.label = Język Skryptu +profile.system.script.scriptLanguage.description = MIME-Typ języka skryptu ("application/vnd.openhab.dsl.rule") +profile.system.script.toItemScript.label = Skrypt do elementu +profile.system.script.toItemScript.description = Skrypt przekształcania stanów i poleceń z kanału do elementu. +profile.system.script.toHandlerScript.label = Skrypt do kanału +profile.system.script.toHandlerScript.description = Skrypt przekształcania stanów i poleceń z elementu do kanału. diff --git a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_he.properties b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_he.properties index 7a170475e7f..82184b42815 100644 --- a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_he.properties +++ b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_he.properties @@ -251,7 +251,7 @@ module-type.core.ItemStateUpdateTrigger.output.event.description = האירוע # core.RuleEnablementAction -module-type.core.RuleEnablementAction.label = הפעל או השבת חוקים +module-type.core.RuleEnablementAction.label = מאפשר או משבית כללים module-type.core.RuleEnablementAction.description = מפעיל או משבית כלל או קבוצת כללים שצוינו במזהי ה-UID שלהם. module-type.core.RuleEnablementAction.config.enable.label = אפשר חוקים module-type.core.RuleEnablementAction.config.enable.description = 'true' מאפשר את כל החוקים שצוינו, 'false' משבית אותם. diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_de.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_de.properties index 65640512249..093bbb739b0 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_de.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_de.properties @@ -11,3 +11,8 @@ min_value_numeric_violated=Der Wert muss mindestens {0} sein. pattern_violated=Der Wert {0} entspricht nicht dem Muster {1}. options_violated=Der Wert {0} ist nicht in den erlaubten Optionen enthalten. Erlaubte Optionen sind\: {1} multiple_limit_violated=Nur {0} Optionen sind zulässig, aber {1} wurden übergeben. + +bridge_not_configured=Es wird eine Bridge benötigt. +type_description_missing=Typbeschreibung für ''{0}'' nicht gefunden, auch das Vorhandensein wurde vorher überprüft. +config_description_missing=Konfigurationsbeschreibung für ''{0}'' nicht gefunden, auch das Vorhandensein wurde vorher überprüft. + diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_he.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_he.properties index 4a9e1670427..0047c2555db 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_he.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_he.properties @@ -11,3 +11,8 @@ min_value_numeric_violated=הערך איננו יכול להיות קטן מ- {0 pattern_violated=הערך {0} לא מתאים לתבנית {1}. options_violated=הערך {0} אינו תואם לאפשרויות הפרמטרים המותרות. האפשרויות המותרות הן\: {1} multiple_limit_violated=רק {0} אלמנטים מותרים אך הוזנו {1}. + +bridge_not_configured=הגדרת מגשר היא חובה. +type_description_missing=סוג תיאור עבור ''{0}'' לא נמצא גם בדקנו את הנוכחות בעבר. +config_description_missing=תיאור התצורה של ''{0}'' לא נמצא גם בדקנו את הנוכחות בעבר. + From 8494bf1199ca1341ca471808cc2f826eb3b447ae Mon Sep 17 00:00:00 2001 From: J-N-K Date: Fri, 24 Mar 2023 10:13:59 +0100 Subject: [PATCH 002/126] [config] Fix ConvigValidationMessage in ThingManagerImpl (#3474) Signed-off-by: Jan N. Klug --- .../java/org/openhab/core/thing/internal/ThingManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingManagerImpl.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingManagerImpl.java index 7bb229c00af..ec0a6c002b7 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingManagerImpl.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingManagerImpl.java @@ -677,7 +677,7 @@ private void normalizeConfiguration(@Nullable AbstractDescriptionType prototype, if (prototype == null) { ConfigValidationMessage message = new ConfigValidationMessage("thing/channel", "Type description for '{0}' not found although we checked the presence before.", - "type_description_missing", targetUID); + "type_description_missing", targetUID.toString()); throw new ConfigValidationException(bundleContext.getBundle(), translationProvider, List.of(message)); } From 4cbc0e613eb589ae2c2b1e6e35f3f7593f22330b Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 25 Mar 2023 16:44:15 +0000 Subject: [PATCH 003/126] Integration tests for org.openhab.core.io.net (#3460) * [org.openhab.core.io.net] add itests Co-authored-by: Wouter Born Signed-off-by: Andrew Fiddian-Green --- bom/compile/pom.xml | 6 + bom/runtime/pom.xml | 6 + .../.classpath | 5 + .../org.openhab.core.io.net.tests/.classpath | 29 +++ itests/org.openhab.core.io.net.tests/.project | 23 +++ itests/org.openhab.core.io.net.tests/NOTICE | 14 ++ .../itest.bndrun | 76 +++++++ itests/org.openhab.core.io.net.tests/pom.xml | 17 ++ .../core/io/net/tests/ClientFactoryTest.java | 189 ++++++++++++++++++ .../net/tests/internal/TestHttpServlet.java | 43 ++++ .../io/net/tests/internal/TestServer.java | 87 ++++++++ .../net/tests/internal/TestStreamAdapter.java | 44 ++++ .../io/net/tests/internal/TestWebSocket.java | 35 ++++ .../tests/internal/TestWebSocketAdapter.java | 39 ++++ .../tests/internal/TestWebSocketServlet.java | 35 ++++ itests/pom.xml | 1 + 16 files changed, 649 insertions(+) create mode 100644 itests/org.openhab.core.io.net.tests/.classpath create mode 100644 itests/org.openhab.core.io.net.tests/.project create mode 100644 itests/org.openhab.core.io.net.tests/NOTICE create mode 100644 itests/org.openhab.core.io.net.tests/itest.bndrun create mode 100644 itests/org.openhab.core.io.net.tests/pom.xml create mode 100644 itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/ClientFactoryTest.java create mode 100644 itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestHttpServlet.java create mode 100644 itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestServer.java create mode 100644 itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestStreamAdapter.java create mode 100644 itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestWebSocket.java create mode 100644 itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestWebSocketAdapter.java create mode 100644 itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestWebSocketServlet.java diff --git a/bom/compile/pom.xml b/bom/compile/pom.xml index 86a71ef2b1f..1823005acc8 100644 --- a/bom/compile/pom.xml +++ b/bom/compile/pom.xml @@ -183,6 +183,12 @@ ${jetty.version} compile + + org.eclipse.jetty.http2 + http2-server + ${jetty.version} + compile + diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index cf8ae22d254..42bd449eb2d 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -757,6 +757,12 @@ ${jetty.version} compile + + org.eclipse.jetty.http2 + http2-server + ${jetty.version} + compile + org.eclipse.jetty jetty-alpn-java-client diff --git a/itests/org.openhab.core.config.core.tests/.classpath b/itests/org.openhab.core.config.core.tests/.classpath index 9e55698cddc..0d0989f1566 100644 --- a/itests/org.openhab.core.config.core.tests/.classpath +++ b/itests/org.openhab.core.config.core.tests/.classpath @@ -25,5 +25,10 @@ + + + + + diff --git a/itests/org.openhab.core.io.net.tests/.classpath b/itests/org.openhab.core.io.net.tests/.classpath new file mode 100644 index 00000000000..9e55698cddc --- /dev/null +++ b/itests/org.openhab.core.io.net.tests/.classpath @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/itests/org.openhab.core.io.net.tests/.project b/itests/org.openhab.core.io.net.tests/.project new file mode 100644 index 00000000000..3b2eca6a8f9 --- /dev/null +++ b/itests/org.openhab.core.io.net.tests/.project @@ -0,0 +1,23 @@ + + + org.openhab.core.io.net.tests + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/itests/org.openhab.core.io.net.tests/NOTICE b/itests/org.openhab.core.io.net.tests/NOTICE new file mode 100644 index 00000000000..6c17d0d8a45 --- /dev/null +++ b/itests/org.openhab.core.io.net.tests/NOTICE @@ -0,0 +1,14 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-core + diff --git a/itests/org.openhab.core.io.net.tests/itest.bndrun b/itests/org.openhab.core.io.net.tests/itest.bndrun new file mode 100644 index 00000000000..0bd9c43c5e3 --- /dev/null +++ b/itests/org.openhab.core.io.net.tests/itest.bndrun @@ -0,0 +1,76 @@ +-include: ../itest-common.bndrun + +Bundle-SymbolicName: ${project.artifactId} +Fragment-Host: org.openhab.core.io.net + +-runrequires: \ + bnd.identity;id='org.openhab.core.io.net.tests',\ + bnd.identity;id='org.eclipse.jetty.http2.hpack',\ + bnd.identity;id='org.eclipse.jetty.websocket.server',\ + bnd.identity;id='org.apache.aries.spifly.dynamic.bundle' + +-runbundles: \ + biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\ + ch.qos.logback.classic;version='[1.2.11,1.2.12)',\ + ch.qos.logback.core;version='[1.2.11,1.2.12)',\ + com.google.gson;version='[2.9.1,2.9.2)',\ + com.sun.jna;version='[5.12.1,5.12.2)',\ + com.sun.xml.bind.jaxb-osgi;version='[2.3.3,2.3.4)',\ + io.methvin.directory-watcher;version='[0.17.1,0.17.2)',\ + jakarta.annotation-api;version='[2.0.0,2.0.1)',\ + jakarta.inject.jakarta.inject-api;version='[2.0.0,2.0.1)',\ + jakarta.xml.bind-api;version='[2.3.3,2.3.4)',\ + javax.measure.unit-api;version='[2.1.2,2.1.3)',\ + junit-jupiter-api;version='[5.9.2,5.9.3)',\ + junit-jupiter-engine;version='[5.9.2,5.9.3)',\ + junit-platform-commons;version='[1.9.2,1.9.3)',\ + junit-platform-engine;version='[1.9.2,1.9.3)',\ + junit-platform-launcher;version='[1.9.2,1.9.3)',\ + org.apache.felix.configadmin;version='[1.9.26,1.9.27)',\ + org.apache.felix.http.servlet-api;version='[1.2.0,1.2.1)',\ + org.apache.felix.scr;version='[2.2.4,2.2.5)',\ + org.apache.servicemix.specs.activation-api-1.2.1;version='[1.2.1,1.2.2)',\ + org.apache.servicemix.specs.annotation-api-1.3;version='[1.3.0,1.3.1)',\ + org.eclipse.equinox.event;version='[1.4.300,1.4.301)',\ + org.eclipse.jetty.alpn.client;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.client;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.http;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.http2.client;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.http2.common;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.http2.hpack;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.io;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.security;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.server;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.servlet;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.util;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.util.ajax;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.websocket.api;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.websocket.client;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.websocket.common;version='[9.4.50,9.4.51)',\ + org.glassfish.hk2.external.javax.inject;version='[2.4.0,2.4.1)',\ + org.glassfish.hk2.osgi-resource-locator;version='[1.0.3,1.0.4)',\ + org.hamcrest;version='[2.2.0,2.2.1)',\ + org.openhab.core;version='[4.0.0,4.0.1)',\ + org.openhab.core.io.net;version='[4.0.0,4.0.1)',\ + org.openhab.core.io.net.tests;version='[4.0.0,4.0.1)',\ + org.openhab.core.test;version='[4.0.0,4.0.1)',\ + org.opentest4j;version='[1.2.0,1.2.1)',\ + org.ops4j.pax.logging.pax-logging-api;version='[2.2.0,2.2.1)',\ + org.osgi.service.cm;version='[1.6.0,1.6.1)',\ + org.osgi.service.component;version='[1.5.0,1.5.1)',\ + org.osgi.service.event;version='[1.4.0,1.4.1)',\ + org.osgi.util.function;version='[1.2.0,1.2.1)',\ + org.osgi.util.promise;version='[1.2.0,1.2.1)',\ + si-units;version='[2.1.0,2.1.1)',\ + si.uom.si-quantity;version='[2.1.0,2.1.1)',\ + tech.units.indriya;version='[2.1.2,2.1.3)',\ + uom-lib-common;version='[2.1.0,2.1.1)',\ + org.eclipse.jetty.http2.server;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.websocket.servlet;version='[9.4.50,9.4.51)',\ + org.apache.aries.spifly.dynamic.bundle;version='[1.3.4,1.3.5)',\ + org.eclipse.jetty.websocket.server;version='[9.4.50,9.4.51)',\ + org.objectweb.asm;version='[9.4.0,9.4.1)',\ + org.objectweb.asm.commons;version='[9.2.0,9.2.1)',\ + org.objectweb.asm.tree;version='[9.2.0,9.2.1)',\ + org.objectweb.asm.tree.analysis;version='[9.2.0,9.2.1)',\ + org.objectweb.asm.util;version='[9.2.0,9.2.1)' diff --git a/itests/org.openhab.core.io.net.tests/pom.xml b/itests/org.openhab.core.io.net.tests/pom.xml new file mode 100644 index 00000000000..f6e7433fc7c --- /dev/null +++ b/itests/org.openhab.core.io.net.tests/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.core.itests + org.openhab.core.reactor.itests + 4.0.0-SNAPSHOT + + + org.openhab.core.io.net.tests + + openHAB Core :: Integration Tests :: Network I/O Tests + + diff --git a/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/ClientFactoryTest.java b/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/ClientFactoryTest.java new file mode 100644 index 00000000000..d805dde53a7 --- /dev/null +++ b/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/ClientFactoryTest.java @@ -0,0 +1,189 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.net.tests; + +import static org.junit.jupiter.api.Assertions.*; + +import java.net.InetSocketAddress; +import java.util.Objects; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.util.Promise.Completable; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.openhab.core.io.net.http.HttpClientFactory; +import org.openhab.core.io.net.http.WebSocketFactory; +import org.openhab.core.io.net.tests.internal.TestServer; +import org.openhab.core.io.net.tests.internal.TestStreamAdapter; +import org.openhab.core.io.net.tests.internal.TestWebSocket; +import org.openhab.core.test.TestPortUtil; +import org.openhab.core.test.java.JavaOSGiTest; + +/** + * Tests for HttpClientFactory and WebSocketFactory implementations. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class ClientFactoryTest extends JavaOSGiTest { + public static final String RESPONSE = "response"; + + private static final String CONSUMER = "consumer"; + private static final String HOST = "127.0.0.1"; + private static final int WAIT_SECONDS = 10; + private static final long CONNECTION_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(WAIT_SECONDS); + + private static @Nullable TestServer server; + + @AfterAll + public static void afterAll() throws Exception { + TestServer testServer = server; + if (testServer != null) { + testServer.stopServer(); + } + } + + @BeforeAll + public static void beforeAll() throws Exception { + TestServer testServer = new TestServer(HOST, TestPortUtil.findFreePort()); + testServer.startServer(); + server = testServer; + } + + private TestServer getServer() { + return Objects.requireNonNull(server); + } + + @Test + public void testHttp1Client() throws Exception { + HttpClientFactory httpClientFactory = getService(HttpClientFactory.class); + assertNotNull(httpClientFactory); + + HttpClient client = httpClientFactory.createHttpClient(CONSUMER); + assertNotNull(client); + + try { + // start client + client.setConnectTimeout(CONNECTION_TIMEOUT_MS); + client.start(); + + // send request + ContentResponse response = client.GET(getServer().getHttpUri()); + + // check response + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals(RESPONSE, response.getContentAsString()); + } finally { + try { + // stop client + client.stop(); + } catch (Exception e) { + } + } + } + + @Test + public void testHttp2Client() throws Exception { + HttpClientFactory httpClientFactory = getService(HttpClientFactory.class); + assertNotNull(httpClientFactory); + + HTTP2Client client = httpClientFactory.createHttp2Client(CONSUMER); + assertNotNull(client); + + Completable<@Nullable Session> sessionCompletable = new Completable<>(); + Completable<@Nullable Stream> streamCompletable = new Completable<>(); + + TestStreamAdapter streamAdapter = new TestStreamAdapter(); + + MetaData.Request request = new MetaData.Request(HttpMethod.GET.toString(), + new HttpURI(getServer().getHttpUri()), HttpVersion.HTTP_2, new HttpFields()); + + HeadersFrame headers = new HeadersFrame(request, null, true); + + try { + // start client + client.setConnectTimeout(CONNECTION_TIMEOUT_MS); + client.start(); + + // establish session + client.connect(new InetSocketAddress(getServer().getHost(), getServer().getPort()), + new Session.Listener.Adapter(), sessionCompletable); + Session session = sessionCompletable.get(WAIT_SECONDS, TimeUnit.SECONDS); + assertNotNull(session); + assertFalse(session.isClosed()); + + // open stream + session.newStream(headers, streamCompletable, streamAdapter); + Stream stream = streamCompletable.get(WAIT_SECONDS, TimeUnit.SECONDS); + assertNotNull(stream); + + // check response + String response = streamAdapter.completable.get(WAIT_SECONDS, TimeUnit.SECONDS); + assertEquals(RESPONSE, response); + } finally { + try { + // stop client + client.stop(); + } catch (Exception e) { + } + } + } + + @Test + public void testWebSocketClient() throws Exception { + WebSocketFactory webSocketFactory = getService(WebSocketFactory.class); + assertNotNull(webSocketFactory); + + WebSocketClient client = webSocketFactory.createWebSocketClient(CONSUMER); + assertNotNull(client); + + try { + // start client + client.setConnectTimeout(CONNECTION_TIMEOUT_MS); + client.start(); + + // create session future + TestWebSocket webSocket = new TestWebSocket(); + Future sessionFuture = client.connect(webSocket, + getServer().getWebSocketUri()); + assertNotNull(sessionFuture); + + // check response + String response = webSocket.completable.get(WAIT_SECONDS, TimeUnit.SECONDS); + assertEquals(RESPONSE, response); + } finally { + try { + // stop client + client.stop(); + } catch (Exception e) { + } + } + } +} diff --git a/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestHttpServlet.java b/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestHttpServlet.java new file mode 100644 index 00000000000..ebb6c0fb5a0 --- /dev/null +++ b/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestHttpServlet.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.net.tests.internal; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.MediaType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.io.net.tests.ClientFactoryTest; + +/** + * Servlet for org.openhab.core.io.net tests. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class TestHttpServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + response.setContentType(MediaType.TEXT_PLAIN); + response.setCharacterEncoding(StandardCharsets.UTF_8.toString()); + response.getWriter().write(ClientFactoryTest.RESPONSE); + } +} diff --git a/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestServer.java b/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestServer.java new file mode 100644 index 00000000000..344d9346356 --- /dev/null +++ b/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestServer.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.net.tests.internal; + +import java.net.URI; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; + +/** + * Embedded jetty server used in the tests. + * + * Based on {@code TestServer} of the FS Internet Radio Binding. + * + * @author Velin Yordanov - Initial contribution + * @author Wouter Born - Increase test coverage + * @author Andrew Fiddian-Green - Adapted for org.openhab.core.io.net.tests + */ +@NonNullByDefault +public class TestServer { + private static final String SERVLET_PATH = "/servlet"; + private static final String WEBSOCKET_PATH = "/ws"; + + private final String host; + private final int port; + private final Server server; + + public TestServer(String host, int port) { + this.host = host; + this.port = port; + this.server = new Server(); + } + + public String getHost() { + return host; + } + + public URI getHttpUri() { + return URI.create("http://" + host + ":" + port + SERVLET_PATH); + } + + public int getPort() { + return port; + } + + public URI getWebSocketUri() { + return URI.create("ws://" + host + ":" + port + WEBSOCKET_PATH); + } + + public void startServer() throws Exception { + ServletHandler handler = new ServletHandler(); + handler.addServletWithMapping(new ServletHolder(new TestHttpServlet()), SERVLET_PATH); + handler.addServletWithMapping(new ServletHolder(new TestWebSocketServlet()), WEBSOCKET_PATH); + server.setHandler(handler); + + HttpConfiguration httpConfig = new HttpConfiguration(); + HttpConnectionFactory h1 = new HttpConnectionFactory(httpConfig); + HTTP2CServerConnectionFactory h2c = new HTTP2CServerConnectionFactory(httpConfig); + + ServerConnector connector = new ServerConnector(server, h1, h2c); + connector.setHost(host); + connector.setPort(port); + server.addConnector(connector); + + server.start(); + } + + public void stopServer() throws Exception { + server.stop(); + } +} diff --git a/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestStreamAdapter.java b/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestStreamAdapter.java new file mode 100644 index 00000000000..cc4b17665e9 --- /dev/null +++ b/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestStreamAdapter.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.net.tests.internal; + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; + +/** + * HTTP/2 stream adapter for org.openhab.core.io.net tests. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@WebSocket +@NonNullByDefault +public class TestStreamAdapter extends Stream.Listener.Adapter { + public final CompletableFuture completable = new CompletableFuture<>(); + + @Override + public void onData(@Nullable Stream stream, @Nullable DataFrame frame, @Nullable Callback callback) { + assertNotNull(stream); + assertNotNull(frame); + assertTrue(frame.isEndStream()); + completable.complete(StandardCharsets.UTF_8.decode(frame.getData()).toString()); + } +} diff --git a/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestWebSocket.java b/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestWebSocket.java new file mode 100644 index 00000000000..d089d5ebbde --- /dev/null +++ b/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestWebSocket.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.net.tests.internal; + +import java.util.concurrent.CompletableFuture; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; + +/** + * WebSocket implementer class for org.openhab.core.io.net tests. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@WebSocket +@NonNullByDefault +public class TestWebSocket { + public final CompletableFuture completable = new CompletableFuture<>(); + + @OnWebSocketMessage + public void onMessage(String message) { + completable.complete(message); + } +} diff --git a/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestWebSocketAdapter.java b/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestWebSocketAdapter.java new file mode 100644 index 00000000000..f201c24b665 --- /dev/null +++ b/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestWebSocketAdapter.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.net.tests.internal; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketAdapter; +import org.openhab.core.io.net.tests.ClientFactoryTest; + +/** + * WebSocket for org.openhab.core.io.net tests. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class TestWebSocketAdapter extends WebSocketAdapter { + + @Override + public void onWebSocketConnect(@Nullable Session session) { + super.onWebSocketConnect(session); + try { + getRemote().sendString(ClientFactoryTest.RESPONSE); + } catch (IOException e) { + } + } +} diff --git a/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestWebSocketServlet.java b/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestWebSocketServlet.java new file mode 100644 index 00000000000..ded3102f5eb --- /dev/null +++ b/itests/org.openhab.core.io.net.tests/src/main/java/org/openhab/core/io/net/tests/internal/TestWebSocketServlet.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.net.tests.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.websocket.servlet.WebSocketServlet; +import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; + +/** + * Servlet for org.openhab.core.io.net tests. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class TestWebSocketServlet extends WebSocketServlet { + private static final long serialVersionUID = 1L; + + @Override + public void configure(@Nullable WebSocketServletFactory factory) { + if (factory != null) { + factory.register(TestWebSocketAdapter.class); + } + } +} diff --git a/itests/pom.xml b/itests/pom.xml index c9846b8286c..8deb43e63e1 100644 --- a/itests/pom.xml +++ b/itests/pom.xml @@ -31,6 +31,7 @@ org.openhab.core.config.discovery.usbserial.tests org.openhab.core.config.dispatch.tests org.openhab.core.ephemeris.tests + org.openhab.core.io.net.tests org.openhab.core.io.rest.core.tests org.openhab.core.model.item.tests org.openhab.core.model.rule.tests From 850a9239de078a1b52cd489fd56e7ebc59e74d5d Mon Sep 17 00:00:00 2001 From: Mark Herwege Date: Sun, 26 Mar 2023 11:15:56 +0200 Subject: [PATCH 004/126] Add inputHint parameter to sitemap Input element (#3418) * Add inputHint param to sitemap Input element Signed-off-by: Mark Herwege --- .../org/openhab/core/model/sitemap/Sitemap.xtext | 12 +++++++----- .../sitemap/validation/SitemapValidator.xtend | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/Sitemap.xtext b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/Sitemap.xtext index d36e7f4c2d0..4b4376ac0a3 100644 --- a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/Sitemap.xtext +++ b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/Sitemap.xtext @@ -54,7 +54,7 @@ Image: Video: 'Video' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? & ('icon=' icon=Icon)? & - ('url=' url=(STRING)) & ('encoding=' encoding=(STRING))? & + ('url=' url=STRING) & ('encoding=' encoding=STRING)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)* ']'))? & @@ -62,7 +62,7 @@ Video: Chart: 'Chart' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & ('icon=' icon=Icon)? & - ('service=' service=(STRING))? & ('refresh=' refresh=INT)? & ('period=' period=ID) & + ('service=' service=STRING)? & ('refresh=' refresh=INT)? & ('period=' period=ID) & ('legend=' legend=BOOLEAN_OBJECT)? & ('forceasitem=' forceAsItem=BOOLEAN_OBJECT)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & @@ -72,7 +72,7 @@ Chart: Webview: 'Webview' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? & ('icon=' icon=Icon)? & - ('height=' height=INT)? & ('url=' url=(STRING)) & + ('height=' height=INT)? & ('url=' url=STRING) & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)* ']'))? & @@ -129,6 +129,7 @@ Colorpicker: Input: 'Input' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? & ('icon=' icon=Icon)? & + ('inputHint=' inputHint=STRING)? & ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)* ']'))? & ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)* ']'))? & ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)* ']'))? & @@ -146,7 +147,7 @@ Mapping: cmd=Command '=' label=(ID | STRING); VisibilityRule: - (item=ID) (condition=("==" | ">" | "<" | ">=" | "<=" | "!=")) (sign=('-' | '+'))? (state=XState); + (item=ID) (condition=('==' | '>' | '<' | '>=' | '<=' | '!=')) (sign=('-' | '+'))? (state=XState); ItemRef: ID; @@ -158,7 +159,7 @@ Icon returns ecore::EString: STRING | ID; ColorArray: - ((item=ID)? (condition=("==" | ">" | "<" | ">=" | "<=" | "!="))? (sign=('-' | '+'))? (state=XState) '=')? + ((item=ID)? (condition=('==' | '>' | '<' | '>=' | '<=' | '!='))? (sign=('-' | '+'))? (state=XState) '=')? (arg=STRING); Command returns ecore::EString: @@ -170,6 +171,7 @@ Number returns ecore::EBigDecimal: XState returns ecore::EString: INT | ID | STRING | FLOAT; +@Override terminal ID: ('^'? ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*) | (('0'..'9')+ ('a'..'z' | 'A'..'Z' | '_') ('0'..'9' | 'a'..'z' | 'A'..'Z' | '_')*); diff --git a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/validation/SitemapValidator.xtend b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/validation/SitemapValidator.xtend index 17df83991a3..9aa2c13a320 100644 --- a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/validation/SitemapValidator.xtend +++ b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/validation/SitemapValidator.xtend @@ -23,6 +23,9 @@ import org.openhab.core.model.sitemap.sitemap.SitemapPackage import org.openhab.core.model.sitemap.sitemap.Widget import org.eclipse.xtext.validation.Check import java.math.BigDecimal +import org.openhab.core.model.sitemap.sitemap.Input +import org.eclipse.xtext.nodemodel.INode +import org.eclipse.xtext.nodemodel.util.NodeModelUtils //import org.eclipse.xtext.validation.Check /** @@ -32,6 +35,8 @@ import java.math.BigDecimal */ class SitemapValidator extends AbstractSitemapValidator { + val ALLOWED_HINTS = #["text", "number", "date", "time", "datetime"] + @Check def void checkFramesInFrame(Frame frame) { for (Widget w : frame.children) { @@ -102,4 +107,14 @@ class SitemapValidator extends AbstractSitemapValidator { SitemapPackage.Literals.SETPOINT.getEStructuralFeature(SitemapPackage.SETPOINT__MIN_VALUE)); } } + + @Check + def void checkInputHintParameter(Input i) { + if (i.inputHint !== null && !ALLOWED_HINTS.contains(i.inputHint)) { + val node = NodeModelUtils.getNode(i) + val line = node.getStartLine() + error("Input on item '" + i.item + "' has invalid inputHint '" + i.inputHint + "' at line " + line, + SitemapPackage.Literals.INPUT.getEStructuralFeature(SitemapPackage.INPUT__INPUT_HINT)) + } + } } From b6c8aeaf10338621f9d8b9bc31e9492710422d85 Mon Sep 17 00:00:00 2001 From: GiviMAD Date: Sun, 26 Mar 2023 19:30:21 +0200 Subject: [PATCH 005/126] Allow using quotes in metadata state/command description value/label (#3363) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miguel Álvarez --- .../MetadataCommandDescriptionProvider.java | 25 +++++++++++++++++-- ...adataStateDescriptionFragmentProvider.java | 21 +++++++++++++--- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataCommandDescriptionProvider.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataCommandDescriptionProvider.java index b2f2478b13b..94d807c8975 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataCommandDescriptionProvider.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataCommandDescriptionProvider.java @@ -63,8 +63,18 @@ public MetadataCommandDescriptionProvider(final @Reference MetadataRegistry meta if (metadata.getConfiguration().containsKey("options")) { Stream.of(metadata.getConfiguration().get("options").toString().split(",")).forEach(o -> { if (o.contains("=")) { - commandDescription.addCommandOption( - new CommandOption(o.split("=")[0].trim(), o.split("=")[1].trim())); + String command; + String label; + if (o.startsWith("\"")) { + String[] parts = o.trim().split("\"=\""); + command = removeSurroundingQuotes(parts[0]); + label = removeSurroundingQuotes(parts[1]); + } else { + String[] parts = o.trim().split("="); + command = parts[0]; + label = parts[1]; + } + commandDescription.addCommandOption(new CommandOption(command.trim(), label.trim())); } else { commandDescription.addCommandOption(new CommandOption(o.trim(), null)); } @@ -80,4 +90,15 @@ public MetadataCommandDescriptionProvider(final @Reference MetadataRegistry meta return null; } + + public static String removeSurroundingQuotes(String input) { + String output = input; + if (input.startsWith("\"")) { + output = output.substring(1); + } + if (input.endsWith("\"")) { + output = output.substring(0, output.length() - 1); + } + return output; + } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataStateDescriptionFragmentProvider.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataStateDescriptionFragmentProvider.java index 705a198765c..1c4cc01fa4e 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataStateDescriptionFragmentProvider.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataStateDescriptionFragmentProvider.java @@ -12,6 +12,8 @@ */ package org.openhab.core.internal.items; +import static org.openhab.core.internal.items.MetadataCommandDescriptionProvider.removeSurroundingQuotes; + import java.math.BigDecimal; import java.math.BigInteger; import java.util.List; @@ -103,9 +105,22 @@ public MetadataStateDescriptionFragmentProvider(final @Reference MetadataRegistr if (metadata.getConfiguration().containsKey("options")) { List stateOptions = Stream .of(metadata.getConfiguration().get("options").toString().split(",")).map(o -> { - return (o.contains("=")) - ? new StateOption(o.split("=")[0].trim(), o.split("=")[1].trim()) - : new StateOption(o.trim(), null); + if (o.contains("=")) { + String value; + String label; + if (o.startsWith("\"")) { + String[] parts = o.trim().split("\"=\""); + value = removeSurroundingQuotes(parts[0]); + label = removeSurroundingQuotes(parts[1]); + } else { + String[] parts = o.trim().split("="); + value = parts[0]; + label = parts[1]; + } + return new StateOption(value.trim(), label.trim()); + } else { + return new StateOption(o.trim(), null); + } }).collect(Collectors.toList()); builder.withOptions(stateOptions); } From 30bbc95506e36f9c61dcfe8adaf3de9be0ed0508 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Wed, 29 Mar 2023 16:41:38 +1000 Subject: [PATCH 006/126] Clarify the script profile config description (#3506) Signed-off-by: Jimmy Tanagra --- .../main/resources/OH-INF/config/script-profile.xml | 10 ++++++---- .../resources/OH-INF/i18n/scriptprofile.properties | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/config/script-profile.xml b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/config/script-profile.xml index 3fd037165e6..cbbfad5bb02 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/config/script-profile.xml +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/config/script-profile.xml @@ -10,12 +10,14 @@ MIME-type ("application/vnd.openhab.dsl.rule") of the scripting language - - The Script for transforming states and commands from handler to item. + + The Script for transforming state updates and commands from the Thing handler to the item. The script + may return null to discard the updates/commands and not pass them through. - - The Script for transforming states and commands from item to handler. + + The Script for transforming commands from the item to the Thing handler. The script may return null to + discard the commands and not pass them through. diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile.properties index 021f5331014..ba168b38021 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile.properties @@ -1,6 +1,6 @@ profile.system.script.scriptLanguage.label = Script Language profile.system.script.scriptLanguage.description = MIME-type ("application/vnd.openhab.dsl.rule") of the scripting language -profile.system.script.toItemScript.label = To Item Script -profile.system.script.toItemScript.description = The Script for transforming states and commands from handler to item. -profile.system.script.toHandlerScript.label = To Handler Script -profile.system.script.toHandlerScript.description = The Script for transforming states and commands from item to handler. +profile.system.script.toItemScript.label = Thing To Item Transformation +profile.system.script.toItemScript.description = The Script for transforming state updates and commands from the Thing handler to the item. The script may return null to discard the updates/commands and not pass them through. +profile.system.script.toHandlerScript.label = Item To Thing Transformation +profile.system.script.toHandlerScript.description = The Script for transforming commands from the item to the Thing handler. The script may return null to discard the commands and not pass them through. From 8df40bc940cf4b7d03cf17f5399116eca23cbfc5 Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Wed, 29 Mar 2023 08:47:45 +0200 Subject: [PATCH 007/126] New Crowdin updates (#3489) * New translations scriptprofile.properties (Hungarian) * New translations voice.properties (Hungarian) --- .../main/resources/OH-INF/i18n/scriptprofile_hu.properties | 6 ++++++ .../src/main/resources/OH-INF/i18n/voice_hu.properties | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_hu.properties diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_hu.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_hu.properties new file mode 100644 index 00000000000..1e4bfb988fa --- /dev/null +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_hu.properties @@ -0,0 +1,6 @@ +profile.system.script.scriptLanguage.label = Szkript nyelve +profile.system.script.scriptLanguage.description = A szkript nyelv MIME-típusa ("application/vnd.openhab.dsl.rule") +profile.system.script.toItemScript.label = Elemhez irányuló szkript +profile.system.script.toItemScript.description = A kezelőtől az elemhez kerülő állapotok és parancsok átalakítását végző szkript. +profile.system.script.toHandlerScript.label = Kezelőhöz irányuló szkript +profile.system.script.toHandlerScript.description = Az elemtől a kezelőhöz kerülő állapotok és parancsok átalakítását végző szkript. diff --git a/bundles/org.openhab.core.voice/src/main/resources/OH-INF/i18n/voice_hu.properties b/bundles/org.openhab.core.voice/src/main/resources/OH-INF/i18n/voice_hu.properties index ccb7093c46e..b85dc48ff72 100644 --- a/bundles/org.openhab.core.voice/src/main/resources/OH-INF/i18n/voice_hu.properties +++ b/bundles/org.openhab.core.voice/src/main/resources/OH-INF/i18n/voice_hu.properties @@ -21,4 +21,4 @@ system.config.voice.listeningMelody.option.E = E error.ks-error = Hiba történt a kulcsszavak észlelésekor, {0} error.stt-error = Hiba történt a szöveg felismerésekor, {0} error.stt-exception = Hiba felismerés közben, {0} -service.system.voice.label = Hang +service.system.voice.label = Beszéd From c8cc05d24b1e6eff39b64287f2fe48587fd49ca3 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Wed, 29 Mar 2023 16:49:04 +1000 Subject: [PATCH 008/126] [REST] Sort profile-type resources by their label (#3491) Signed-off-by: Jimmy Tanagra --- .../io/rest/core/internal/profile/ProfileTypeResource.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/profile/ProfileTypeResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/profile/ProfileTypeResource.java index 20861006ccb..f15688c83bf 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/profile/ProfileTypeResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/profile/ProfileTypeResource.java @@ -13,6 +13,7 @@ package org.openhab.core.io.rest.core.internal.profile; import java.util.Collection; +import java.util.Comparator; import java.util.Locale; import java.util.function.Predicate; import java.util.stream.Stream; @@ -114,7 +115,8 @@ public Response getAll( protected Stream getProfileTypes(@Nullable Locale locale, @Nullable String channelTypeUID, @Nullable String itemType) { return profileTypeRegistry.getProfileTypes(locale).stream().filter(matchesChannelUID(channelTypeUID, locale)) - .filter(matchesItemType(itemType)).map(profileType -> ProfileTypeDTOMapper.map(profileType)); + .filter(matchesItemType(itemType)).sorted(Comparator.comparing(ProfileType::getLabel)) + .map(profileType -> ProfileTypeDTOMapper.map(profileType)); } private Predicate matchesChannelUID(@Nullable String channelTypeUID, @Nullable Locale locale) { From 6294c48be474adde3dd3d8fb173b64b2ae35484c Mon Sep 17 00:00:00 2001 From: J-N-K Date: Wed, 29 Mar 2023 09:54:41 +0200 Subject: [PATCH 009/126] Use try-with-resources on Files.walk/Files.list (#3504) Signed-off-by: Jan N. Klug --- .../community/CommunityKarafAddonHandler.java | 24 +++++++++++-------- .../MarketplaceBundleInstaller.java | 23 ++++++++++-------- .../utils/BundledSoundFileHandler.java | 11 ++++----- .../openhab/core/cache/lru/LRUMediaCache.java | 23 +++++++++--------- 4 files changed, 43 insertions(+), 38 deletions(-) diff --git a/bundles/org.openhab.core.addon.marketplace.karaf/src/main/java/org/openhab/core/addon/marketplace/karaf/internal/community/CommunityKarafAddonHandler.java b/bundles/org.openhab.core.addon.marketplace.karaf/src/main/java/org/openhab/core/addon/marketplace/karaf/internal/community/CommunityKarafAddonHandler.java index 1a301f5c70e..4de98365d5c 100644 --- a/bundles/org.openhab.core.addon.marketplace.karaf/src/main/java/org/openhab/core/addon/marketplace/karaf/internal/community/CommunityKarafAddonHandler.java +++ b/bundles/org.openhab.core.addon.marketplace.karaf/src/main/java/org/openhab/core/addon/marketplace/karaf/internal/community/CommunityKarafAddonHandler.java @@ -78,8 +78,12 @@ public boolean supports(String type, String contentType) { } private Stream karFilesStream(Path addonDirectory) throws IOException { - return Files.isDirectory(addonDirectory) ? Files.list(addonDirectory).map(Path::getFileName) - .filter(path -> path.toString().endsWith(KAR_EXTENSION)) : Stream.empty(); + if (Files.isDirectory(addonDirectory)) { + try (Stream files = Files.list(addonDirectory)) { + return files.map(Path::getFileName).filter(path -> path.toString().endsWith(KAR_EXTENSION)); + } + } + return Stream.empty(); } private String pathToKarRepoName(Path path) { @@ -153,8 +157,8 @@ private void addKarToCache(String addonId, URL sourceUrl) throws MarketplaceHand private void installFromCache(String addonId) throws MarketplaceHandlerException { Path addonPath = getAddonCacheDirectory(addonId); if (Files.isDirectory(addonPath)) { - try { - List karFiles = Files.list(addonPath).collect(Collectors.toList()); + try (Stream files = Files.list(addonPath)) { + List karFiles = files.toList(); if (karFiles.size() != 1) { throw new MarketplaceHandlerException( "The local cache folder doesn't contain a single file: " + addonPath, null); @@ -172,10 +176,10 @@ private void installFromCache(String addonId) throws MarketplaceHandlerException } private void ensureCachedKarsAreInstalled() { - try { - if (Files.isDirectory(KAR_CACHE_PATH)) { - Files.list(KAR_CACHE_PATH).filter(Files::isDirectory).map(this::addonIdFromPath) - .filter(addonId -> !isInstalled(addonId)).forEach(addonId -> { + if (Files.isDirectory(KAR_CACHE_PATH)) { + try (Stream files = Files.list(KAR_CACHE_PATH)) { + files.filter(Files::isDirectory).map(this::addonIdFromPath).filter(addonId -> !isInstalled(addonId)) + .forEach(addonId -> { logger.info("Reinstalling missing marketplace KAR: {}", addonId); try { installFromCache(addonId); @@ -183,9 +187,9 @@ private void ensureCachedKarsAreInstalled() { logger.warn("Failed reinstalling add-on from cache", e); } }); + } catch (IOException e) { + logger.warn("Failed to re-install KARs: {}", e.getMessage()); } - } catch (IOException e) { - logger.warn("Failed to re-install KARs: {}", e.getMessage()); } isReady = true; } diff --git a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/MarketplaceBundleInstaller.java b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/MarketplaceBundleInstaller.java index a429e7a89bc..c4ff1dfff5c 100644 --- a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/MarketplaceBundleInstaller.java +++ b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/MarketplaceBundleInstaller.java @@ -22,7 +22,7 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.List; -import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.OpenHAB; @@ -79,8 +79,8 @@ protected void addBundleToCache(String addonId, URL sourceUrl) throws Marketplac protected void installFromCache(BundleContext bundleContext, String addonId) throws MarketplaceHandlerException { Path addonPath = getAddonCacheDirectory(addonId); if (Files.isDirectory(addonPath)) { - try { - List bundleFiles = Files.list(addonPath).collect(Collectors.toList()); + try (Stream files = Files.list(addonPath)) { + List bundleFiles = files.toList(); if (bundleFiles.size() != 1) { throw new MarketplaceHandlerException( "The local cache folder doesn't contain a single file: " + addonPath, null); @@ -122,8 +122,10 @@ protected void uninstallBundle(BundleContext bundleContext, String addonId) thro try { Path addonPath = getAddonCacheDirectory(addonId); if (Files.isDirectory(addonPath)) { - for (Path bundleFile : Files.list(addonPath).collect(Collectors.toList())) { - Files.delete(bundleFile); + try (Stream files = Files.list(addonPath)) { + for (Path path : files.toList()) { + Files.delete(path); + } } } Files.delete(addonPath); @@ -147,9 +149,9 @@ protected void uninstallBundle(BundleContext bundleContext, String addonId) thro * @param bundleContext the {@link BundleContext} to use to look up the bundles */ protected void ensureCachedBundlesAreInstalled(BundleContext bundleContext) { - try { - if (Files.isDirectory(BUNDLE_CACHE_PATH)) { - Files.list(BUNDLE_CACHE_PATH).filter(Files::isDirectory).map(this::addonIdFromPath) + if (Files.isDirectory(BUNDLE_CACHE_PATH)) { + try (Stream files = Files.list(BUNDLE_CACHE_PATH)) { + files.filter(Files::isDirectory).map(this::addonIdFromPath) .filter(addonId -> !isBundleInstalled(bundleContext, addonId)).forEach(addonId -> { logger.info("Reinstalling missing marketplace bundle: {}", addonId); try { @@ -158,9 +160,10 @@ protected void ensureCachedBundlesAreInstalled(BundleContext bundleContext) { logger.warn("Failed reinstalling add-on from cache", e); } }); + + } catch (IOException e) { + logger.warn("Failed to re-install bundles: {}", e.getMessage()); } - } catch (IOException e) { - logger.warn("Failed to re-install bundles: {}", e.getMessage()); } } diff --git a/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/utils/BundledSoundFileHandler.java b/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/utils/BundledSoundFileHandler.java index 7adfb3a0ffe..05de1268042 100644 --- a/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/utils/BundledSoundFileHandler.java +++ b/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/utils/BundledSoundFileHandler.java @@ -21,6 +21,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Comparator; +import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.OpenHAB; @@ -71,12 +72,10 @@ public BundledSoundFileHandler() throws IOException { public void close() { System.setProperty(OpenHAB.CONFIG_DIR_PROG_ARGUMENT, OpenHAB.DEFAULT_CONFIG_FOLDER); - if (tmpdir != null) { - try { - Files.walk(tmpdir).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); - } catch (IOException ex) { - logger.error("Exception while deleting files", ex); - } + try (Stream files = Files.walk(tmpdir)) { + files.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } catch (IOException ex) { + logger.error("Exception while deleting files", ex); } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/lru/LRUMediaCache.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/lru/LRUMediaCache.java index d1c0b10f652..da1ce32b974 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/lru/LRUMediaCache.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/lru/LRUMediaCache.java @@ -20,6 +20,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; @@ -30,7 +31,7 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; -import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -112,21 +113,19 @@ public LRUMediaCache(@Reference StorageService storageService, long maxCacheSize } private void cleanCacheDirectory() throws IOException { - try { - List<@Nullable Path> filesInCacheFolder = Files.list(cacheFolder).collect(Collectors.toList()); + try (Stream files = Files.list(cacheFolder)) { + List filesInCacheFolder = new ArrayList<>(files.toList()); // 1 delete empty files - Iterator<@Nullable Path> fileDeleterIterator = filesInCacheFolder.iterator(); + Iterator fileDeleterIterator = filesInCacheFolder.iterator(); while (fileDeleterIterator.hasNext()) { Path path = fileDeleterIterator.next(); - if (path != null) { - File file = path.toFile(); - if (file.length() == 0) { - file.delete(); - String fileName = path.getFileName().toString(); - storage.remove(fileName); - fileDeleterIterator.remove(); - } + File file = path.toFile(); + if (file.length() == 0) { + file.delete(); + String fileName = path.getFileName().toString(); + storage.remove(fileName); + fileDeleterIterator.remove(); } } From 4d0dba1abc29ae61b5379112881f00a18954436a Mon Sep 17 00:00:00 2001 From: GiviMAD Date: Thu, 30 Mar 2023 20:33:21 +0200 Subject: [PATCH 010/126] Fix OOBE in item metadata descriptions providers (#3488) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miguel Álvarez --- .../MetadataCommandDescriptionProvider.java | 34 ++++++++----------- ...adataStateDescriptionFragmentProvider.java | 16 ++------- ...etadataCommandDescriptionProviderTest.java | 7 ++-- ...aStateDescriptionFragmentProviderTest.java | 5 ++- 4 files changed, 26 insertions(+), 36 deletions(-) diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataCommandDescriptionProvider.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataCommandDescriptionProvider.java index 94d807c8975..cba4baa9608 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataCommandDescriptionProvider.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataCommandDescriptionProvider.java @@ -63,18 +63,8 @@ public MetadataCommandDescriptionProvider(final @Reference MetadataRegistry meta if (metadata.getConfiguration().containsKey("options")) { Stream.of(metadata.getConfiguration().get("options").toString().split(",")).forEach(o -> { if (o.contains("=")) { - String command; - String label; - if (o.startsWith("\"")) { - String[] parts = o.trim().split("\"=\""); - command = removeSurroundingQuotes(parts[0]); - label = removeSurroundingQuotes(parts[1]); - } else { - String[] parts = o.trim().split("="); - command = parts[0]; - label = parts[1]; - } - commandDescription.addCommandOption(new CommandOption(command.trim(), label.trim())); + var pair = parseValueLabelPair(o.trim()); + commandDescription.addCommandOption(new CommandOption(pair[0], pair[1])); } else { commandDescription.addCommandOption(new CommandOption(o.trim(), null)); } @@ -91,14 +81,18 @@ public MetadataCommandDescriptionProvider(final @Reference MetadataRegistry meta return null; } - public static String removeSurroundingQuotes(String input) { - String output = input; - if (input.startsWith("\"")) { - output = output.substring(1); + public static String[] parseValueLabelPair(String text) { + String value; + String label; + if (text.startsWith("\"") && text.contains("\"=\"") && text.endsWith("\"")) { + String[] parts = text.split("\"=\""); + value = parts[0].substring(1); + label = parts[1].substring(0, parts[1].length() - 1); + } else { + String[] parts = text.split("="); + value = parts[0]; + label = parts[1]; } - if (input.endsWith("\"")) { - output = output.substring(0, output.length() - 1); - } - return output; + return new String[] { value.trim(), label.trim() }; } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataStateDescriptionFragmentProvider.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataStateDescriptionFragmentProvider.java index 1c4cc01fa4e..5bafdffaa17 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataStateDescriptionFragmentProvider.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataStateDescriptionFragmentProvider.java @@ -12,7 +12,7 @@ */ package org.openhab.core.internal.items; -import static org.openhab.core.internal.items.MetadataCommandDescriptionProvider.removeSurroundingQuotes; +import static org.openhab.core.internal.items.MetadataCommandDescriptionProvider.parseValueLabelPair; import java.math.BigDecimal; import java.math.BigInteger; @@ -106,18 +106,8 @@ public MetadataStateDescriptionFragmentProvider(final @Reference MetadataRegistr List stateOptions = Stream .of(metadata.getConfiguration().get("options").toString().split(",")).map(o -> { if (o.contains("=")) { - String value; - String label; - if (o.startsWith("\"")) { - String[] parts = o.trim().split("\"=\""); - value = removeSurroundingQuotes(parts[0]); - label = removeSurroundingQuotes(parts[1]); - } else { - String[] parts = o.trim().split("="); - value = parts[0]; - label = parts[1]; - } - return new StateOption(value.trim(), label.trim()); + var pair = parseValueLabelPair(o.trim()); + return new StateOption(pair[0], pair[1]); } else { return new StateOption(o.trim(), null); } diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/items/MetadataCommandDescriptionProviderTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/items/MetadataCommandDescriptionProviderTest.java index 926e7649b2c..9bf79f3a3b8 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/items/MetadataCommandDescriptionProviderTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/items/MetadataCommandDescriptionProviderTest.java @@ -97,14 +97,14 @@ public void testEmptyConfig() throws Exception { public void testOptions() throws Exception { MetadataKey metadataKey = new MetadataKey("commandDescription", ITEM_NAME); Map metadataConfig = new HashMap<>(); - metadataConfig.put("options", "OPTION1,OPTION2 , 3 =Option 3 "); + metadataConfig.put("options", "OPTION1,OPTION2 , 3 =Option 3 , \" 4=4 \"=\"Option 4 \""); Metadata metadata = new Metadata(metadataKey, "N/A", metadataConfig); metadataRegistryMock.added(managedProviderMock, metadata); CommandDescription commandDescription = commandDescriptionProvider.getCommandDescription(ITEM_NAME, null); assertNotNull(commandDescription); assertNotNull(commandDescription.getCommandOptions()); - assertEquals(3, commandDescription.getCommandOptions().size()); + assertEquals(4, commandDescription.getCommandOptions().size()); Iterator it = commandDescription.getCommandOptions().iterator(); CommandOption commandOption = it.next(); @@ -116,5 +116,8 @@ public void testOptions() throws Exception { commandOption = it.next(); assertEquals("3", commandOption.getCommand()); assertEquals("Option 3", commandOption.getLabel()); + commandOption = it.next(); + assertEquals("4=4", commandOption.getCommand()); + assertEquals("Option 4", commandOption.getLabel()); } } diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/items/MetadataStateDescriptionFragmentProviderTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/items/MetadataStateDescriptionFragmentProviderTest.java index 0891736188a..21ed971171a 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/items/MetadataStateDescriptionFragmentProviderTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/items/MetadataStateDescriptionFragmentProviderTest.java @@ -96,7 +96,7 @@ public void testFragment() throws Exception { metadataConfig.put("max", "34"); metadataConfig.put("step", 3); metadataConfig.put("readOnly", "true"); - metadataConfig.put("options", "OPTION1,OPTION2 , 3 =Option 3 "); + metadataConfig.put("options", "OPTION1,OPTION2 , 3 =Option 3 ,\"4=4\"=\" Option=4 \" "); Metadata metadata = new Metadata(metadataKey, "N/A", metadataConfig); metadataRegistryMock.added(managedProviderMock, metadata); @@ -119,5 +119,8 @@ public void testFragment() throws Exception { stateOption = it.next(); assertEquals("3", stateOption.getValue()); assertEquals("Option 3", stateOption.getLabel()); + stateOption = it.next(); + assertEquals("4=4", stateOption.getValue()); + assertEquals("Option=4", stateOption.getLabel()); } } From 4182980ec89357047b60a92f2b7f0b4ae7350260 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Fri, 31 Mar 2023 23:01:40 +0200 Subject: [PATCH 011/126] Add ItemStateUpdatedEvent and enable group channel-links (#3141) * Add (Group)ItemStateUpdatedEvent Signed-off-by: Jan N. Klug --- .../handler/ItemStateTriggerHandler.java | 8 +- .../thing/internal/CommunicationManager.java | 10 ++- .../org/openhab/core/items/GenericItem.java | 13 +++- .../org/openhab/core/items/GroupItem.java | 14 +++- .../events/GroupItemStateChangedEvent.java | 3 +- .../items/events/GroupStateUpdatedEvent.java | 58 ++++++++++++++ .../core/items/events/ItemEventFactory.java | 75 ++++++++++++++++++- .../core/items/events/ItemStateEvent.java | 4 +- .../items/events/ItemStateUpdatedEvent.java | 68 +++++++++++++++++ .../openhab/core/items/GenericItemTest.java | 41 ++++++++-- .../core/library/items/NumberItemTest.java | 53 +++++++++++++ .../internal/BasicConditionHandlerTest.java | 4 +- .../openhab/core/items/GroupItemOSGiTest.java | 63 +++++++++++----- .../CommunicationManagerOSGiTest.java | 8 +- 14 files changed, 374 insertions(+), 48 deletions(-) create mode 100644 bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/GroupStateUpdatedEvent.java create mode 100644 bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/ItemStateUpdatedEvent.java diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateTriggerHandler.java index 702e1977731..c931b4ca6ec 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateTriggerHandler.java @@ -31,7 +31,7 @@ import org.openhab.core.items.events.ItemAddedEvent; import org.openhab.core.items.events.ItemRemovedEvent; import org.openhab.core.items.events.ItemStateChangedEvent; -import org.openhab.core.items.events.ItemStateEvent; +import org.openhab.core.items.events.ItemStateUpdatedEvent; import org.openhab.core.types.State; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; @@ -77,7 +77,7 @@ public ItemStateTriggerHandler(Trigger module, String ruleUID, BundleContext bun this.previousState = (String) module.getConfiguration().get(CFG_PREVIOUS_STATE); this.ruleUID = ruleUID; if (UPDATE_MODULE_TYPE_ID.equals(module.getTypeUID())) { - this.types = Set.of(ItemStateEvent.TYPE, ItemAddedEvent.TYPE, ItemRemovedEvent.TYPE); + this.types = Set.of(ItemStateUpdatedEvent.TYPE, ItemAddedEvent.TYPE, ItemRemovedEvent.TYPE); } else { this.types = Set.of(ItemStateChangedEvent.TYPE, GroupItemStateChangedEvent.TYPE, ItemAddedEvent.TYPE, ItemRemovedEvent.TYPE); @@ -122,9 +122,9 @@ public void receive(Event event) { logger.trace("Received Event: Source: {} Topic: {} Type: {} Payload: {}", event.getSource(), event.getTopic(), event.getType(), event.getPayload()); Map values = new HashMap<>(); - if (event instanceof ItemStateEvent && UPDATE_MODULE_TYPE_ID.equals(module.getTypeUID())) { + if (event instanceof ItemStateUpdatedEvent && UPDATE_MODULE_TYPE_ID.equals(module.getTypeUID())) { String state = this.state; - State itemState = ((ItemStateEvent) event).getItemState(); + State itemState = ((ItemStateUpdatedEvent) event).getItemState(); if ((state == null || state.equals(itemState.toFullString()))) { values.put("state", itemState); } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java index b41c1ca5014..e2b3a9b8b1d 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java @@ -41,8 +41,10 @@ import org.openhab.core.items.ItemStateConverter; import org.openhab.core.items.ItemUtil; import org.openhab.core.items.events.AbstractItemRegistryEvent; +import org.openhab.core.items.events.GroupStateUpdatedEvent; import org.openhab.core.items.events.ItemCommandEvent; import org.openhab.core.items.events.ItemStateEvent; +import org.openhab.core.items.events.ItemStateUpdatedEvent; import org.openhab.core.library.items.NumberItem; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.QuantityType; @@ -115,7 +117,7 @@ public void onStateUpdateFromItem(State state) { public static final long THINGHANDLER_EVENT_TIMEOUT = TimeUnit.SECONDS.toMillis(30); private static final Set SUBSCRIBED_EVENT_TYPES = Set.of(ItemStateEvent.TYPE, ItemCommandEvent.TYPE, - ChannelTriggeredEvent.TYPE); + GroupStateUpdatedEvent.TYPE, ChannelTriggeredEvent.TYPE); private final Logger logger = LoggerFactory.getLogger(CommunicationManager.class); @@ -179,8 +181,8 @@ public Set getSubscribedEventTypes() { @Override public void receive(Event event) { - if (event instanceof ItemStateEvent) { - receiveUpdate((ItemStateEvent) event); + if (event instanceof ItemStateUpdatedEvent) { + receiveUpdate((ItemStateUpdatedEvent) event); } else if (event instanceof ItemCommandEvent) { receiveCommand((ItemCommandEvent) event); } else if (event instanceof ChannelTriggeredEvent) { @@ -358,7 +360,7 @@ private void receiveCommand(ItemCommandEvent commandEvent) { }); } - private void receiveUpdate(ItemStateEvent updateEvent) { + private void receiveUpdate(ItemStateUpdatedEvent updateEvent) { final String itemName = updateEvent.getItemName(); final State newState = updateEvent.getItemState(); handleEvent(itemName, newState, updateEvent.getSource(), s -> acceptedStateTypeMap.get(s), diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GenericItem.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GenericItem.java index 5e7c3366b01..dd0a53ec29e 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GenericItem.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GenericItem.java @@ -231,14 +231,23 @@ protected final void applyState(State state) { State oldState = this.state; this.state = state; notifyListeners(oldState, state); + sendStateUpdatedEvent(state); if (!oldState.equals(state)) { sendStateChangedEvent(state, oldState); } } + private void sendStateUpdatedEvent(State newState) { + EventPublisher eventPublisher1 = this.eventPublisher; + if (eventPublisher1 != null) { + eventPublisher1.post(ItemEventFactory.createStateUpdatedEvent(this.name, newState, null)); + } + } + private void sendStateChangedEvent(State newState, State oldState) { - if (eventPublisher != null) { - eventPublisher.post(ItemEventFactory.createStateChangedEvent(this.name, newState, oldState)); + EventPublisher eventPublisher1 = this.eventPublisher; + if (eventPublisher1 != null) { + eventPublisher1.post(ItemEventFactory.createStateChangedEvent(this.name, newState, oldState)); } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GroupItem.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GroupItem.java index 2125ed7ac7f..546d435fc58 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GroupItem.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GroupItem.java @@ -25,6 +25,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.events.EventPublisher; import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.events.ItemEventFactory; import org.openhab.core.service.CommandDescriptionService; @@ -371,6 +372,7 @@ public void stateUpdated(Item item, State state) { State calculatedState = function.calculate(getStateMembers(getMembers())); newState = itemStateConverter.convertToAcceptedState(calculatedState, baseItem); setState(newState); + sendGroupStateUpdatedEvent(item.getName(), newState); } if (!oldState.equals(newState)) { sendGroupStateChangedEvent(item.getName(), newState, oldState); @@ -413,9 +415,17 @@ public void setUnitProvider(@Nullable UnitProvider unitProvider) { } } + private void sendGroupStateUpdatedEvent(String memberName, State state) { + EventPublisher eventPublisher1 = this.eventPublisher; + if (eventPublisher1 != null) { + eventPublisher1.post(ItemEventFactory.createGroupStateEvent(getName(), memberName, state, null)); + } + } + private void sendGroupStateChangedEvent(String memberName, State newState, State oldState) { - if (eventPublisher != null) { - eventPublisher + EventPublisher eventPublisher1 = this.eventPublisher; + if (eventPublisher1 != null) { + eventPublisher1 .post(ItemEventFactory.createGroupStateChangedEvent(getName(), memberName, newState, oldState)); } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/GroupItemStateChangedEvent.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/GroupItemStateChangedEvent.java index 171aae7ff81..0545c5f1573 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/GroupItemStateChangedEvent.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/GroupItemStateChangedEvent.java @@ -17,7 +17,8 @@ /** * {@link GroupItemStateChangedEvent}s can be used to deliver group item state changes through the openHAB event bus. In - * contrast to the {@link GroupItemStateEvent} the {@link GroupItemStateChangedEvent} is only sent if the state changed. + * contrast to the {@link GroupStateUpdatedEvent} the {@link GroupItemStateChangedEvent} is only sent if the state + * changed. * State events must be created with the {@link ItemEventFactory}. * * @author Christoph Knauf - Initial contribution diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/GroupStateUpdatedEvent.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/GroupStateUpdatedEvent.java new file mode 100644 index 00000000000..9a1d8d773c7 --- /dev/null +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/GroupStateUpdatedEvent.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.items.events; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.types.State; + +/** + * {@link GroupStateUpdatedEvent}s can be used to deliver group item state updates through the openHAB event bus. + * In contrast to the {@link GroupItemStateChangedEvent} it is always sent. + * State events must be created with the {@link ItemEventFactory}. + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class GroupStateUpdatedEvent extends ItemStateUpdatedEvent { + + /** + * The group item state changed event type. + */ + public static final String TYPE = GroupStateUpdatedEvent.class.getSimpleName(); + + private final String memberName; + + protected GroupStateUpdatedEvent(String topic, String payload, String itemName, String memberName, + State newItemState, @Nullable String source) { + super(topic, payload, itemName, newItemState, source); + this.memberName = memberName; + } + + /** + * @return the name of the changed group member + */ + public String getMemberName() { + return this.memberName; + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public String toString() { + return String.format("Group '%s' updated to %s through %s", itemName, itemState, memberName); + } +} diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/ItemEventFactory.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/ItemEventFactory.java index b380be7270c..ccf1cababa0 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/ItemEventFactory.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/ItemEventFactory.java @@ -50,10 +50,14 @@ public class ItemEventFactory extends AbstractEventFactory { private static final String ITEM_STATE_EVENT_TOPIC = "openhab/items/{itemName}/state"; + private static final String ITEM_STATE_UPDATED_EVENT_TOPIC = "openhab/items/{itemName}/stateupdated"; + private static final String ITEM_STATE_PREDICTED_EVENT_TOPIC = "openhab/items/{itemName}/statepredicted"; private static final String ITEM_STATE_CHANGED_EVENT_TOPIC = "openhab/items/{itemName}/statechanged"; + private static final String GROUP_STATE_EVENT_TOPIC = "openhab/items/{itemName}/{memberName}/stateupdated"; + private static final String GROUPITEM_STATE_CHANGED_EVENT_TOPIC = "openhab/items/{itemName}/{memberName}/statechanged"; private static final String ITEM_ADDED_EVENT_TOPIC = "openhab/items/{itemName}/added"; @@ -67,8 +71,8 @@ public class ItemEventFactory extends AbstractEventFactory { */ public ItemEventFactory() { super(Set.of(ItemCommandEvent.TYPE, ItemStateEvent.TYPE, ItemStatePredictedEvent.TYPE, - ItemStateChangedEvent.TYPE, ItemAddedEvent.TYPE, ItemUpdatedEvent.TYPE, ItemRemovedEvent.TYPE, - GroupItemStateChangedEvent.TYPE)); + ItemStateUpdatedEvent.TYPE, ItemStateChangedEvent.TYPE, ItemAddedEvent.TYPE, ItemUpdatedEvent.TYPE, + ItemRemovedEvent.TYPE, GroupStateUpdatedEvent.TYPE, GroupItemStateChangedEvent.TYPE)); } @Override @@ -80,6 +84,8 @@ protected Event createEventByType(String eventType, String topic, String payload return createStateEvent(topic, payload, source); } else if (ItemStatePredictedEvent.TYPE.equals(eventType)) { return createStatePredictedEvent(topic, payload); + } else if (ItemStateUpdatedEvent.TYPE.equals(eventType)) { + return createStateUpdatedEvent(topic, payload); } else if (ItemStateChangedEvent.TYPE.equals(eventType)) { return createStateChangedEvent(topic, payload); } else if (ItemAddedEvent.TYPE.equals(eventType)) { @@ -88,12 +94,22 @@ protected Event createEventByType(String eventType, String topic, String payload return createUpdatedEvent(topic, payload); } else if (ItemRemovedEvent.TYPE.equals(eventType)) { return createRemovedEvent(topic, payload); + } else if (GroupStateUpdatedEvent.TYPE.equals(eventType)) { + return createGroupStateEvent(topic, payload); } else if (GroupItemStateChangedEvent.TYPE.equals(eventType)) { return createGroupStateChangedEvent(topic, payload); } throw new IllegalArgumentException("The event type '" + eventType + "' is not supported by this factory."); } + private Event createGroupStateEvent(String topic, String payload) { + String itemName = getItemName(topic); + String memberName = getMemberName(topic); + ItemEventPayloadBean bean = deserializePayload(payload, ItemEventPayloadBean.class); + State state = getState(bean.getType(), bean.getValue()); + return new GroupStateUpdatedEvent(topic, payload, itemName, memberName, state, null); + } + private Event createGroupStateChangedEvent(String topic, String payload) { String itemName = getItemName(topic); String memberName = getMemberName(topic); @@ -124,6 +140,13 @@ private Event createStatePredictedEvent(String topic, String payload) { return new ItemStatePredictedEvent(topic, payload, itemName, state, bean.isConfirmation()); } + private Event createStateUpdatedEvent(String topic, String payload) { + String itemName = getItemName(topic); + ItemEventPayloadBean bean = deserializePayload(payload, ItemEventPayloadBean.class); + State state = getState(bean.getType(), bean.getValue()); + return new ItemStateUpdatedEvent(topic, payload, itemName, state, null); + } + private Event createStateChangedEvent(String topic, String payload) { String itemName = getItemName(topic); ItemStateChangedEventPayloadBean bean = deserializePayload(payload, ItemStateChangedEventPayloadBean.class); @@ -268,6 +291,54 @@ public static ItemEvent createStateEvent(String itemName, State state) { return createStateEvent(itemName, state, null); } + /** + * Creates an item state updated event. + * + * @param itemName the name of the item to report the state update for + * @param state the new state + * @return the created item state update event + * @throws IllegalArgumentException if itemName or state is null + */ + public static ItemStateUpdatedEvent createStateUpdatedEvent(String itemName, State state) { + return createStateUpdatedEvent(itemName, state, null); + } + + /** + * Creates an item state updated event. + * + * @param itemName the name of the item to report the state update for + * @param state the new state + * @param source the name of the source identifying the sender (can be null) + * @return the created item state update event + * @throws IllegalArgumentException if itemName or state is null + */ + public static ItemStateUpdatedEvent createStateUpdatedEvent(String itemName, State state, @Nullable String source) { + assertValidArguments(itemName, state, "state"); + String topic = buildTopic(ITEM_STATE_UPDATED_EVENT_TOPIC, itemName); + ItemEventPayloadBean bean = new ItemEventPayloadBean(getStateType(state), state.toFullString()); + String payload = serializePayload(bean); + return new ItemStateUpdatedEvent(topic, payload, itemName, state, source); + } + + /** + * Creates an group item state updated event. + * + * @param groupName the name of the group to report the state update for + * @param member the name of the item that updated the group state + * @param state the new state + * @param source the name of the source identifying the sender (can be null) + * @return the created group item state update event + * @throws IllegalArgumentException if groupName or state is null + */ + public static GroupStateUpdatedEvent createGroupStateEvent(String groupName, String member, State state, + @Nullable String source) { + assertValidArguments(groupName, member, state, "state"); + String topic = buildGroupTopic(GROUP_STATE_EVENT_TOPIC, groupName, member); + ItemEventPayloadBean bean = new ItemEventPayloadBean(getStateType(state), state.toFullString()); + String payload = serializePayload(bean); + return new GroupStateUpdatedEvent(topic, payload, groupName, member, state, source); + } + /** * Creates an item state predicted event. * diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/ItemStateEvent.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/ItemStateEvent.java index 80865370ca5..90d6eb326a4 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/ItemStateEvent.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/ItemStateEvent.java @@ -30,7 +30,7 @@ public class ItemStateEvent extends ItemEvent { */ public static final String TYPE = ItemStateEvent.class.getSimpleName(); - private final State itemState; + protected final State itemState; /** * Constructs a new item state event. @@ -62,6 +62,6 @@ public State getItemState() { @Override public String toString() { - return String.format("Item '%s' updated to %s", itemName, itemState); + return String.format("Item '%s' shall update to %s", itemName, itemState); } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/ItemStateUpdatedEvent.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/ItemStateUpdatedEvent.java new file mode 100644 index 00000000000..0b582c282c4 --- /dev/null +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/ItemStateUpdatedEvent.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.items.events; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.types.State; + +/** + * {@link ItemStateUpdatedEvent}s can be used to report item status updates through the openHAB event bus. + * State update events must be created with the {@link ItemEventFactory}. + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class ItemStateUpdatedEvent extends ItemEvent { + + /** + * The item state event type. + */ + public static final String TYPE = ItemStateUpdatedEvent.class.getSimpleName(); + + protected final State itemState; + + /** + * Constructs a new item state event. + * + * @param topic the topic + * @param payload the payload + * @param itemName the item name + * @param itemState the item state + * @param source the source, can be null + */ + protected ItemStateUpdatedEvent(String topic, String payload, String itemName, State itemState, + @Nullable String source) { + super(topic, payload, itemName, source); + this.itemState = itemState; + } + + @Override + public String getType() { + return TYPE; + } + + /** + * Gets the item state. + * + * @return the item state + */ + public State getItemState() { + return itemState; + } + + @Override + public String toString() { + return String.format("Item '%s' updated to %s", itemName, itemState); + } +} diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/items/GenericItemTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/items/GenericItemTest.java index 370b86025ab..8a60f9ec776 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/items/GenericItemTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/items/GenericItemTest.java @@ -27,7 +27,9 @@ import org.mockito.ArgumentCaptor; import org.openhab.core.events.EventPublisher; import org.openhab.core.i18n.UnitProvider; +import org.openhab.core.items.events.ItemEvent; import org.openhab.core.items.events.ItemStateChangedEvent; +import org.openhab.core.items.events.ItemStateUpdatedEvent; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.RawType; @@ -58,24 +60,51 @@ public void testItemPostsEventsCorrectly() { item.setEventPublisher(publisher); State oldState = item.getState(); - // State changes -> one change event is fired + // State changes -> one update and one change event is fired item.setState(new RawType(new byte[0], RawType.DEFAULT_MIME_TYPE)); - ArgumentCaptor captor = ArgumentCaptor.forClass(ItemStateChangedEvent.class); + ArgumentCaptor captor = ArgumentCaptor.forClass(ItemEvent.class); - verify(publisher, times(1)).post(captor.capture()); + verify(publisher, times(2)).post(captor.capture()); - ItemStateChangedEvent change = captor.getValue(); + List events = captor.getAllValues(); + assertEquals(2, events.size()); + // first event should be updated event + assertInstanceOf(ItemStateUpdatedEvent.class, events.get(0)); + ItemStateUpdatedEvent updated = (ItemStateUpdatedEvent) events.get(0); + assertEquals(item.getName(), updated.getItemName()); + assertEquals("openhab/items/member1/stateupdated", updated.getTopic()); + assertEquals(item.getState(), updated.getItemState()); + assertEquals(ItemStateUpdatedEvent.TYPE, updated.getType()); + + // second event should be changed event + assertInstanceOf(ItemStateChangedEvent.class, events.get(1)); + ItemStateChangedEvent change = (ItemStateChangedEvent) events.get(1); assertEquals(item.getName(), change.getItemName()); assertEquals("openhab/items/member1/statechanged", change.getTopic()); assertEquals(oldState, change.getOldItemState()); assertEquals(item.getState(), change.getItemState()); assertEquals(ItemStateChangedEvent.TYPE, change.getType()); - // State doesn't change -> no event is fired + // reset invocations and captor + clearInvocations(publisher); + captor = ArgumentCaptor.forClass(ItemEvent.class); + + // State doesn't change -> only update event is fired item.setState(item.getState()); - verifyNoMoreInteractions(publisher); + verify(publisher).post(captor.capture()); + + events = captor.getAllValues(); + assertEquals(1, events.size()); // two before and one additional + + // event should be updated event + assertInstanceOf(ItemStateUpdatedEvent.class, events.get(0)); + updated = (ItemStateUpdatedEvent) events.get(0); + assertEquals(item.getName(), updated.getItemName()); + assertEquals("openhab/items/member1/stateupdated", updated.getTopic()); + assertEquals(item.getState(), updated.getItemState()); + assertEquals(ItemStateUpdatedEvent.TYPE, updated.getType()); } @Test diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/items/NumberItemTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/items/NumberItemTest.java index 147c85ba65f..6d4612ac10d 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/items/NumberItemTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/items/NumberItemTest.java @@ -14,13 +14,18 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.*; +import java.util.List; + import javax.measure.quantity.Energy; import javax.measure.quantity.Temperature; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -29,9 +34,11 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.openhab.core.events.Event; import org.openhab.core.events.EventPublisher; import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.events.ItemCommandEvent; +import org.openhab.core.items.events.ItemStateUpdatedEvent; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.PercentType; @@ -57,12 +64,15 @@ public class NumberItemTest { private static final String ITEM_NAME = "test"; private @Mock @NonNullByDefault({}) StateDescriptionService stateDescriptionServiceMock; + private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock; + private @Mock @NonNullByDefault({}) EventPublisher eventPublisherMock; @BeforeEach public void setup() { when(stateDescriptionServiceMock.getStateDescription(ITEM_NAME, null)) .thenReturn(StateDescriptionFragmentBuilder.create().withPattern("%.1f " + UnitUtils.UNIT_PLACEHOLDER) .build().toStateDescription()); + when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS); } @Test @@ -286,4 +296,47 @@ void testNoUnitWhenUnitPlaceholderUsed() { assertThat(item.getState(), is(new QuantityType<>("329 kWh"))); assertThat(item.getUnit(), is(nullValue())); } + + public void quantityTypeCorrectlySetWithDifferentUnit() { + NumberItem numberItem = new NumberItem("Number:Temperature", ITEM_NAME); + numberItem.setUnitProvider(unitProviderMock); + numberItem.setEventPublisher(eventPublisherMock); + numberItem.setState(new QuantityType<>("140 °F")); + + assertThat(numberItem.getState(), Matchers.is(new QuantityType<>("60 °C"))); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Event.class); + verify(eventPublisherMock, times(2)).post(captor.capture()); + + List events = captor.getAllValues(); + assertThat(events, hasSize(2)); + + assertThat(events.get(0), Matchers.is(instanceOf(ItemStateUpdatedEvent.class))); + + ItemStateUpdatedEvent updatedEvent = (ItemStateUpdatedEvent) events.get(0); + assertThat(updatedEvent.getItemName(), Matchers.is(ITEM_NAME)); + assertThat(updatedEvent.getItemState(), Matchers.is(new QuantityType<>("60°C"))); + } + + @Test + public void decimalTypeCorrectlySetWithUnit() { + NumberItem numberItem = new NumberItem("Number:Temperature", ITEM_NAME); + numberItem.setUnitProvider(unitProviderMock); + numberItem.setEventPublisher(eventPublisherMock); + numberItem.setState(new DecimalType(10)); + + assertThat(numberItem.getState(), Matchers.is(new QuantityType<>("10 °C"))); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Event.class); + verify(eventPublisherMock, times(2)).post(captor.capture()); + + List events = captor.getAllValues(); + assertThat(events, hasSize(2)); + + assertThat(events.get(0), Matchers.is(instanceOf(ItemStateUpdatedEvent.class))); + + ItemStateUpdatedEvent updatedEvent = (ItemStateUpdatedEvent) events.get(0); + assertThat(updatedEvent.getItemName(), Matchers.is(ITEM_NAME)); + assertThat(updatedEvent.getItemState(), Matchers.is(new QuantityType<>("10°C"))); + } } diff --git a/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/BasicConditionHandlerTest.java b/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/BasicConditionHandlerTest.java index 2d04b921f3f..3a06424bad8 100644 --- a/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/BasicConditionHandlerTest.java +++ b/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/BasicConditionHandlerTest.java @@ -179,7 +179,7 @@ public void receive(Event event) { logger.info("Rule is enabled and idle"); logger.info("Send and wait for item state is ON"); - eventPublisher.post(ItemEventFactory.createStateEvent(testItemName1, OnOffType.ON)); + eventPublisher.post(ItemEventFactory.createStateUpdatedEvent(testItemName1, OnOffType.ON)); waitForAssert(() -> { assertThat(itemEvent, is(notNullValue())); @@ -194,7 +194,7 @@ public void receive(Event event) { // prepare the execution itemEvent = null; - eventPublisher.post(ItemEventFactory.createStateEvent(testItemName1, OnOffType.ON)); + eventPublisher.post(ItemEventFactory.createStateUpdatedEvent(testItemName1, OnOffType.ON)); waitForAssert(() -> { assertThat(itemEvent, is(nullValue())); }); diff --git a/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/GroupItemOSGiTest.java b/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/GroupItemOSGiTest.java index 19501fc7e11..e1d8afe2c82 100644 --- a/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/GroupItemOSGiTest.java +++ b/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/GroupItemOSGiTest.java @@ -46,7 +46,9 @@ import org.openhab.core.internal.items.ItemStateConverterImpl; import org.openhab.core.items.dto.GroupFunctionDTO; import org.openhab.core.items.events.GroupItemStateChangedEvent; +import org.openhab.core.items.events.GroupStateUpdatedEvent; import org.openhab.core.items.events.ItemCommandEvent; +import org.openhab.core.items.events.ItemStateUpdatedEvent; import org.openhab.core.items.events.ItemUpdatedEvent; import org.openhab.core.library.CoreItemFactory; import org.openhab.core.library.items.ColorItem; @@ -422,6 +424,7 @@ public void assertCyclicGroupItemsCalculateStateWithSubGroupFunction() { public void assertThatGroupItemPostsEventsForChangesCorrectly() { // from ItemEventFactory.GROUPITEM_STATE_CHANGED_EVENT_TOPIC String groupitemStateChangedEventTopic = "openhab/items/{itemName}/{memberName}/statechanged"; + String groupitemStateUpdatedEventTopic = "openhab/items/{itemName}/{memberName}/stateupdated"; events.clear(); GroupItem groupItem = new GroupItem("root", new SwitchItem("mySwitch"), new GroupFunction.Equality()); @@ -432,10 +435,21 @@ public void assertThatGroupItemPostsEventsForChangesCorrectly() { groupItem.setEventPublisher(publisher); State oldGroupState = groupItem.getState(); - // State changes -> one change event is fired + // State changes -> one update and one change event is fired member.setState(OnOffType.ON); - waitForAssert(() -> assertThat(events.size(), is(1))); + waitForAssert(() -> assertThat(events.size(), is(2))); + + List updates = events.stream().filter(it -> it instanceof GroupStateUpdatedEvent) + .collect(Collectors.toList()); + assertThat(updates.size(), is(1)); + + GroupStateUpdatedEvent update = (GroupStateUpdatedEvent) updates.get(0); + assertThat(update.getItemName(), is(groupItem.getName())); + assertThat(update.getMemberName(), is(member.getName())); + assertThat(update.getTopic(), is(groupitemStateUpdatedEventTopic.replace("{memberName}", member.getName()) + .replace("{itemName}", groupItem.getName()))); + assertThat(update.getItemState(), is(groupItem.getState())); List changes = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent) .collect(Collectors.toList()); @@ -451,9 +465,10 @@ public void assertThatGroupItemPostsEventsForChangesCorrectly() { events.clear(); - // State doesn't change -> no events are fired + // State doesn't change -> only update event is posted member.setState(member.getState()); - assertThat(events.size(), is(0)); + waitForAssert(() -> assertThat(events.size(), is(1))); + assertThat(events.get(0), is(instanceOf(ItemStateUpdatedEvent.class))); } @Test @@ -470,10 +485,10 @@ public void assertThatGroupItemChangesRespectGroupFunctionOR() throws Interrupte groupItem.setEventPublisher(publisher); - // State changes -> one change event is fired + // State changes -> one update and one change event is fired sw1.setState(OnOffType.ON); - waitForAssert(() -> assertThat(events, hasSize(1))); + waitForAssert(() -> assertThat(events, hasSize(2))); List groupItemStateChangedEvents = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent) .collect(Collectors.toList()); @@ -490,13 +505,13 @@ public void assertThatGroupItemChangesRespectGroupFunctionOR() throws Interrupte events.clear(); - // State does not change -> no change event is fired + // State does not change -> only update event is fired sw2.setState(OnOffType.ON); // wait to see that the event doesn't fire Thread.sleep(WAIT_EVENT_TO_BE_HANDLED); - waitForAssert(() -> assertThat(events, hasSize(0))); + waitForAssert(() -> assertThat(events, hasSize(1))); } @Test @@ -543,15 +558,19 @@ public void assertThatGroupItemChangesRespectGroupFunctionORWithUNDEF() throws I groupItem.setEventPublisher(publisher); - // State changes -> one change event is fired + // State changes -> one update and one change event is fired sw1.setState(OnOffType.ON); - waitForAssert(() -> assertThat(events, hasSize(1))); + waitForAssert(() -> assertThat(events, hasSize(2))); List changes = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent) .collect(Collectors.toList()); assertThat(changes, hasSize(1)); + List updates = events.stream().filter(it -> it instanceof GroupStateUpdatedEvent) + .collect(Collectors.toList()); + assertThat(updates, hasSize(1)); + GroupItemStateChangedEvent change = (GroupItemStateChangedEvent) changes.get(0); assertThat(change.getItemName(), is(groupItem.getName())); @@ -563,15 +582,21 @@ public void assertThatGroupItemChangesRespectGroupFunctionORWithUNDEF() throws I events.clear(); - // State does not change -> no change event is fired + // State does not change -> only update event is fired sw2.setState(OnOffType.ON); sw2.setState(UnDefType.UNDEF); - // wait to see that the event doesn't fire + // wait to see that only state updated events are fired Thread.sleep(WAIT_EVENT_TO_BE_HANDLED); - assertThat(events, hasSize(0)); + assertThat(events, hasSize(2)); + + changes = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent).collect(Collectors.toList()); + assertThat(changes, hasSize(0)); + + updates = events.stream().filter(it -> it instanceof GroupStateUpdatedEvent).collect(Collectors.toList()); + assertThat(updates, hasSize(2)); assertThat(groupItem.getState(), is(OnOffType.ON)); } @@ -590,10 +615,10 @@ public void assertThatGroupItemChangesRespectGroupFunctionAND() { groupItem.setEventPublisher(publisher); - // State changes -> one change event is fired + // State changes -> one update and one change event is fired sw1.setState(OnOffType.ON); - waitForAssert(() -> assertThat(events, hasSize(1))); + waitForAssert(() -> assertThat(events, hasSize(2))); List changes = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent) .collect(Collectors.toList()); @@ -610,10 +635,10 @@ public void assertThatGroupItemChangesRespectGroupFunctionAND() { events.clear(); - // State changes -> one change event is fired + // State changes -> one update and one change event is fired sw2.setState(OnOffType.ON); - waitForAssert(() -> assertThat(events, hasSize(1))); + waitForAssert(() -> assertThat(events, hasSize(2))); changes = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent).collect(Collectors.toList()); assertThat(changes, hasSize(1)); @@ -727,7 +752,7 @@ public void assertThatGroupItemwithDimmeritemAcceptsGetsPercentTypeStateIfMember member1.setState(new PercentType(50)); - waitForAssert(() -> assertThat(events.size(), is(1))); + waitForAssert(() -> assertThat(events.size(), is(2))); List changes = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent) .collect(Collectors.toList()); @@ -746,7 +771,7 @@ public void assertThatGroupItemwithDimmeritemAcceptsGetsPercentTypeStateIfMember member2.setState(new PercentType(10)); - waitForAssert(() -> assertThat(events.size(), is(1))); + waitForAssert(() -> assertThat(events.size(), is(2))); changes = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent).collect(Collectors.toList()); assertThat(changes.size(), is(1)); diff --git a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/CommunicationManagerOSGiTest.java b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/CommunicationManagerOSGiTest.java index 3d4caccf051..faf25f40bd8 100644 --- a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/CommunicationManagerOSGiTest.java +++ b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/CommunicationManagerOSGiTest.java @@ -351,7 +351,7 @@ public void testItemCommandEventNotToSource() { @Test public void testItemStateEventSingleLink() { - manager.receive(ItemEventFactory.createStateEvent(ITEM_NAME_2, OnOffType.ON)); + manager.receive(ItemEventFactory.createStateUpdatedEvent(ITEM_NAME_2, OnOffType.ON)); waitForAssert(() -> { verify(stateProfileMock).onStateUpdateFromItem(eq(OnOffType.ON)); verify(triggerProfileMock).onStateUpdateFromItem(eq(OnOffType.ON)); @@ -362,7 +362,7 @@ public void testItemStateEventSingleLink() { @Test public void testItemStateEventMultiLink() { - manager.receive(ItemEventFactory.createStateEvent(ITEM_NAME_1, OnOffType.ON)); + manager.receive(ItemEventFactory.createStateUpdatedEvent(ITEM_NAME_1, OnOffType.ON)); waitForAssert(() -> { verify(stateProfileMock, times(2)).onStateUpdateFromItem(eq(OnOffType.ON)); verify(triggerProfileMock, times(2)).onStateUpdateFromItem(eq(OnOffType.ON)); @@ -374,7 +374,7 @@ public void testItemStateEventMultiLink() { @Test public void testItemStateEventNotToSource() { manager.receive( - ItemEventFactory.createStateEvent(ITEM_NAME_1, OnOffType.ON, STATE_CHANNEL_UID_2.getAsString())); + ItemEventFactory.createStateUpdatedEvent(ITEM_NAME_1, OnOffType.ON, STATE_CHANNEL_UID_2.getAsString())); waitForAssert(() -> { verify(stateProfileMock).onStateUpdateFromItem(eq(OnOffType.ON)); verify(triggerProfileMock, times(2)).onStateUpdateFromItem(eq(OnOffType.ON)); @@ -596,7 +596,7 @@ public void testItemStateEventTypeDowncast() { thing.setHandler(thingHandlerMock); when(thingRegistryMock.get(eq(THING_UID))).thenReturn(thing); - manager.receive(ItemEventFactory.createStateEvent(ITEM_NAME_2, HSBType.fromRGB(128, 128, 128))); + manager.receive(ItemEventFactory.createStateUpdatedEvent(ITEM_NAME_2, HSBType.fromRGB(128, 128, 128))); waitForAssert(() -> { ArgumentCaptor stateCaptor = ArgumentCaptor.forClass(State.class); verify(stateProfileMock).onStateUpdateFromItem(stateCaptor.capture()); From 003635dbfad713f33cd854434d5380e8f043c5ac Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Sat, 1 Apr 2023 17:32:12 +1000 Subject: [PATCH 012/126] Fix Semantics default synonyms (#3511) Fix incorrect Property id Signed-off-by: Jimmy Tanagra --- .../src/main/java/org/openhab/core/semantics/Property.java | 2 +- .../main/java/org/openhab/core/semantics/SemanticTags.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Property.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Property.java index 5caca212469..b2a003023a7 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Property.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Property.java @@ -20,6 +20,6 @@ * @author Kai Kreuzer - Initial contribution */ @NonNullByDefault -@TagInfo(id = "MeasurementProperty") +@TagInfo(id = "Property") public interface Property extends Tag { } diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTags.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTags.java index 6dd51feac59..e0a8d5db7b2 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTags.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTags.java @@ -22,6 +22,7 @@ import java.util.Set; import java.util.TreeMap; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -84,7 +85,10 @@ public static List getLabelAndSynonyms(Class tag, Locale String entry = rb.getString(tag.getAnnotation(TagInfo.class).id()); return List.of(entry.toLowerCase(locale).split(",")); } catch (MissingResourceException e) { - return List.of(tag.getAnnotation(TagInfo.class).label()); + TagInfo tagInfo = tag.getAnnotation(TagInfo.class); + Stream label = Stream.of(tagInfo.label()); + Stream synonyms = Stream.of(tagInfo.synonyms().split(",")); + return Stream.concat(label, synonyms).map(s -> s.toLowerCase(locale)).distinct().toList(); } } From 74052e7bcd1b63774a292738877d1f3102ac6291 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Sat, 1 Apr 2023 09:51:12 +0200 Subject: [PATCH 013/126] Code cleanup: Use Java17 features (#3508) Signed-off-by: Holger Friedrich --- .../openhab/core/cache/ExpiringCacheMap.java | 2 +- .../core/common/ThreadPoolManager.java | 8 ++--- .../common/registry/AbstractProvider.java | 2 +- .../common/registry/AbstractRegistry.java | 2 +- .../org/openhab/core/i18n/LocalizedKey.java | 5 +-- .../core/internal/auth/UserRegistryImpl.java | 6 ++-- .../common/AbstractInvocationHandler.java | 8 ++--- .../WrappedScheduledExecutorService.java | 3 +- .../core/internal/events/EventHandler.java | 8 ++--- .../core/internal/items/ExpireManager.java | 17 ++++------ .../core/internal/items/ItemBuilderImpl.java | 9 +++-- .../core/internal/items/ItemRegistryImpl.java | 33 +++++++++--------- .../items/ItemStateConverterImpl.java | 4 +-- .../core/internal/items/ItemUpdater.java | 3 +- ...adataStateDescriptionFragmentProvider.java | 24 ++++++------- .../internal/scheduler/CronSchedulerImpl.java | 8 ++--- .../internal/scheduler/SchedulerImpl.java | 3 +- .../internal/service/ReadyServiceImpl.java | 13 +++---- .../org/openhab/core/items/GroupItem.java | 34 +++++++++---------- .../core/items/ManagedItemProvider.java | 30 ++++++++-------- .../java/org/openhab/core/items/Metadata.java | 5 +-- .../openhab/core/items/dto/ItemDTOMapper.java | 6 ++-- .../events/AbstractItemEventSubscriber.java | 8 ++--- .../openhab/core/library/items/ColorItem.java | 14 ++++---- .../core/library/items/LocationItem.java | 5 ++- .../core/library/items/NumberItem.java | 14 ++++---- .../core/library/types/DecimalType.java | 8 ++--- .../openhab/core/library/types/HSBType.java | 2 +- .../openhab/core/library/types/PointType.java | 9 ++--- .../QuantityTypeArithmeticGroupFunction.java | 6 ++-- .../openhab/core/library/types/RawType.java | 5 +-- .../java/org/openhab/core/net/NetUtil.java | 16 ++++----- .../core/service/StartLevelService.java | 12 +++---- .../core/types/CommandDescriptionBuilder.java | 2 +- .../openhab/core/types/util/UnitUtils.java | 14 ++++---- .../cache/lru/LRUMediaCacheEntryTest.java | 7 ++-- .../core/cache/lru/LRUMediaCacheTest.java | 5 ++- .../internal/auth/UserRegistryImplTest.java | 6 ++-- .../internal/scheduler/SchedulerImplTest.java | 8 ++--- .../core/library/items/DimmerItemTest.java | 3 +- .../openhab/core/library/unit/UnitsTest.java | 3 +- 41 files changed, 168 insertions(+), 212 deletions(-) diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/ExpiringCacheMap.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/ExpiringCacheMap.java index 1b8474c224f..499d637bb69 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/ExpiringCacheMap.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/ExpiringCacheMap.java @@ -231,7 +231,7 @@ public synchronized void invalidate(K key) { * Invalidates all values in the cache. */ public synchronized void invalidateAll() { - items.values().forEach(item -> item.invalidateValue()); + items.values().forEach(ExpiringCache::invalidateValue); } /** diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/common/ThreadPoolManager.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/common/ThreadPoolManager.java index 8df3497ddad..94c72c6c1ac 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/common/ThreadPoolManager.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/common/ThreadPoolManager.java @@ -96,9 +96,9 @@ protected void modified(Map properties) { if (config == null) { configs.remove(poolName); } - if (config instanceof String) { + if (config instanceof String string) { try { - Integer poolSize = Integer.valueOf((String) config); + Integer poolSize = Integer.valueOf(string); configs.put(poolName, poolSize); ThreadPoolExecutor pool = (ThreadPoolExecutor) pools.get(poolName); if (pool instanceof ScheduledThreadPoolExecutor) { @@ -142,8 +142,8 @@ public static ScheduledExecutorService getScheduledPool(String poolName) { } } } - if (pool instanceof ScheduledExecutorService) { - return new UnstoppableScheduledExecutorService(poolName, (ScheduledExecutorService) pool); + if (pool instanceof ScheduledExecutorService service) { + return new UnstoppableScheduledExecutorService(poolName, service); } else { throw new IllegalArgumentException("Pool " + poolName + " is not a scheduled pool!"); } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/common/registry/AbstractProvider.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/common/registry/AbstractProvider.java index bdcc76d0ee1..9d076df8927 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/common/registry/AbstractProvider.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/common/registry/AbstractProvider.java @@ -36,7 +36,7 @@ public abstract class AbstractProvider<@NonNull E> implements Provider { private enum EventType { ADDED, REMOVED, - UPDATED; + UPDATED } protected final Logger logger = LoggerFactory.getLogger(AbstractProvider.class); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/common/registry/AbstractRegistry.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/common/registry/AbstractRegistry.java index 777e6c01099..6938670a993 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/common/registry/AbstractRegistry.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/common/registry/AbstractRegistry.java @@ -63,7 +63,7 @@ public abstract class AbstractRegistry<@NonNull E extends Identifiable, @NonN private enum EventType { ADDED, REMOVED, - UPDATED; + UPDATED } private final Logger logger = LoggerFactory.getLogger(AbstractRegistry.class); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/i18n/LocalizedKey.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/i18n/LocalizedKey.java index 2903d8eca59..f7bc6ce0f5c 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/i18n/LocalizedKey.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/i18n/LocalizedKey.java @@ -65,9 +65,6 @@ public boolean equals(@Nullable Object obj) { if (!Objects.equals(key, other.key)) { return false; } - if (!Objects.equals(locale, other.locale)) { - return false; - } - return true; + return Objects.equals(locale, other.locale); } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/auth/UserRegistryImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/auth/UserRegistryImpl.java index f53abe707da..60dacd37638 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/auth/UserRegistryImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/auth/UserRegistryImpl.java @@ -133,8 +133,7 @@ private Optional hash(String password, String salt, int iterations) { @Override public Authentication authenticate(Credentials credentials) throws AuthenticationException { - if (credentials instanceof UsernamePasswordCredentials) { - UsernamePasswordCredentials usernamePasswordCreds = (UsernamePasswordCredentials) credentials; + if (credentials instanceof UsernamePasswordCredentials usernamePasswordCreds) { User user = get(usernamePasswordCreds.getUsername()); if (user == null) { throw new AuthenticationException("User not found: " + usernamePasswordCreds.getUsername()); @@ -148,8 +147,7 @@ public Authentication authenticate(Credentials credentials) throws Authenticatio } return new Authentication(managedUser.getName(), managedUser.getRoles().stream().toArray(String[]::new)); - } else if (credentials instanceof UserApiTokenCredentials) { - UserApiTokenCredentials apiTokenCreds = (UserApiTokenCredentials) credentials; + } else if (credentials instanceof UserApiTokenCredentials apiTokenCreds) { String[] apiTokenParts = apiTokenCreds.getApiToken().split("\\."); if (apiTokenParts.length != 3 || !APITOKEN_PREFIX.equals(apiTokenParts[0])) { throw new AuthenticationException("Invalid API token format"); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/common/AbstractInvocationHandler.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/common/AbstractInvocationHandler.java index 83c10c1e7cd..84ab4478db9 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/common/AbstractInvocationHandler.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/common/AbstractInvocationHandler.java @@ -88,10 +88,10 @@ Runnable getTimeoutHandler() { void handleExecutionException(Method method, ExecutionException e) { Throwable cause = e.getCause(); - if (cause instanceof DuplicateExecutionException) { - handleDuplicate(method, (DuplicateExecutionException) cause); - } else if (cause instanceof InvocationTargetException) { - handleException(method, (InvocationTargetException) cause); + if (cause instanceof DuplicateExecutionException exception) { + handleDuplicate(method, exception); + } else if (cause instanceof InvocationTargetException exception) { + handleException(method, exception); } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/common/WrappedScheduledExecutorService.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/common/WrappedScheduledExecutorService.java index 4ed0b059755..2d3bf4f9556 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/common/WrappedScheduledExecutorService.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/common/WrappedScheduledExecutorService.java @@ -43,8 +43,7 @@ public WrappedScheduledExecutorService(int corePoolSize, ThreadFactory threadFac protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); Throwable actualThrowable = t; - if (actualThrowable == null && r instanceof Future) { - Future f = (Future) r; + if (actualThrowable == null && r instanceof Future f) { // The Future is the wrapper task around our scheduled Runnable. This is only "done" if an Exception // occurred, the Task was completed, or aborted. A periodic Task (scheduleWithFixedDelay etc.) is NEVER diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/events/EventHandler.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/events/EventHandler.java index 24958732c19..4217bec0a24 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/events/EventHandler.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/events/EventHandler.java @@ -75,11 +75,9 @@ public void handleEvent(org.osgi.service.event.Event osgiEvent) { Object topicObj = osgiEvent.getProperty("topic"); Object sourceObj = osgiEvent.getProperty("source"); - if (typeObj instanceof String && payloadObj instanceof String && topicObj instanceof String) { - String typeStr = (String) typeObj; - String payloadStr = (String) payloadObj; - String topicStr = (String) topicObj; - String sourceStr = (sourceObj instanceof String) ? (String) sourceObj : null; + if (typeObj instanceof String typeStr && payloadObj instanceof String payloadStr + && topicObj instanceof String topicStr) { + String sourceStr = (sourceObj instanceof String s) ? s : null; if (!typeStr.isEmpty() && !payloadStr.isEmpty() && !topicStr.isEmpty()) { handleEvent(typeStr, payloadStr, topicStr, sourceStr); } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ExpireManager.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ExpireManager.java index f83a8eef1f4..5be2b606881 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ExpireManager.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ExpireManager.java @@ -242,18 +242,15 @@ public void receive(Event event) { return; } - if (event instanceof ItemStateEvent) { - ItemStateEvent isEvent = (ItemStateEvent) event; + if (event instanceof ItemStateEvent isEvent) { if (!expireConfig.ignoreStateUpdates) { processEvent(itemName, isEvent.getItemState(), expireConfig, event.getClass()); } - } else if (event instanceof ItemCommandEvent) { - ItemCommandEvent icEvent = (ItemCommandEvent) event; + } else if (event instanceof ItemCommandEvent icEvent) { if (!expireConfig.ignoreCommands) { processEvent(itemName, icEvent.getItemCommand(), expireConfig, event.getClass()); } - } else if (event instanceof ItemStateChangedEvent) { - ItemStateChangedEvent icEvent = (ItemStateChangedEvent) event; + } else if (event instanceof ItemStateChangedEvent icEvent) { processEvent(itemName, icEvent.getItemState(), expireConfig, event.getClass()); } } @@ -386,10 +383,10 @@ public ExpireConfig(Item item, String configString, Map configur private boolean getBooleanConfigValue(Map configuration, String configKey) { boolean configValue; Object configValueObject = configuration.get(configKey); - if (configValueObject instanceof String) { - configValue = Boolean.parseBoolean((String) configValueObject); - } else if (configValueObject instanceof Boolean) { - configValue = (Boolean) configValueObject; + if (configValueObject instanceof String string) { + configValue = Boolean.parseBoolean(string); + } else if (configValueObject instanceof Boolean boolean1) { + configValue = boolean1; } else { configValue = false; } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemBuilderImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemBuilderImpl.java index 93586f06208..be6c0fabf28 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemBuilderImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemBuilderImpl.java @@ -55,9 +55,9 @@ public ItemBuilderImpl(Set itemFactories, Item item) { this.groups = item.getGroupNames(); this.label = item.getLabel(); this.tags = item.getTags(); - if (item instanceof GroupItem) { - this.baseItem = ((GroupItem) item).getBaseItem(); - this.groupFunction = ((GroupItem) item).getFunction(); + if (item instanceof GroupItem groupItem) { + this.baseItem = groupItem.getBaseItem(); + this.groupFunction = groupItem.getFunction(); } } @@ -129,8 +129,7 @@ public Item build() { if (item == null) { throw new IllegalStateException("No item factory could handle type " + itemType); } - if (item instanceof ActiveItem) { - ActiveItem activeItem = (ActiveItem) item; + if (item instanceof ActiveItem activeItem) { activeItem.setLabel(label); activeItem.setCategory(category); activeItem.addGroupNames(groups); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemRegistryImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemRegistryImpl.java index 7d9adb1b42e..f3e33dced26 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemRegistryImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemRegistryImpl.java @@ -147,8 +147,8 @@ private void addToGroupItems(Item item, List groupItemNames) { if (groupName != null) { try { Item groupItem = getItem(groupName); - if (groupItem instanceof GroupItem) { - ((GroupItem) groupItem).addMember(item); + if (groupItem instanceof GroupItem groupItem1) { + groupItem1.addMember(item); } } catch (ItemNotFoundException e) { // the group might not yet be registered, let's ignore this @@ -162,8 +162,8 @@ private void replaceInGroupItems(Item oldItem, Item newItem, List groupI if (groupName != null) { try { Item groupItem = getItem(groupName); - if (groupItem instanceof GroupItem) { - ((GroupItem) groupItem).replaceMember(oldItem, newItem); + if (groupItem instanceof GroupItem item) { + item.replaceMember(oldItem, newItem); } } catch (ItemNotFoundException e) { // the group might not yet be registered, let's ignore this @@ -185,9 +185,9 @@ private void initializeItem(Item item) throws IllegalArgumentException { injectServices(item); - if (item instanceof GroupItem) { + if (item instanceof GroupItem groupItem) { // fill group with its members - addMembersToGroupItem((GroupItem) item); + addMembersToGroupItem(groupItem); } // add the item to all relevant groups @@ -195,8 +195,7 @@ private void initializeItem(Item item) throws IllegalArgumentException { } private void injectServices(Item item) { - if (item instanceof GenericItem) { - GenericItem genericItem = (GenericItem) item; + if (item instanceof GenericItem genericItem) { genericItem.setEventPublisher(getEventPublisher()); genericItem.setStateDescriptionService(stateDescriptionService); genericItem.setCommandDescriptionService(commandDescriptionService); @@ -218,8 +217,8 @@ private void removeFromGroupItems(Item item, List groupItemNames) { if (groupName != null) { try { Item groupItem = getItem(groupName); - if (groupItem instanceof GroupItem) { - ((GroupItem) groupItem).removeMember(item); + if (groupItem instanceof GroupItem groupItem1) { + groupItem1.removeMember(item); } } catch (ItemNotFoundException e) { // the group might not yet be registered, let's ignore this @@ -235,16 +234,16 @@ protected void onAddElement(Item element) throws IllegalArgumentException { @Override protected void onRemoveElement(Item element) { - if (element instanceof GenericItem) { - ((GenericItem) element).dispose(); + if (element instanceof GenericItem item) { + item.dispose(); } removeFromGroupItems(element, element.getGroupNames()); } @Override protected void beforeUpdateElement(Item existingElement) { - if (existingElement instanceof GenericItem) { - ((GenericItem) existingElement).dispose(); + if (existingElement instanceof GenericItem item) { + item.dispose(); } } @@ -253,13 +252,13 @@ protected void onUpdateElement(Item oldItem, Item item) { // don't use #initialize and retain order of items in groups: List oldNames = oldItem.getGroupNames(); List newNames = item.getGroupNames(); - List commonNames = oldNames.stream().filter(name -> newNames.contains(name)).collect(toList()); + List commonNames = oldNames.stream().filter(newNames::contains).collect(toList()); removeFromGroupItems(oldItem, oldNames.stream().filter(name -> !commonNames.contains(name)).collect(toList())); replaceInGroupItems(oldItem, item, commonNames); addToGroupItems(item, newNames.stream().filter(name -> !commonNames.contains(name)).collect(toList())); - if (item instanceof GroupItem) { - addMembersToGroupItem((GroupItem) item); + if (item instanceof GroupItem groupItem) { + addMembersToGroupItem(groupItem); } injectServices(item); } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemStateConverterImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemStateConverterImpl.java index 7a32c527018..c25d0fce24b 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemStateConverterImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemStateConverterImpl.java @@ -70,10 +70,8 @@ public State convertToAcceptedState(@Nullable State state, @Nullable Item item) } } - if (item instanceof NumberItem && state instanceof QuantityType) { - NumberItem numberItem = (NumberItem) item; + if (item instanceof NumberItem numberItem && state instanceof QuantityType quantityState) { if (numberItem.getDimension() != null) { - QuantityType quantityState = (QuantityType) state; // in case the item does define a unit it takes precedence over all other conversions: Unit itemUnit = parseItemUnit(numberItem); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemUpdater.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemUpdater.java index 2ee6dea8c7d..3b5db162036 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemUpdater.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemUpdater.java @@ -89,8 +89,7 @@ protected void receiveUpdate(ItemStateEvent updateEvent) { protected void receiveCommand(ItemCommandEvent commandEvent) { try { Item item = itemRegistry.getItem(commandEvent.getItemName()); - if (item instanceof GroupItem) { - GroupItem groupItem = (GroupItem) item; + if (item instanceof GroupItem groupItem) { groupItem.send(commandEvent.getItemCommand()); } } catch (ItemNotFoundException e) { diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataStateDescriptionFragmentProvider.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataStateDescriptionFragmentProvider.java index 5bafdffaa17..a61bd846e64 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataStateDescriptionFragmentProvider.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/MetadataStateDescriptionFragmentProvider.java @@ -127,14 +127,14 @@ public MetadataStateDescriptionFragmentProvider(final @Reference MetadataRegistr private BigDecimal getBigDecimal(Object value) { BigDecimal ret = null; if (value != null) { - if (value instanceof BigDecimal) { - ret = (BigDecimal) value; - } else if (value instanceof String) { - ret = new BigDecimal((String) value); - } else if (value instanceof BigInteger) { - ret = new BigDecimal((BigInteger) value); - } else if (value instanceof Number) { - ret = new BigDecimal(((Number) value).doubleValue()); + if (value instanceof BigDecimal decimal) { + ret = decimal; + } else if (value instanceof String string) { + ret = new BigDecimal(string); + } else if (value instanceof BigInteger integer) { + ret = new BigDecimal(integer); + } else if (value instanceof Number number) { + ret = new BigDecimal(number.doubleValue()); } else { throw new ClassCastException("Not possible to coerce [" + value + "] from class " + value.getClass() + " into a BigDecimal."); @@ -146,10 +146,10 @@ private BigDecimal getBigDecimal(Object value) { private Boolean getBoolean(Object value) { Boolean ret = null; if (value != null) { - if (value instanceof Boolean) { - ret = (Boolean) value; - } else if (value instanceof String) { - ret = Boolean.parseBoolean((String) value); + if (value instanceof Boolean boolean1) { + ret = boolean1; + } else if (value instanceof String string) { + ret = Boolean.parseBoolean(string); } else { throw new ClassCastException( "Not possible to coerce [" + value + "] from class " + value.getClass() + " into a Boolean."); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/scheduler/CronSchedulerImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/scheduler/CronSchedulerImpl.java index 96742e301be..baa37bd5361 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/scheduler/CronSchedulerImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/scheduler/CronSchedulerImpl.java @@ -80,10 +80,10 @@ void addSchedule(CronJob cronJob, Map map) { final Object scheduleConfig = map.get(CronJob.CRON); String[] schedules = null; - if (scheduleConfig instanceof String[]) { - schedules = (String[]) scheduleConfig; - } else if (scheduleConfig instanceof String) { - schedules = new String[] { (String) scheduleConfig }; + if (scheduleConfig instanceof String[] strings) { + schedules = strings; + } else if (scheduleConfig instanceof String string) { + schedules = new String[] { string }; } if (schedules == null || schedules.length == 0) { logger.info("No schedules in map with key '" + CronJob.CRON + "'. Nothing scheduled"); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/scheduler/SchedulerImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/scheduler/SchedulerImpl.java index d4e91bb3b0d..e08423b80bf 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/scheduler/SchedulerImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/scheduler/SchedulerImpl.java @@ -176,8 +176,7 @@ private void schedule(ScheduledCompletableFutureRecurring recurringSchedu ZonedDateTime.from(newTime)); deferred.thenAccept(v -> { - if (temporalAdjuster instanceof SchedulerTemporalAdjuster) { - final SchedulerTemporalAdjuster schedulerTemporalAdjuster = (SchedulerTemporalAdjuster) temporalAdjuster; + if (temporalAdjuster instanceof SchedulerTemporalAdjuster schedulerTemporalAdjuster) { if (!schedulerTemporalAdjuster.isDone(newTime)) { schedule(recurringSchedule, runnable, identifier, temporalAdjuster); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/ReadyServiceImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/ReadyServiceImpl.java index d70d7ae4ce0..1b8e993c9b0 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/ReadyServiceImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/ReadyServiceImpl.java @@ -72,11 +72,8 @@ public void unmarkReady(ReadyMarker readyMarker) { } private void notifyTrackers(ReadyMarker readyMarker, Consumer action) { - trackers.entrySet().stream().filter(entry -> { - return entry.getValue().apply(readyMarker); - }).map(entry -> { - return entry.getKey(); - }).forEach(action); + trackers.entrySet().stream().filter(entry -> entry.getValue().apply(readyMarker)).map(Map.Entry::getKey) + .forEach(action); } @Override @@ -95,7 +92,7 @@ public void registerTracker(ReadyTracker readyTracker, ReadyMarkerFilter filter) try { if (!trackers.containsKey(readyTracker)) { trackers.put(readyTracker, filter); - notifyTracker(readyTracker, marker -> readyTracker.onReadyMarkerAdded(marker)); + notifyTracker(readyTracker, readyTracker::onReadyMarkerAdded); } } catch (RuntimeException e) { logger.error("Registering tracker '{}' failed!", readyTracker, e); @@ -109,7 +106,7 @@ public void unregisterTracker(ReadyTracker readyTracker) { rwlTrackers.writeLock().lock(); try { if (trackers.containsKey(readyTracker)) { - notifyTracker(readyTracker, marker -> readyTracker.onReadyMarkerRemoved(marker)); + notifyTracker(readyTracker, readyTracker::onReadyMarkerRemoved); } trackers.remove(readyTracker); } finally { @@ -119,6 +116,6 @@ public void unregisterTracker(ReadyTracker readyTracker) { private void notifyTracker(ReadyTracker readyTracker, Consumer action) { ReadyMarkerFilter f = trackers.get(readyTracker); - markers.stream().filter(marker -> f.apply(marker)).forEach(action); + markers.stream().filter(f::apply).forEach(action); } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GroupItem.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GroupItem.java index 546d435fc58..360deb2bd7a 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GroupItem.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GroupItem.java @@ -139,8 +139,8 @@ private void collectMembers(Collection allMembers, Collection member continue; } allMembers.add(member); - if (member instanceof GroupItem) { - collectMembers(allMembers, ((GroupItem) member).members); + if (member instanceof GroupItem item) { + collectMembers(allMembers, item.members); } } } @@ -172,22 +172,20 @@ public void addMember(Item item) { // in case membership is constructed programmatically this sanitizes // the group names on the item: - if (added && item instanceof GenericItem) { - ((GenericItem) item).addGroupName(getName()); + if (added && item instanceof GenericItem genericItem) { + genericItem.addGroupName(getName()); } registerStateListener(item); } private void registerStateListener(Item item) { - if (item instanceof GenericItem) { - GenericItem genericItem = (GenericItem) item; + if (item instanceof GenericItem genericItem) { genericItem.addStateChangeListener(this); } } private void unregisterStateListener(Item old) { - if (old instanceof GenericItem) { - GenericItem genericItem = (GenericItem) old; + if (old instanceof GenericItem genericItem) { genericItem.removeStateChangeListener(this); } } @@ -309,9 +307,9 @@ protected void internalSend(Command command) { newState = function.getStateAs(getStateMembers(getMembers()), typeClass); } - if (newState == null && baseItem != null && baseItem instanceof GenericItem) { + if (newState == null && baseItem != null && baseItem instanceof GenericItem item) { // we use the transformation method from the base item - ((GenericItem) baseItem).setState(state); + item.setState(state); newState = baseItem.getStateAs(typeClass); } if (newState == null) { @@ -382,8 +380,8 @@ public void stateUpdated(Item item, State state) { @Override public void setState(State state) { State oldState = this.state; - if (baseItem != null && baseItem instanceof GenericItem) { - ((GenericItem) baseItem).setState(state); + if (baseItem != null && baseItem instanceof GenericItem item) { + item.setState(state); this.state = baseItem.getState(); } else { this.state = state; @@ -394,24 +392,24 @@ public void setState(State state) { @Override public void setStateDescriptionService(@Nullable StateDescriptionService stateDescriptionService) { super.setStateDescriptionService(stateDescriptionService); - if (baseItem instanceof GenericItem) { - ((GenericItem) baseItem).setStateDescriptionService(stateDescriptionService); + if (baseItem instanceof GenericItem item) { + item.setStateDescriptionService(stateDescriptionService); } } @Override public void setCommandDescriptionService(@Nullable CommandDescriptionService commandDescriptionService) { super.setCommandDescriptionService(commandDescriptionService); - if (baseItem instanceof GenericItem) { - ((GenericItem) baseItem).setCommandDescriptionService(commandDescriptionService); + if (baseItem instanceof GenericItem item) { + item.setCommandDescriptionService(commandDescriptionService); } } @Override public void setUnitProvider(@Nullable UnitProvider unitProvider) { super.setUnitProvider(unitProvider); - if (baseItem instanceof GenericItem) { - ((GenericItem) baseItem).setUnitProvider(unitProvider); + if (baseItem instanceof GenericItem item) { + item.setUnitProvider(unitProvider); } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/ManagedItemProvider.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/ManagedItemProvider.java index 59268718282..2a54f1e9b14 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/ManagedItemProvider.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/ManagedItemProvider.java @@ -86,8 +86,8 @@ public ManagedItemProvider(final @Reference StorageService storageService, @Override public @Nullable Item remove(String key) { Item item = get(key); - if (item instanceof GroupItem) { - removeGroupNameFromMembers((GroupItem) item); + if (item instanceof GroupItem groupItem) { + removeGroupNameFromMembers(groupItem); } return super.remove(key); @@ -102,8 +102,8 @@ public ManagedItemProvider(final @Reference StorageService storageService, */ public @Nullable Item remove(String itemName, boolean recursive) { Item item = get(itemName); - if (recursive && item instanceof GroupItem) { - for (String member : getMemberNamesRecursively((GroupItem) item, getAll())) { + if (recursive && item instanceof GroupItem groupItem) { + for (String member : getMemberNamesRecursively(groupItem, getAll())) { remove(member); } } @@ -128,8 +128,8 @@ private List getMemberNamesRecursively(GroupItem groupItem, Collection getMembers(GroupItem groupItem, Collection allItems) { private @Nullable Item createItem(String itemType, String itemName) { try { - Item item = itemBuilderFactory.newItemBuilder(itemType, itemName).build(); - return item; + return itemBuilderFactory.newItemBuilder(itemType, itemName).build(); } catch (IllegalStateException e) { logger.debug("Couldn't create item '{}' of type '{}'", itemName, itemType); return null; @@ -159,8 +158,8 @@ private Set getMembers(GroupItem groupItem, Collection allItems) { private void removeGroupNameFromMembers(GroupItem groupItem) { Set members = getMembers(groupItem, getAll()); for (Item member : members) { - if (member instanceof GenericItem) { - ((GenericItem) member).removeGroupName(groupItem.getUID()); + if (member instanceof GenericItem item) { + item.removeGroupName(groupItem.getUID()); update(member); } } @@ -187,9 +186,9 @@ protected void addItemFactory(ItemFactory itemFactory) { String itemName = entry.getKey(); PersistedItem persistedItem = entry.getValue(); Item item = itemFactory.createItem(persistedItem.itemType, itemName); - if (item != null && item instanceof GenericItem) { + if (item != null && item instanceof GenericItem genericItem) { iterator.remove(); - configureItem(persistedItem, (GenericItem) item); + configureItem(persistedItem, genericItem); notifyListenersAboutAddedElement(item); } else { logger.debug("The added item factory '{}' still could not instantiate item '{}'.", itemFactory, @@ -237,8 +236,8 @@ protected String keyToString(String key) { item = createItem(persistedItem.itemType, itemName); } - if (item != null && item instanceof GenericItem) { - configureItem(persistedItem, (GenericItem) item); + if (item != null && item instanceof GenericItem genericItem) { + configureItem(persistedItem, genericItem); } if (item == null) { @@ -283,8 +282,7 @@ protected PersistedItem toPersistableElement(Item item) { PersistedItem persistedItem = new PersistedItem( item instanceof GroupItem ? GroupItem.TYPE : toItemFactoryName(item)); - if (item instanceof GroupItem) { - GroupItem groupItem = (GroupItem) item; + if (item instanceof GroupItem groupItem) { String baseItemType = null; Item baseItem = groupItem.getBaseItem(); if (baseItem != null) { diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/Metadata.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/Metadata.java index b31cddeb81a..068c1177905 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/Metadata.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/Metadata.java @@ -95,10 +95,7 @@ public boolean equals(@Nullable Object obj) { return false; } Metadata other = (Metadata) obj; - if (!key.equals(other.key)) { - return false; - } - return true; + return key.equals(other.key); } @Override diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/dto/ItemDTOMapper.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/dto/ItemDTOMapper.java index ed65f881c28..fff5703f16c 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/dto/ItemDTOMapper.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/dto/ItemDTOMapper.java @@ -60,8 +60,7 @@ public class ItemDTOMapper { if (itemDTO.type != null) { ItemBuilder builder = itemBuilderFactory.newItemBuilder(itemDTO.type, itemDTO.name); - if (itemDTO instanceof GroupItemDTO && GroupItem.TYPE.equals(itemDTO.type)) { - GroupItemDTO groupItemDTO = (GroupItemDTO) itemDTO; + if (itemDTO instanceof GroupItemDTO groupItemDTO && GroupItem.TYPE.equals(itemDTO.type)) { Item baseItem = null; if (groupItemDTO.groupType != null && !groupItemDTO.groupType.isEmpty()) { baseItem = itemBuilderFactory.newItemBuilder(groupItemDTO.groupType, itemDTO.name).build(); @@ -107,8 +106,7 @@ public static ItemDTO map(Item item) { } private static void fillProperties(ItemDTO itemDTO, Item item) { - if (item instanceof GroupItem) { - GroupItem groupItem = (GroupItem) item; + if (item instanceof GroupItem groupItem) { GroupItemDTO groupItemDTO = (GroupItemDTO) itemDTO; Item baseItem = groupItem.getBaseItem(); if (baseItem != null) { diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/AbstractItemEventSubscriber.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/AbstractItemEventSubscriber.java index d0b1a57f84d..e9f323fc991 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/AbstractItemEventSubscriber.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/AbstractItemEventSubscriber.java @@ -40,10 +40,10 @@ public Set getSubscribedEventTypes() { @Override public void receive(Event event) { - if (event instanceof ItemStateEvent) { - receiveUpdate((ItemStateEvent) event); - } else if (event instanceof ItemCommandEvent) { - receiveCommand((ItemCommandEvent) event); + if (event instanceof ItemStateEvent stateEvent) { + receiveUpdate(stateEvent); + } else if (event instanceof ItemCommandEvent commandEvent) { + receiveCommand(commandEvent); } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/ColorItem.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/ColorItem.java index 3e53d4b030c..45afe755309 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/ColorItem.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/ColorItem.java @@ -63,19 +63,19 @@ public void setState(State state) { if (isAcceptedState(ACCEPTED_DATA_TYPES, state)) { State currentState = this.state; - if (currentState instanceof HSBType) { - DecimalType hue = ((HSBType) currentState).getHue(); - PercentType saturation = ((HSBType) currentState).getSaturation(); + if (currentState instanceof HSBType hsbType) { + DecimalType hue = hsbType.getHue(); + PercentType saturation = hsbType.getSaturation(); // we map ON/OFF values to dark/bright, so that the hue and saturation values are not changed if (state == OnOffType.OFF) { applyState(new HSBType(hue, saturation, PercentType.ZERO)); } else if (state == OnOffType.ON) { applyState(new HSBType(hue, saturation, PercentType.HUNDRED)); - } else if (state instanceof PercentType && !(state instanceof HSBType)) { - applyState(new HSBType(hue, saturation, (PercentType) state)); - } else if (state instanceof DecimalType && !(state instanceof HSBType)) { + } else if (state instanceof PercentType percentType && !(state instanceof HSBType)) { + applyState(new HSBType(hue, saturation, percentType)); + } else if (state instanceof DecimalType decimalType && !(state instanceof HSBType)) { applyState(new HSBType(hue, saturation, - new PercentType(((DecimalType) state).toBigDecimal().multiply(BigDecimal.valueOf(100))))); + new PercentType(decimalType.toBigDecimal().multiply(BigDecimal.valueOf(100))))); } else { applyState(state); } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/LocationItem.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/LocationItem.java index 341e1dd42aa..3d8f691e7fe 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/LocationItem.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/LocationItem.java @@ -65,9 +65,8 @@ public List> getAcceptedCommandTypes() { * @return distance between the two points in meters */ public DecimalType distanceFrom(@Nullable LocationItem awayItem) { - if (awayItem != null && awayItem.state instanceof PointType && this.state instanceof PointType) { - PointType thisPoint = (PointType) this.state; - PointType awayPoint = (PointType) awayItem.state; + if (awayItem != null && awayItem.state instanceof PointType awayPoint + && this.state instanceof PointType thisPoint) { return thisPoint.distanceFrom(awayPoint); } return new DecimalType(-1); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/NumberItem.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/NumberItem.java index 619d9a6f4eb..b1c78381222 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/NumberItem.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/NumberItem.java @@ -115,28 +115,28 @@ public void send(QuantityType command) { @Override public void setState(State state) { // QuantityType update to a NumberItem without, strip unit - if (state instanceof QuantityType && dimension == null) { - DecimalType plainState = new DecimalType(((QuantityType) state).toBigDecimal()); + if (state instanceof QuantityType quantityType && dimension == null) { + DecimalType plainState = new DecimalType(quantityType.toBigDecimal()); super.setState(plainState); return; } // DecimalType update for a NumberItem with dimension, convert to QuantityType: - if (state instanceof DecimalType && dimension != null) { + if (state instanceof DecimalType decimalType && dimension != null) { Unit unit = getUnit(dimension, false); if (unit != null) { - super.setState(new QuantityType<>(((DecimalType) state).doubleValue(), unit)); + super.setState(new QuantityType<>(decimalType.doubleValue(), unit)); return; } } // QuantityType update, check unit and convert if necessary: - if (state instanceof QuantityType) { + if (state instanceof QuantityType quantityType) { Unit itemUnit = getUnit(dimension, true); - Unit stateUnit = ((QuantityType) state).getUnit(); + Unit stateUnit = quantityType.getUnit(); if (itemUnit != null && (!stateUnit.getSystemUnit().equals(itemUnit.getSystemUnit()) || UnitUtils.isDifferentMeasurementSystem(itemUnit, stateUnit))) { - QuantityType convertedState = ((QuantityType) state).toInvertibleUnit(itemUnit); + QuantityType convertedState = quantityType.toInvertibleUnit(itemUnit); if (convertedState != null) { super.setState(convertedState); return; diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/DecimalType.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/DecimalType.java index d427968dd94..137a0f15006 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/DecimalType.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/DecimalType.java @@ -51,10 +51,10 @@ public DecimalType() { * @param value a number */ public DecimalType(Number value) { - if (value instanceof QuantityType) { - this.value = ((QuantityType) value).toBigDecimal(); - } else if (value instanceof HSBType) { - this.value = ((HSBType) value).toBigDecimal(); + if (value instanceof QuantityType type) { + this.value = type.toBigDecimal(); + } else if (value instanceof HSBType type) { + this.value = type.toBigDecimal(); } else { this.value = new BigDecimal(value.toString()); } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/HSBType.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/HSBType.java index c2360f861c2..a3fefda2379 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/HSBType.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/HSBType.java @@ -85,7 +85,7 @@ public HSBType(DecimalType h, PercentType s, PercentType b) { * @param value a stringified HSBType value in the format "hue,saturation,brightness" */ public HSBType(String value) { - List constituents = Arrays.stream(value.split(",")).map(in -> in.trim()).collect(Collectors.toList()); + List constituents = Arrays.stream(value.split(",")).map(String::trim).collect(Collectors.toList()); if (constituents.size() == 3) { this.hue = new BigDecimal(constituents.get(0)); this.saturation = new BigDecimal(constituents.get(1)); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/PointType.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/PointType.java index 23af517628d..40db3682d20 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/PointType.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/PointType.java @@ -85,7 +85,7 @@ public PointType(StringType latitude, StringType longitude, StringType altitude) public PointType(String value) { if (!value.isEmpty()) { - List elements = Arrays.stream(value.split(",")).map(in -> in.trim()).collect(Collectors.toList()); + List elements = Arrays.stream(value.split(",")).map(String::trim).collect(Collectors.toList()); if (elements.size() >= 2) { canonicalize(new DecimalType(elements.get(0)), new DecimalType(elements.get(1))); if (elements.size() == 3) { @@ -264,10 +264,7 @@ public boolean equals(@Nullable Object obj) { return false; } PointType other = (PointType) obj; - if (!getLatitude().equals(other.getLatitude()) || !getLongitude().equals(other.getLongitude()) - || !getAltitude().equals(other.getAltitude())) { - return false; - } - return true; + return !(!getLatitude().equals(other.getLatitude()) || !getLongitude().equals(other.getLongitude()) + || !getAltitude().equals(other.getAltitude())); } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/QuantityTypeArithmeticGroupFunction.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/QuantityTypeArithmeticGroupFunction.java index 8443ec5abff..4a143e2c71f 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/QuantityTypeArithmeticGroupFunction.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/QuantityTypeArithmeticGroupFunction.java @@ -59,10 +59,10 @@ public State[] getParameters() { } protected boolean isSameDimension(@Nullable Item item) { - if (item instanceof GroupItem) { - return isSameDimension(((GroupItem) item).getBaseItem()); + if (item instanceof GroupItem groupItem) { + return isSameDimension(groupItem.getBaseItem()); } - return item instanceof NumberItem && dimension.equals(((NumberItem) item).getDimension()); + return item instanceof NumberItem ni && dimension.equals(ni.getDimension()); } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/RawType.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/RawType.java index 3fc3ff54cbe..7e646a272e6 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/RawType.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/RawType.java @@ -101,9 +101,6 @@ public boolean equals(@Nullable Object obj) { if (!mimeType.equals(other.mimeType)) { return false; } - if (!Arrays.equals(bytes, other.bytes)) { - return false; - } - return true; + return Arrays.equals(bytes, other.bytes); } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/net/NetUtil.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/net/NetUtil.java index d30775e55de..b213d6718f7 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/net/NetUtil.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/net/NetUtil.java @@ -287,7 +287,7 @@ public static List getAllBroadcastAddresses() { if (primaryIp != null) { try { Short prefix = getAllInterfaceAddresses().stream() - .filter(a -> a.getAddress().getHostAddress().equals(primaryIp)).map(a -> a.getPrefix()) + .filter(a -> a.getAddress().getHostAddress().equals(primaryIp)).map(CidrAddress::getPrefix) .findFirst().get().shortValue(); broadcastAddress = getIpv4NetBroadcastAddress(primaryIp, prefix); } catch (IllegalArgumentException ex) { @@ -467,7 +467,7 @@ public static String getIpv4NetBroadcastAddress(String ipAddressString, short pr String ipv4AddressOnInterface = addr.getHostAddress(); String subnetStringOnInterface = getIpv4NetAddress(ipv4AddressOnInterface, - ifAddr.getNetworkPrefixLength()) + "/" + String.valueOf(ifAddr.getNetworkPrefixLength()); + ifAddr.getNetworkPrefixLength()) + "/" + ifAddr.getNetworkPrefixLength(); String configuredSubnetString = getIpv4NetAddress(ipAddress, Short.parseShort(subnetMask)) + "/" + subnetMask; @@ -493,7 +493,7 @@ public static String getIpv4NetBroadcastAddress(String ipAddressString, short pr */ public static boolean isValidIPConfig(String ipAddress) { if (ipAddress.contains("/")) { - String parts[] = ipAddress.split("/"); + String[] parts = ipAddress.split("/"); boolean ipMatches = IPV4_PATTERN.matcher(parts[0]).matches(); int netMask = Integer.parseInt(parts[1]); @@ -518,7 +518,7 @@ private void scheduleToPollNetworkInterface(int intervalInSeconds) { } networkInterfacePollFuture = scheduledExecutorService.scheduleWithFixedDelay( - () -> this.pollAndNotifyNetworkInterfaceAddress(), 1, intervalInSeconds, TimeUnit.SECONDS); + this::pollAndNotifyNetworkInterfaceAddress, 1, intervalInSeconds, TimeUnit.SECONDS); } private void pollAndNotifyNetworkInterfaceAddress() { @@ -587,11 +587,11 @@ private boolean getConfigParameter(Map parameters, String parame if (value == null) { return defaultValue; } - if (value instanceof Boolean) { - return (Boolean) value; + if (value instanceof Boolean boolean1) { + return boolean1; } - if (value instanceof String) { - return Boolean.valueOf((String) value); + if (value instanceof String string) { + return Boolean.valueOf(string); } else { return defaultValue; } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/service/StartLevelService.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/service/StartLevelService.java index a824a67bfb7..6eebbf9aa46 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/service/StartLevelService.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/service/StartLevelService.java @@ -171,7 +171,7 @@ private void handleOSGiStartlevel() { protected void modified(Map configuration) { // clean up slmarker.clear(); - trackers.values().forEach(t -> readyService.unregisterTracker(t)); + trackers.values().forEach(readyService::unregisterTracker); trackers.clear(); // set up trackers and markers @@ -180,7 +180,7 @@ protected void modified(Map configuration) { .forEach(sl -> slmarker.put(sl, new ReadyMarker(STARTLEVEL_MARKER_TYPE, Integer.toString(sl)))); slmarker.put(STARTLEVEL_COMPLETE, new ReadyMarker(STARTLEVEL_MARKER_TYPE, Integer.toString(STARTLEVEL_COMPLETE))); - startlevels.values().stream().forEach(ms -> ms.forEach(e -> registerTracker(e))); + startlevels.values().stream().forEach(ms -> ms.forEach(this::registerTracker)); } private void registerTracker(ReadyMarker e) { @@ -204,7 +204,7 @@ public void onReadyMarkerAdded(ReadyMarker readyMarker) { private Map> parseConfig(Map configuration) { return configuration.entrySet().stream() // - .filter(e -> hasIntegerKey(e)) // + .filter(this::hasIntegerKey) // .map(e -> new AbstractMap.SimpleEntry<>(Integer.valueOf(e.getKey()), markerSet(e.getValue()))) // .sorted(Map.Entry.> comparingByKey().reversed()) // .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); @@ -212,8 +212,8 @@ private Map> parseConfig(Map configura private Set markerSet(Object value) { Set markerSet = new HashSet<>(); - if (value instanceof String) { - String[] segments = ((String) value).split(","); + if (value instanceof String string) { + String[] segments = string.split(","); for (String segment : segments) { if (segment.contains(":")) { String[] markerParts = segment.strip().split(":"); @@ -238,7 +238,7 @@ private boolean hasIntegerKey(Entry entry) { @Deactivate protected void deactivate() { slmarker.clear(); - trackers.values().forEach(t -> readyService.unregisterTracker(t)); + trackers.values().forEach(readyService::unregisterTracker); ScheduledFuture job = this.job; if (job != null) { job.cancel(true); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/types/CommandDescriptionBuilder.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/types/CommandDescriptionBuilder.java index ed9be11400a..965e5ed2887 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/types/CommandDescriptionBuilder.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/types/CommandDescriptionBuilder.java @@ -48,7 +48,7 @@ public static CommandDescriptionBuilder create() { */ public CommandDescription build() { CommandDescriptionImpl commandDescription = new CommandDescriptionImpl(); - commandOptions.forEach(co -> commandDescription.addCommandOption(co)); + commandOptions.forEach(commandDescription::addCommandOption); return commandDescription; } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/types/util/UnitUtils.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/types/util/UnitUtils.java index b26c0c77731..b72ba56ddda 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/types/util/UnitUtils.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/types/util/UnitUtils.java @@ -116,8 +116,8 @@ public class UnitUtils { for (Field field : system.getDeclaredFields()) { if (field.getType().isAssignableFrom(Unit.class) && Modifier.isStatic(field.getModifiers())) { Type genericType = field.getGenericType(); - if (genericType instanceof ParameterizedType) { - Type typeParam = ((ParameterizedType) genericType).getActualTypeArguments()[0]; + if (genericType instanceof ParameterizedType type) { + Type typeParam = type.getActualTypeArguments()[0]; if (typeParam instanceof WildcardType) { continue; } @@ -188,14 +188,12 @@ public static boolean isDifferentMeasurementSystem(Unit> t || (siUnits.contains(thatUnit) && usUnits.contains(thisUnit)); if (!differentSystems) { - if (thisUnit instanceof TransformedUnit - && isMetricConversion(((TransformedUnit) thisUnit).getConverter())) { - return isDifferentMeasurementSystem(((TransformedUnit) thisUnit).getParentUnit(), thatUnit); + if (thisUnit instanceof TransformedUnit unit && isMetricConversion(unit.getConverter())) { + return isDifferentMeasurementSystem(unit.getParentUnit(), thatUnit); } - if (thatUnit instanceof TransformedUnit - && isMetricConversion(((TransformedUnit) thatUnit).getConverter())) { - return isDifferentMeasurementSystem(thisUnit, ((TransformedUnit) thatUnit).getParentUnit()); + if (thatUnit instanceof TransformedUnit unit && isMetricConversion(unit.getConverter())) { + return isDifferentMeasurementSystem(thisUnit, unit.getParentUnit()); } } diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/lru/LRUMediaCacheEntryTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/lru/LRUMediaCacheEntryTest.java index 32cad08eb1b..cc41cb64c4f 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/lru/LRUMediaCacheEntryTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/lru/LRUMediaCacheEntryTest.java @@ -74,9 +74,8 @@ public Storage getStorage(String name) { } private LRUMediaCache createCache(long size) throws IOException { - LRUMediaCache voiceLRUCache = new LRUMediaCache(storageService, size, - "lrucachetest.pid", this.getClass().getClassLoader()); - return voiceLRUCache; + return new LRUMediaCache(storageService, size, "lrucachetest.pid", + this.getClass().getClassLoader()); } public static class FakeStream extends InputStream { @@ -193,7 +192,7 @@ public void loadTwoThreadsAtTheSameTimeFromTheSameSupplierTest() throws IOExcept // read bytes from the two stream concurrently List parallelAudioStreamList = Arrays.asList(actualAudioStream1, actualAudioStream2); - List bytesResultList = parallelAudioStreamList.parallelStream().map(stream -> readSafe(stream)) + List bytesResultList = parallelAudioStreamList.parallelStream().map(this::readSafe) .collect(Collectors.toList()); assertArrayEquals(randomData, bytesResultList.get(0)); diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/lru/LRUMediaCacheTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/lru/LRUMediaCacheTest.java index 20690ec38a8..90a9effc65f 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/lru/LRUMediaCacheTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/lru/LRUMediaCacheTest.java @@ -75,9 +75,8 @@ public Storage getStorage(String name) { } private LRUMediaCache createCache(long size) throws IOException { - LRUMediaCache voiceLRUCache = new LRUMediaCache(storageService, size, - "lrucachetest.pid", this.getClass().getClassLoader()); - return voiceLRUCache; + return new LRUMediaCache(storageService, size, "lrucachetest.pid", + this.getClass().getClassLoader()); } /** diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/auth/UserRegistryImplTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/auth/UserRegistryImplTest.java index aab0c0981ad..320a4fae6b5 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/auth/UserRegistryImplTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/auth/UserRegistryImplTest.java @@ -118,11 +118,11 @@ public void testApiTokens() throws Exception { registry.authenticate(new UserApiTokenCredentials(token2)); registry.authenticate(new UserApiTokenCredentials(token3)); registry.removeUserApiToken(user, - user.getApiTokens().stream().filter(t -> t.getName().equals("token1")).findAny().get()); + user.getApiTokens().stream().filter(t -> "token1".equals(t.getName())).findAny().get()); registry.removeUserApiToken(user, - user.getApiTokens().stream().filter(t -> t.getName().equals("token2")).findAny().get()); + user.getApiTokens().stream().filter(t -> "token2".equals(t.getName())).findAny().get()); registry.removeUserApiToken(user, - user.getApiTokens().stream().filter(t -> t.getName().equals("token3")).findAny().get()); + user.getApiTokens().stream().filter(t -> "token3".equals(t.getName())).findAny().get()); assertEquals(user.getApiTokens().size(), 0); } } diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/scheduler/SchedulerImplTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/scheduler/SchedulerImplTest.java index b0b430f6c11..c2b5808d69b 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/scheduler/SchedulerImplTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/scheduler/SchedulerImplTest.java @@ -77,7 +77,7 @@ public void testAfterCancelled() throws InterruptedException, InvocationTargetEx assertTrue(after.isCancelled(), "Scheduled job cancelled before timeout"); Thread.sleep(200); assertFalse(check.get(), "Callable method should not been called"); - assertThrows(CancellationException.class, () -> after.get()); + assertThrows(CancellationException.class, after::get); } @Test @@ -144,9 +144,7 @@ public void testBeforeCancelled() throws InterruptedException, InvocationTargetE ScheduledCompletableFuture before = scheduler.before(d, Duration.ofMillis(100)); before.cancel(true); assertTrue(before.getPromise().isCompletedExceptionally(), "Scheduled job cancelled before timeout"); - assertThrows(CancellationException.class, () -> { - before.get(); - }); + assertThrows(CancellationException.class, before::get); } @Test @@ -322,7 +320,7 @@ protected long currentTimeMillis() { } }; final AtomicReference> reference = new AtomicReference<>(); - final ScheduledCompletableFuture future = scheduler.schedule(() -> counter.incrementAndGet(), + final ScheduledCompletableFuture future = scheduler.schedule(counter::incrementAndGet, temporalAdjuster); reference.set(future); future.get(); diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/items/DimmerItemTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/items/DimmerItemTest.java index 18d67e8d7ca..a3052cfc701 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/items/DimmerItemTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/items/DimmerItemTest.java @@ -41,8 +41,7 @@ private static DimmerItem createDimmerItem(final State state) { private static BigDecimal getState(final Item item, Class typeClass) { final State state = item.getStateAs(typeClass); final String str = state.toString(); - final BigDecimal result = new BigDecimal(str); - return result; + return new BigDecimal(str); } @Test diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/unit/UnitsTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/unit/UnitsTest.java index 2cbde582bcd..ef65bf45d2d 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/unit/UnitsTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/unit/UnitsTest.java @@ -397,8 +397,7 @@ public QuantityEquals(Quantity quantity) { @Override public boolean matches(@Nullable Object actualValue) { - if (actualValue instanceof Quantity) { - Quantity other = (Quantity) actualValue; + if (actualValue instanceof Quantity other) { if (!other.getUnit().isCompatible(quantity.getUnit())) { return false; From 8aa9e11342a27ef570618d258bf422fa3247b62b Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 1 Apr 2023 11:32:53 +0200 Subject: [PATCH 014/126] Fix resource leak in SysfsUsbSerialScanner (#3513) Signed-off-by: Jan N. Klug --- .../internal/SysfsUsbSerialScanner.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.usbserial.linuxsysfs/src/main/java/org/openhab/core/config/discovery/usbserial/linuxsysfs/internal/SysfsUsbSerialScanner.java b/bundles/org.openhab.core.config.discovery.usbserial.linuxsysfs/src/main/java/org/openhab/core/config/discovery/usbserial/linuxsysfs/internal/SysfsUsbSerialScanner.java index 2096ddd0e02..3efdb8389ed 100644 --- a/bundles/org.openhab.core.config.discovery.usbserial.linuxsysfs/src/main/java/org/openhab/core/config/discovery/usbserial/linuxsysfs/internal/SysfsUsbSerialScanner.java +++ b/bundles/org.openhab.core.config.discovery.usbserial.linuxsysfs/src/main/java/org/openhab/core/config/discovery/usbserial/linuxsysfs/internal/SysfsUsbSerialScanner.java @@ -154,16 +154,18 @@ private Set getSerialPortInfos() throws IOException { Path devSerialDir = Path.of(DEV_SERIAL_BY_ID_DIRECTORY); if (exists(devSerialDir) && isDirectory(devSerialDir) && isReadable(devSerialDir)) { // browse serial/by-id directory : - for (Path devLinkPath : newDirectoryStream(devSerialDir)) { - if (Files.isSymbolicLink(devLinkPath)) { - Path devicePath = getRealDevicePath(devLinkPath); - if (devicePath != null) { - String serialPortName = devicePath.getFileName().toString(); - // get the corresponding real sysinfo special dir : - Path sysfsDevicePath = getRealDevicePath( - Paths.get(sysfsTtyDevicesDirectory).resolve(serialPortName)); - if (sysfsDevicePath != null && isReadable(devicePath) && isWritable(devicePath)) { - result.add(new SerialPortInfo(devLinkPath, sysfsDevicePath)); + try (DirectoryStream directoryStream = newDirectoryStream(devSerialDir)) { + for (Path devLinkPath : directoryStream) { + if (Files.isSymbolicLink(devLinkPath)) { + Path devicePath = getRealDevicePath(devLinkPath); + if (devicePath != null) { + String serialPortName = devicePath.getFileName().toString(); + // get the corresponding real sysinfo special dir : + Path sysfsDevicePath = getRealDevicePath( + Paths.get(sysfsTtyDevicesDirectory).resolve(serialPortName)); + if (sysfsDevicePath != null && isReadable(devicePath) && isWritable(devicePath)) { + result.add(new SerialPortInfo(devLinkPath, sysfsDevicePath)); + } } } } From 41ff466d65e91cc4e88012debb182d6be09b106f Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Sat, 1 Apr 2023 13:36:43 +0200 Subject: [PATCH 015/126] Upgrade maven-surefire-plugin to 3.0.0 (#3514) This fixes deprecation warnings when runnings tests with Maven 3.9.x: `[WARNING] Parameter 'localRepository' is deprecated core expression; Avoid use of ArtifactRepository type. If you need access to local repository, switch to '${repositorySystemSession}' expression and get LRM from it instead.` See: https://issues.apache.org/jira/browse/SUREFIRE-2154 Related to #3512 Signed-off-by: Wouter Born --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6208674faf1..89b310b402f 100644 --- a/pom.xml +++ b/pom.xml @@ -389,7 +389,7 @@ Import-Package: \\ org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M7 + 3.0.0 --add-opens java.base/java.net=ALL-UNNAMED From d557a64532176a5a0e25bdab075d087dbe090ac7 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 1 Apr 2023 14:15:01 +0200 Subject: [PATCH 016/126] Fix AbstractDependencyTracker (#3510) Signed-off-by: Jan N. Klug --- .../AbstractScriptDependencyTracker.java | 4 +- .../AbstractScriptDependencyTrackerTest.java | 49 ++++++++++++------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/loader/AbstractScriptDependencyTracker.java b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/loader/AbstractScriptDependencyTracker.java index 931ebaebe7d..52293dd137e 100644 --- a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/loader/AbstractScriptDependencyTracker.java +++ b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/loader/AbstractScriptDependencyTracker.java @@ -79,9 +79,9 @@ public Path getLibraryPath() { @Override public void processWatchEvent(WatchService.Kind kind, Path path) { - File file = path.toFile(); + File file = libraryPath.resolve(path).toFile(); if (!file.isHidden() && (kind == DELETE || (file.canRead() && (kind == CREATE || kind == MODIFY)))) { - dependencyChanged(file.getPath()); + dependencyChanged(file.toString()); } } diff --git a/bundles/org.openhab.core.automation.module.script.rulesupport/src/test/java/org/openhab/core/automation/module/script/rulesupport/loader/AbstractScriptDependencyTrackerTest.java b/bundles/org.openhab.core.automation.module.script.rulesupport/src/test/java/org/openhab/core/automation/module/script/rulesupport/loader/AbstractScriptDependencyTrackerTest.java index 4c4632fb252..3b27f589964 100644 --- a/bundles/org.openhab.core.automation.module.script.rulesupport/src/test/java/org/openhab/core/automation/module/script/rulesupport/loader/AbstractScriptDependencyTrackerTest.java +++ b/bundles/org.openhab.core.automation.module.script.rulesupport/src/test/java/org/openhab/core/automation/module/script/rulesupport/loader/AbstractScriptDependencyTrackerTest.java @@ -15,6 +15,8 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -22,6 +24,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; @@ -40,15 +43,27 @@ public class AbstractScriptDependencyTrackerTest { private static final String WATCH_DIR = "test"; + private static final Path DEPENDENCY = Path.of("depFile"); + private static final Path DEPENDENCY2 = Path.of("depFile2"); private @NonNullByDefault({}) AbstractScriptDependencyTracker scriptDependencyTracker; private @Mock @NonNullByDefault({}) WatchService watchServiceMock; + private @NonNullByDefault({}) @TempDir Path rootWatchPath; + private @NonNullByDefault({}) Path depPath; + private @NonNullByDefault({}) Path depPath2; @BeforeEach - public void setup() { - when(watchServiceMock.getWatchPath()).thenReturn(Path.of("")); + public void setup() throws IOException { + when(watchServiceMock.getWatchPath()).thenReturn(rootWatchPath); scriptDependencyTracker = new AbstractScriptDependencyTracker(watchServiceMock, WATCH_DIR) { }; + + depPath = rootWatchPath.resolve(WATCH_DIR).resolve(DEPENDENCY); + depPath2 = rootWatchPath.resolve(WATCH_DIR).resolve(DEPENDENCY2); + + Files.createFile(depPath); + + Files.createFile(depPath2); } @AfterEach @@ -58,7 +73,7 @@ public void tearDown() { @Test public void testScriptLibraryWatcherIsCreatedAndActivated() { - verify(watchServiceMock).registerListener(eq(scriptDependencyTracker), eq(Path.of(WATCH_DIR))); + verify(watchServiceMock).registerListener(eq(scriptDependencyTracker), eq(rootWatchPath.resolve(WATCH_DIR))); } @Test @@ -69,15 +84,15 @@ public void testScriptLibraryWatchersIsDeactivatedOnShutdown() { } @Test - public void testDependencyChangeIsForwardedToMultipleListeners() { + public void testDependencyChangeIsForwardedToMultipleListeners() throws IOException { ScriptDependencyTracker.Listener listener1 = mock(ScriptDependencyTracker.Listener.class); ScriptDependencyTracker.Listener listener2 = mock(ScriptDependencyTracker.Listener.class); scriptDependencyTracker.addChangeTracker(listener1); scriptDependencyTracker.addChangeTracker(listener2); - scriptDependencyTracker.startTracking("scriptId", "depPath"); - scriptDependencyTracker.dependencyChanged("depPath"); + scriptDependencyTracker.startTracking("scriptId", depPath.toString()); + scriptDependencyTracker.processWatchEvent(WatchService.Kind.CREATE, DEPENDENCY); verify(listener1).onDependencyChange(eq("scriptId")); verify(listener2).onDependencyChange(eq("scriptId")); @@ -91,10 +106,9 @@ public void testDependencyChangeIsForwardedForMultipleScriptIds() { scriptDependencyTracker.addChangeTracker(listener); - scriptDependencyTracker.startTracking("scriptId1", "depPath"); - scriptDependencyTracker.startTracking("scriptId2", "depPath"); - - scriptDependencyTracker.dependencyChanged("depPath"); + scriptDependencyTracker.startTracking("scriptId1", depPath.toString()); + scriptDependencyTracker.startTracking("scriptId2", depPath.toString()); + scriptDependencyTracker.processWatchEvent(WatchService.Kind.MODIFY, DEPENDENCY); verify(listener).onDependencyChange(eq("scriptId1")); verify(listener).onDependencyChange(eq("scriptId2")); @@ -107,11 +121,10 @@ public void testDependencyChangeIsForwardedForMultipleDependencies() { scriptDependencyTracker.addChangeTracker(listener); - scriptDependencyTracker.startTracking("scriptId", "depPath1"); - scriptDependencyTracker.startTracking("scriptId", "depPath2"); - - scriptDependencyTracker.dependencyChanged("depPath1"); - scriptDependencyTracker.dependencyChanged("depPath2"); + scriptDependencyTracker.startTracking("scriptId", depPath.toString()); + scriptDependencyTracker.startTracking("scriptId", depPath2.toString()); + scriptDependencyTracker.processWatchEvent(WatchService.Kind.MODIFY, DEPENDENCY); + scriptDependencyTracker.processWatchEvent(WatchService.Kind.DELETE, DEPENDENCY2); verify(listener, times(2)).onDependencyChange(eq("scriptId")); verifyNoMoreInteractions(listener); @@ -123,10 +136,10 @@ public void testDependencyChangeIsForwardedForCorrectDependencies() { scriptDependencyTracker.addChangeTracker(listener); - scriptDependencyTracker.startTracking("scriptId1", "depPath1"); - scriptDependencyTracker.startTracking("scriptId2", "depPath2"); + scriptDependencyTracker.startTracking("scriptId1", depPath.toString()); + scriptDependencyTracker.startTracking("scriptId2", depPath2.toString()); - scriptDependencyTracker.dependencyChanged("depPath1"); + scriptDependencyTracker.processWatchEvent(WatchService.Kind.CREATE, DEPENDENCY); verify(listener).onDependencyChange(eq("scriptId1")); verifyNoMoreInteractions(listener); From b745d70d6db9224609acb12192428966f6ef1fc1 Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Sat, 1 Apr 2023 14:39:25 +0200 Subject: [PATCH 017/126] Add issue and pull request templates and configuration (#3463) Signed-off-by: Wouter Born --- .github/ISSUE_TEMPLATE/bug_report.md | 45 ++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 11 +++++ .github/ISSUE_TEMPLATE/feature_request.md | 17 +++++++ .../sitemap_change.md | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 47 +++++++++++++++++++ 5 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md rename .github/{issue_template => ISSUE_TEMPLATE}/sitemap_change.md (96%) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..4c292a99f86 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,45 @@ +--- +name: "🐛 Bug report" +about: Something isn't working correctly in the Core. This is the wrong place for openHAB user-interface or add-on issues +labels: bug + +--- + + + + + + + + +## Expected Behavior + + + +## Current Behavior + + + + + + + +## Possible Solution + + + +## Steps to Reproduce (for Bugs) + + +1. +2. + +## Context + + + +## Your Environment + +* Version used: (e.g., openHAB and add-on versions) +* Environment name and version (e.g. Chrome 111, Java 17, Node.js 18.15, ...): +* Operating System and version (desktop or mobile, Windows 11, Raspbian Bullseye, ...): diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000..b8c32b01222 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: true +contact_links: + - name: 🤔 Support/Usage Question + url: https://community.openhab.org + about: Feel free to ask anything + - name: 📖 Documentation + url: https://www.openhab.org/docs/ + about: Official documentation + - name: 📚 Translation feedback + url: https://crowdin.com/project/openhab-core + about: Share feedback on translations diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000..16168137206 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: "✨ Feature request" +about: You think that the Core should gain another feature +labels: enhancement + +--- + + + + + + +## Your Environment + +* Version used: (e.g., openHAB and add-on versions) +* Environment name and version (e.g. Chrome 111, Java 17, Node.js 18.15, ...): +* Operating System and version (desktop or mobile, Windows 11, Raspbian Bullseye, ...): diff --git a/.github/issue_template/sitemap_change.md b/.github/ISSUE_TEMPLATE/sitemap_change.md similarity index 96% rename from .github/issue_template/sitemap_change.md rename to .github/ISSUE_TEMPLATE/sitemap_change.md index 52975ceb27c..783ca757415 100644 --- a/.github/issue_template/sitemap_change.md +++ b/.github/ISSUE_TEMPLATE/sitemap_change.md @@ -1,5 +1,5 @@ --- -name: Suggest a Sitemap change +name: 🗺 Sitemap change about: 'Suggest a change in the Sitemap syntax and corresponding UIs' labels: sitemap --- diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..c01cdb58c39 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,47 @@ + From 4851bfbcae59861ce5a1bb4a2e8d08a456a60cbc Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Sat, 1 Apr 2023 17:18:22 +0200 Subject: [PATCH 018/126] Fix recently introduced SAT issues (#3516) * Fix recently introduced SAT issues Signed-off-by: Wouter Born --- .../MarketplaceBundleInstaller.java | 1 - .../java/org/openhab/core/addon/Addon.java | 3 +- .../internal/AbstractAudioServletTest.java | 4 +-- .../rest/internal/ThingActionsResource.java | 7 ++-- .../handler/ItemCommandTriggerHandler.java | 3 -- .../auth/internal/AuthenticationHandler.java | 4 +-- .../http/internal/WebClientFactoryImpl.java | 1 - .../core/internal/folder/FolderObserver.java | 5 ++- .../update/UpdateChannelInstructionImpl.java | 1 - .../org/openhab/core/voice/VoiceManager.java | 1 - .../voice/internal/cache/TTSLRUCacheImpl.java | 9 +++-- .../internal/cache/TTSLRUCacheImplTest.java | 1 - .../core/cache/lru/LRUMediaCacheEntry.java | 1 - .../WrappedScheduledExecutorService.java | 1 - .../items/ItemStateConverterImpl.java | 3 -- .../internal/scheduler/SchedulerImpl.java | 1 - .../cache/lru/LRUMediaCacheEntryTest.java | 1 - .../service/WatchServiceImplTest.java | 34 +++++++++---------- .../openhab/core/library/unit/UnitsTest.java | 1 - .../json/internal/ThingMigrationOSGiTest.java | 3 +- .../internal/ChannelLinkNotifierOSGiTest.java | 12 +++---- 21 files changed, 35 insertions(+), 62 deletions(-) diff --git a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/MarketplaceBundleInstaller.java b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/MarketplaceBundleInstaller.java index c4ff1dfff5c..0b5a15fffc0 100644 --- a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/MarketplaceBundleInstaller.java +++ b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/MarketplaceBundleInstaller.java @@ -160,7 +160,6 @@ protected void ensureCachedBundlesAreInstalled(BundleContext bundleContext) { logger.warn("Failed reinstalling add-on from cache", e); } }); - } catch (IOException e) { logger.warn("Failed to re-install bundles: {}", e.getMessage()); } diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/Addon.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/Addon.java index 1f33c5aea00..84019c79fb9 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/Addon.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/Addon.java @@ -298,8 +298,7 @@ public List getLoggerPackages() { * Create a builder for an {@link Addon} * * @param uid the UID of the add-on (e.g. "binding-dmx", "json:transform-format" or "marketplace:123456") - * - * @return + * @return the builder */ public static Builder create(String uid) { return new Builder(uid); diff --git a/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/AbstractAudioServletTest.java b/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/AbstractAudioServletTest.java index 50b387df286..14224cb2ac6 100644 --- a/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/AbstractAudioServletTest.java +++ b/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/AbstractAudioServletTest.java @@ -60,8 +60,8 @@ public abstract class AbstractAudioServletTest extends JavaTest { private @NonNullByDefault({}) HttpClient httpClient; private @NonNullByDefault({}) CompletableFuture serverStarted; - private @Mock @NonNullByDefault({}) HttpService httpServiceMock; - private @Mock @NonNullByDefault({}) HttpContext httpContextMock; + public @Mock @NonNullByDefault({}) HttpService httpServiceMock; + public @Mock @NonNullByDefault({}) HttpContext httpContextMock; @BeforeEach public void setupServerAndClient() { diff --git a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/ThingActionsResource.java b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/ThingActionsResource.java index 5bf9e44f67e..3a46a40c1e3 100644 --- a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/ThingActionsResource.java +++ b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/ThingActionsResource.java @@ -44,7 +44,7 @@ import org.openhab.core.automation.type.Input; import org.openhab.core.automation.type.ModuleTypeRegistry; import org.openhab.core.automation.type.Output; -import org.openhab.core.automation.util.ActionBuilder; +import org.openhab.core.automation.util.ModuleBuilder; import org.openhab.core.config.core.Configuration; import org.openhab.core.io.rest.LocaleService; import org.openhab.core.io.rest.RESTConstants; @@ -64,8 +64,6 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -91,7 +89,6 @@ public class ThingActionsResource implements RESTResource { public static final String PATH_THINGS = "actions"; - private final Logger logger = LoggerFactory.getLogger(ThingActionsResource.class); private final LocaleService localeService; private final ModuleTypeRegistry moduleTypeRegistry; @@ -209,7 +206,7 @@ public Response executeThingAction(@PathParam("thingUID") @Parameter(description Configuration configuration = new Configuration(); configuration.put("config", thingUID); - Action action = ActionBuilder.createAction().withConfiguration(configuration) + Action action = ModuleBuilder.createAction().withConfiguration(configuration) .withId(UUID.randomUUID().toString()).withTypeUID(actionTypeUid).build(); ModuleHandlerFactory moduleHandlerFactory = moduleHandlerFactories.stream() diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemCommandTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemCommandTriggerHandler.java index 5283e2eda1b..258bd5b503b 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemCommandTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemCommandTriggerHandler.java @@ -12,9 +12,7 @@ */ package org.openhab.core.automation.internal.module.handler; -import java.util.Dictionary; import java.util.HashMap; -import java.util.Hashtable; import java.util.Map; import java.util.Set; @@ -74,7 +72,6 @@ public ItemCommandTriggerHandler(Trigger module, String ruleUID, BundleContext b this.bundleContext = bundleContext; this.ruleUID = ruleUID; this.types = Set.of(ItemCommandEvent.TYPE, ItemAddedEvent.TYPE, ItemRemovedEvent.TYPE); - Dictionary properties = new Hashtable<>(); eventSubscriberRegistration = this.bundleContext.registerService(EventSubscriber.class.getName(), this, null); if (itemRegistry.get(itemName) == null) { logger.warn("Item '{}' needed for rule '{}' is missing. Trigger '{}' will not work.", itemName, ruleUID, diff --git a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthenticationHandler.java b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthenticationHandler.java index 7e054f7d494..a6276171d69 100644 --- a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthenticationHandler.java +++ b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthenticationHandler.java @@ -149,11 +149,11 @@ void activate(Map properties) { void modified(Map properties) { Object authenticationEnabled = properties.get(AUTHENTICATION_ENABLED); if (authenticationEnabled != null) { - this.enabled = Boolean.valueOf(authenticationEnabled.toString()); + this.enabled = Boolean.parseBoolean(authenticationEnabled.toString()); } Object loginUri = properties.get(AUTHENTICATION_ENDPOINT); - if (loginUri != null && loginUri instanceof String) { + if (loginUri instanceof String) { this.loginUri = (String) loginUri; } } diff --git a/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/internal/WebClientFactoryImpl.java b/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/internal/WebClientFactoryImpl.java index 46fba9b0027..01fea18467d 100644 --- a/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/internal/WebClientFactoryImpl.java +++ b/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/internal/WebClientFactoryImpl.java @@ -322,7 +322,6 @@ private WebSocketClient createWebSocketClientInternal(String consumerName, } return webSocketClient; - } catch (RuntimeException e) { throw e; } catch (Exception e) { diff --git a/bundles/org.openhab.core.model.core/src/main/java/org/openhab/core/model/core/internal/folder/FolderObserver.java b/bundles/org.openhab.core.model.core/src/main/java/org/openhab/core/model/core/internal/folder/FolderObserver.java index ed4d2f77f3d..f1c181d20d9 100644 --- a/bundles/org.openhab.core.model.core/src/main/java/org/openhab/core/model/core/internal/folder/FolderObserver.java +++ b/bundles/org.openhab.core.model.core/src/main/java/org/openhab/core/model/core/internal/folder/FolderObserver.java @@ -12,8 +12,7 @@ */ package org.openhab.core.model.core.internal.folder; -import static org.openhab.core.service.WatchService.Kind.CREATE; -import static org.openhab.core.service.WatchService.Kind.MODIFY; +import static org.openhab.core.service.WatchService.Kind.*; import java.io.File; import java.io.FilenameFilter; @@ -162,7 +161,7 @@ private void addModelsToRepo() { if (!folderFileExtMap.isEmpty()) { for (String folderName : folderFileExtMap.keySet()) { final List validExtension = folderFileExtMap.get(folderName); - if (validExtension != null && validExtension.size() > 0) { + if (validExtension != null && !validExtension.isEmpty()) { File folder = watchService.getWatchPath().resolve(folderName).toFile(); File[] files = folder.listFiles(new FileExtensionsFilter(validExtension)); diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/update/UpdateChannelInstructionImpl.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/update/UpdateChannelInstructionImpl.java index 41ba71f5428..7433ad9f0f3 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/update/UpdateChannelInstructionImpl.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/update/UpdateChannelInstructionImpl.java @@ -88,7 +88,6 @@ public void perform(Thing thing, ThingBuilder thingBuilder) { } private void doChannel(Thing thing, ThingBuilder thingBuilder, ChannelUID affectedChannelUid) { - if (removeOldChannel) { thingBuilder.withoutChannel(affectedChannelUid); } diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/VoiceManager.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/VoiceManager.java index bfa9c2a5bd5..9abb3fc48e8 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/VoiceManager.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/VoiceManager.java @@ -290,7 +290,6 @@ void listenAndAnswer(@Nullable STTService stt, @Nullable TTSService tts, @Nullab * Only one registration can be done for an audio source. * * @param registration with the desired services ids and options for the dialog - * * @throws IllegalStateException if there is another registration for the same source */ void registerDialog(DialogRegistration registration) throws IllegalStateException; diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/cache/TTSLRUCacheImpl.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/cache/TTSLRUCacheImpl.java index 0ec33b9883d..03ba00db2ad 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/cache/TTSLRUCacheImpl.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/cache/TTSLRUCacheImpl.java @@ -99,7 +99,6 @@ protected void modified(Map config) { @Override public AudioStream get(CachedTTSService tts, String text, Voice voice, AudioFormat requestedFormat) throws TTSException { - LRUMediaCache lruMediaCacheLocal = lruMediaCache; if (!enableCacheTTS || lruMediaCacheLocal == null) { return tts.synthesizeForCache(text, voice, requestedFormat); @@ -115,14 +114,14 @@ public AudioStream get(CachedTTSService tts, String text, Voice voice, AudioForm return new LRUMediaCacheEntry(key, audioInputStream, new AudioFormatInfo(audioInputStream.getFormat())); } catch (TTSException e) { - throw new RuntimeException(e); + throw new IllegalStateException(e); } }); - } catch (RuntimeException re) { - if (re.getCause() != null && re.getCause() instanceof TTSException ttse) { + } catch (IllegalStateException ise) { + if (ise.getCause() != null && ise.getCause() instanceof TTSException ttse) { throw ttse; } else { - throw re; + throw ise; } } diff --git a/bundles/org.openhab.core.voice/src/test/java/org/openhab/core/voice/internal/cache/TTSLRUCacheImplTest.java b/bundles/org.openhab.core.voice/src/test/java/org/openhab/core/voice/internal/cache/TTSLRUCacheImplTest.java index ec336fe04b7..3828dea964d 100644 --- a/bundles/org.openhab.core.voice/src/test/java/org/openhab/core/voice/internal/cache/TTSLRUCacheImplTest.java +++ b/bundles/org.openhab.core.voice/src/test/java/org/openhab/core/voice/internal/cache/TTSLRUCacheImplTest.java @@ -116,7 +116,6 @@ public void getCacheMissAndTwoHitAndTTsIsCalledOnlyOnce() throws TTSException, I @Test public void loadTTSResultsFromCacheDirectory() throws IOException, TTSException { - // prepare cache directory Path cacheDirectory = tempDir.resolve("cache").resolve(TTSLRUCacheImpl.VOICE_TTS_CACHE_PID); Files.createDirectories(cacheDirectory); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/lru/LRUMediaCacheEntry.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/lru/LRUMediaCacheEntry.java index c88350d3b03..74c83aaabde 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/lru/LRUMediaCacheEntry.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/lru/LRUMediaCacheEntry.java @@ -166,7 +166,6 @@ protected String getKey() { * @throws IOException */ public InputStream getInputStream() throws IOException { - File localFile = file; if (localFile == null) { // the cache entry is not tied to the disk. The cache is not ready or not to be used. InputStream inputStreamLocal = inputStream; diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/common/WrappedScheduledExecutorService.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/common/WrappedScheduledExecutorService.java index 2d3bf4f9556..77766cde174 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/common/WrappedScheduledExecutorService.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/common/WrappedScheduledExecutorService.java @@ -44,7 +44,6 @@ protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); Throwable actualThrowable = t; if (actualThrowable == null && r instanceof Future f) { - // The Future is the wrapper task around our scheduled Runnable. This is only "done" if an Exception // occurred, the Task was completed, or aborted. A periodic Task (scheduleWithFixedDelay etc.) is NEVER // "done" unless there was an Exception because the outer Task is always rescheduled. diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemStateConverterImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemStateConverterImpl.java index c25d0fce24b..4a47fdf3eb1 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemStateConverterImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemStateConverterImpl.java @@ -72,7 +72,6 @@ public State convertToAcceptedState(@Nullable State state, @Nullable Item item) if (item instanceof NumberItem numberItem && state instanceof QuantityType quantityState) { if (numberItem.getDimension() != null) { - // in case the item does define a unit it takes precedence over all other conversions: Unit itemUnit = parseItemUnit(numberItem); if (itemUnit != null) { @@ -92,8 +91,6 @@ public State convertToAcceptedState(@Nullable State state, @Nullable Item item) && UnitUtils.isDifferentMeasurementSystem(conversionUnit, quantityState.getUnit())) { return convertOrUndef(quantityState, conversionUnit); } - - return state; } else { State convertedState = state.as(DecimalType.class); if (convertedState != null) { diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/scheduler/SchedulerImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/scheduler/SchedulerImpl.java index e08423b80bf..361be34786c 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/scheduler/SchedulerImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/scheduler/SchedulerImpl.java @@ -177,7 +177,6 @@ private void schedule(ScheduledCompletableFutureRecurring recurringSchedu deferred.thenAccept(v -> { if (temporalAdjuster instanceof SchedulerTemporalAdjuster schedulerTemporalAdjuster) { - if (!schedulerTemporalAdjuster.isDone(newTime)) { schedule(recurringSchedule, runnable, identifier, temporalAdjuster); return; diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/lru/LRUMediaCacheEntryTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/lru/LRUMediaCacheEntryTest.java index cc41cb64c4f..078f80ea043 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/lru/LRUMediaCacheEntryTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/lru/LRUMediaCacheEntryTest.java @@ -250,7 +250,6 @@ public void streamAndMetadataTest() throws IOException { @Test public void getTotalSizeByForcingReadAllTest() throws IOException { - LRUMediaCache lruMediaCache = createCache(1000); // init simulated data stream diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/service/WatchServiceImplTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/service/WatchServiceImplTest.java index ef1c6ee07cb..841b704e497 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/service/WatchServiceImplTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/service/WatchServiceImplTest.java @@ -13,12 +13,8 @@ package org.openhab.core.internal.service; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.not; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.*; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -26,19 +22,19 @@ import java.nio.file.Path; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ExecutorService; import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.openhab.core.JavaTest; import org.openhab.core.OpenHAB; -import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.service.WatchService; import org.openhab.core.service.WatchService.Kind; import org.osgi.framework.BundleContext; @@ -57,11 +53,10 @@ public class WatchServiceImplTest extends JavaTest { private @NonNullByDefault({}) String systemConfDirProperty; - private @NonNullByDefault({}) WatchServiceImpl.WatchServiceConfiguration configurationMock; + public @Mock @NonNullByDefault({}) WatchServiceImpl.WatchServiceConfiguration configurationMock; private @NonNullByDefault({}) WatchServiceImpl watchService; private @NonNullByDefault({}) Path rootPath; - private @NonNullByDefault({}) Path subDirPath; private @NonNullByDefault({}) TestWatchEventListener listener; @BeforeEach @@ -70,8 +65,6 @@ public void setup() throws IOException { systemConfDirProperty = System.getProperty(OpenHAB.CONFIG_DIR_PROG_ARGUMENT); rootPath = Files.createDirectories(Path.of("target", "test-watcher")); - subDirPath = Files.createDirectories(rootPath.resolve(SUB_DIR_PATH_NAME)); - ExecutorService ex = ThreadPoolManager.getScheduledPool("file-processing"); System.setProperty(OpenHAB.CONFIG_DIR_PROG_ARGUMENT, rootPath.toString()); when(configurationMock.name()).thenReturn("unnamed"); @@ -88,7 +81,8 @@ public void tearDown() throws IOException { } @Test - private void testFileInWatchedDir() throws IOException, InterruptedException { + @Disabled("Broken") + public void testFileInWatchedDir() throws IOException, InterruptedException { watchService.registerListener(listener, Path.of(""), false); Path testFile = rootPath.resolve(TEST_FILE_NANE); @@ -108,7 +102,8 @@ private void testFileInWatchedDir() throws IOException, InterruptedException { } @Test - private void testFileInWatchedSubDir() throws IOException, InterruptedException { + @Disabled("Broken") + public void testFileInWatchedSubDir() throws IOException, InterruptedException { // listener is listening to root and sub-dir watchService.registerListener(listener, Path.of(""), false); @@ -129,7 +124,8 @@ private void testFileInWatchedSubDir() throws IOException, InterruptedException } @Test - private void testFileInWatchedSubDir2() throws IOException, InterruptedException { + @Disabled("Broken") + public void testFileInWatchedSubDir2() throws IOException, InterruptedException { // listener is only listening to sub-dir of root watchService.registerListener(listener, Path.of(SUB_DIR_PATH_NAME), false); @@ -150,7 +146,8 @@ private void testFileInWatchedSubDir2() throws IOException, InterruptedException } @Test - private void testFileInUnwatchedSubDir() throws IOException, InterruptedException { + @Disabled("Broken") + public void testFileInUnwatchedSubDir() throws IOException, InterruptedException { watchService.registerListener(listener, Path.of(""), false); Path testFile = rootPath.resolve(SUB_DIR_PATH_NAME).resolve(TEST_FILE_NANE); @@ -169,7 +166,8 @@ private void testFileInUnwatchedSubDir() throws IOException, InterruptedExceptio } @Test - private void testNewSubDirAlsoWatched() throws IOException, InterruptedException { + @Disabled("Broken") + public void testNewSubDirAlsoWatched() throws IOException, InterruptedException { watchService.registerListener(listener, Path.of(""), false); Path subDirSubDir = Files.createDirectories(rootPath.resolve(SUB_DIR_PATH_NAME).resolve(SUB_DIR_PATH_NAME)); @@ -209,7 +207,7 @@ private void assertEvent(Path path, Kind kind) throws InterruptedException { listener.events.clear(); } - private class TestWatchEventListener implements WatchService.WatchEventListener { + private static class TestWatchEventListener implements WatchService.WatchEventListener { List events = new CopyOnWriteArrayList<>(); @Override diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/unit/UnitsTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/unit/UnitsTest.java index ef65bf45d2d..14d995c3361 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/unit/UnitsTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/unit/UnitsTest.java @@ -398,7 +398,6 @@ public QuantityEquals(Quantity quantity) { @Override public boolean matches(@Nullable Object actualValue) { if (actualValue instanceof Quantity other) { - if (!other.getUnit().isCompatible(quantity.getUnit())) { return false; } diff --git a/itests/org.openhab.core.storage.json.tests/src/main/java/org/openhab/core/storage/json/internal/ThingMigrationOSGiTest.java b/itests/org.openhab.core.storage.json.tests/src/main/java/org/openhab/core/storage/json/internal/ThingMigrationOSGiTest.java index 2cea337292a..722f0e56f4a 100644 --- a/itests/org.openhab.core.storage.json.tests/src/main/java/org/openhab/core/storage/json/internal/ThingMigrationOSGiTest.java +++ b/itests/org.openhab.core.storage.json.tests/src/main/java/org/openhab/core/storage/json/internal/ThingMigrationOSGiTest.java @@ -13,7 +13,7 @@ package org.openhab.core.storage.json.internal; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.is; import java.io.IOException; import java.nio.file.Files; @@ -38,7 +38,6 @@ public class ThingMigrationOSGiTest extends JavaOSGiTest { private static final Path DB_DIR = Path.of(OpenHAB.getUserDataFolder(), "jsondb"); private static final String DB_NAME = "org.openhab.core.thing.Thing"; - private static final String DB_OLD_NAME = "org.openhab.core.thing.Thing-old"; @Test public void migrationParsable() throws IOException { diff --git a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/ChannelLinkNotifierOSGiTest.java b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/ChannelLinkNotifierOSGiTest.java index 6ab98da08ab..42d01aa87d1 100644 --- a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/ChannelLinkNotifierOSGiTest.java +++ b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/ChannelLinkNotifierOSGiTest.java @@ -15,11 +15,9 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; import java.util.ArrayList; import java.util.HashMap; @@ -116,9 +114,9 @@ public class ChannelLinkNotifierOSGiTest extends JavaOSGiTest { private @NonNullByDefault({}) ManagedThingProvider managedThingProvider; private @NonNullByDefault({}) ThingRegistry thingRegistry; - private @Mock @NonNullByDefault({}) Bundle bundleMock; - private @Mock @NonNullByDefault({}) BundleResolver bundleResolverMock; - private @Mock @NonNullByDefault({}) ThingHandlerFactory thingHandlerFactoryMock; + public @Mock @NonNullByDefault({}) Bundle bundleMock; + public @Mock @NonNullByDefault({}) BundleResolver bundleResolverMock; + public @Mock @NonNullByDefault({}) ThingHandlerFactory thingHandlerFactoryMock; /** * A thing handler which updates the {@link ThingStatus} when initialized to the provided {@code thingStatus} value. From 38a6d1e87bdd3fadcc2adcd4604a2bed90ab4c06 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 2 Apr 2023 12:09:35 +0200 Subject: [PATCH 019/126] # This is a combination of 2 commits. (#3486) Bump commons-fileupload Signed-off-by: Jan N. Klug --- bom/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 42bd449eb2d..a127bb0abea 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -392,7 +392,7 @@ commons-fileupload commons-fileupload - 1.4 + 1.5 compile From ee392e861fe7e6684b28e19a65e808beca2a83d2 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Tue, 4 Apr 2023 07:33:16 +0200 Subject: [PATCH 020/126] Fix WatchService tests (#3518) Signed-off-by: Jan N. Klug --- .../service/WatchServiceImplTest.java | 60 +++++++++---------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/service/WatchServiceImplTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/service/WatchServiceImplTest.java index 841b704e497..af0f9e2914d 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/service/WatchServiceImplTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/service/WatchServiceImplTest.java @@ -26,15 +26,14 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.openhab.core.JavaTest; -import org.openhab.core.OpenHAB; import org.openhab.core.service.WatchService; import org.openhab.core.service.WatchService.Kind; import org.osgi.framework.BundleContext; @@ -49,44 +48,37 @@ @MockitoSettings(strictness = Strictness.LENIENT) public class WatchServiceImplTest extends JavaTest { private static final String SUB_DIR_PATH_NAME = "subDir"; - private static final String TEST_FILE_NANE = "testFile"; - - private @NonNullByDefault({}) String systemConfDirProperty; + private static final String TEST_FILE_NAME = "testFile"; public @Mock @NonNullByDefault({}) WatchServiceImpl.WatchServiceConfiguration configurationMock; + public @Mock @NonNullByDefault({}) BundleContext bundleContextMock; private @NonNullByDefault({}) WatchServiceImpl watchService; - private @NonNullByDefault({}) Path rootPath; + private @NonNullByDefault({}) @TempDir Path rootPath; private @NonNullByDefault({}) TestWatchEventListener listener; @BeforeEach public void setup() throws IOException { - // store property so we can restore later - systemConfDirProperty = System.getProperty(OpenHAB.CONFIG_DIR_PROG_ARGUMENT); - - rootPath = Files.createDirectories(Path.of("target", "test-watcher")); - System.setProperty(OpenHAB.CONFIG_DIR_PROG_ARGUMENT, rootPath.toString()); - when(configurationMock.name()).thenReturn("unnamed"); - when(configurationMock.path()).thenReturn(""); + when(configurationMock.path()).thenReturn(rootPath.toString()); - watchService = new WatchServiceImpl(configurationMock, mock(BundleContext.class)); + watchService = new WatchServiceImpl(configurationMock, bundleContextMock); listener = new TestWatchEventListener(); + + verify(bundleContextMock, timeout(5000)).registerService(eq(WatchService.class), eq(watchService), any()); } @AfterEach public void tearDown() throws IOException { watchService.deactivate(); - System.setProperty(OpenHAB.CONFIG_DIR_PROG_ARGUMENT, systemConfDirProperty); } @Test - @Disabled("Broken") public void testFileInWatchedDir() throws IOException, InterruptedException { - watchService.registerListener(listener, Path.of(""), false); + watchService.registerListener(listener, rootPath, false); - Path testFile = rootPath.resolve(TEST_FILE_NANE); - Path relativeTestFilePath = Path.of(TEST_FILE_NANE); + Path testFile = rootPath.resolve(TEST_FILE_NAME); + Path relativeTestFilePath = Path.of(TEST_FILE_NAME); Files.writeString(testFile, "initial content", StandardCharsets.UTF_8); assertEvent(relativeTestFilePath, Kind.CREATE); @@ -102,13 +94,14 @@ public void testFileInWatchedDir() throws IOException, InterruptedException { } @Test - @Disabled("Broken") public void testFileInWatchedSubDir() throws IOException, InterruptedException { + Files.createDirectories(rootPath.resolve(SUB_DIR_PATH_NAME)); + // listener is listening to root and sub-dir - watchService.registerListener(listener, Path.of(""), false); + watchService.registerListener(listener, rootPath, true); - Path testFile = rootPath.resolve(SUB_DIR_PATH_NAME).resolve(TEST_FILE_NANE); - Path relativeTestFilePath = Path.of(SUB_DIR_PATH_NAME, TEST_FILE_NANE); + Path testFile = rootPath.resolve(SUB_DIR_PATH_NAME).resolve(TEST_FILE_NAME); + Path relativeTestFilePath = Path.of(SUB_DIR_PATH_NAME, TEST_FILE_NAME); Files.writeString(testFile, "initial content", StandardCharsets.UTF_8); assertEvent(relativeTestFilePath, Kind.CREATE); @@ -124,13 +117,14 @@ public void testFileInWatchedSubDir() throws IOException, InterruptedException { } @Test - @Disabled("Broken") public void testFileInWatchedSubDir2() throws IOException, InterruptedException { + Files.createDirectories(rootPath.resolve(SUB_DIR_PATH_NAME)); + // listener is only listening to sub-dir of root watchService.registerListener(listener, Path.of(SUB_DIR_PATH_NAME), false); - Path testFile = rootPath.resolve(SUB_DIR_PATH_NAME).resolve(TEST_FILE_NANE); - Path relativeTestFilePath = Path.of(TEST_FILE_NANE); + Path testFile = rootPath.resolve(SUB_DIR_PATH_NAME).resolve(TEST_FILE_NAME); + Path relativeTestFilePath = Path.of(TEST_FILE_NAME); Files.writeString(testFile, "initial content", StandardCharsets.UTF_8); assertEvent(relativeTestFilePath, Kind.CREATE); @@ -146,11 +140,12 @@ public void testFileInWatchedSubDir2() throws IOException, InterruptedException } @Test - @Disabled("Broken") public void testFileInUnwatchedSubDir() throws IOException, InterruptedException { - watchService.registerListener(listener, Path.of(""), false); + Files.createDirectories(rootPath.resolve(SUB_DIR_PATH_NAME)); + + watchService.registerListener(listener, rootPath, false); - Path testFile = rootPath.resolve(SUB_DIR_PATH_NAME).resolve(TEST_FILE_NANE); + Path testFile = rootPath.resolve(SUB_DIR_PATH_NAME).resolve(TEST_FILE_NAME); Files.writeString(testFile, "initial content", StandardCharsets.UTF_8); assertNoEvent(); @@ -166,15 +161,14 @@ public void testFileInUnwatchedSubDir() throws IOException, InterruptedException } @Test - @Disabled("Broken") public void testNewSubDirAlsoWatched() throws IOException, InterruptedException { - watchService.registerListener(listener, Path.of(""), false); + watchService.registerListener(listener, rootPath, true); Path subDirSubDir = Files.createDirectories(rootPath.resolve(SUB_DIR_PATH_NAME).resolve(SUB_DIR_PATH_NAME)); assertNoEvent(); - Path testFile = subDirSubDir.resolve(TEST_FILE_NANE); - Path relativeTestFilePath = testFile.relativize(rootPath); + Path testFile = subDirSubDir.resolve(TEST_FILE_NAME); + Path relativeTestFilePath = rootPath.relativize(testFile); Files.writeString(testFile, "initial content", StandardCharsets.UTF_8); assertEvent(relativeTestFilePath, Kind.CREATE); From 4a82f5ba4943bc363f1a482c0d7e6b06f30205ba Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Thu, 6 Apr 2023 20:51:40 +0200 Subject: [PATCH 021/126] New Crowdin updates (#3509) * New translations scriptprofile.properties (Italian) * New translations messages.properties (German) * New translations scriptprofile.properties (German) * New translations scriptprofile.properties (Hebrew) * New translations scriptprofile.properties (Polish) --- .../resources/OH-INF/i18n/scriptprofile_de.properties | 8 ++++---- .../resources/OH-INF/i18n/scriptprofile_he.properties | 8 ++++---- .../resources/OH-INF/i18n/scriptprofile_it.properties | 8 ++++---- .../resources/OH-INF/i18n/scriptprofile_pl.properties | 8 ++++---- .../src/main/resources/messages_de.properties | 4 ++-- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_de.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_de.properties index 7f3a1ab014b..a645a911802 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_de.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_de.properties @@ -1,6 +1,6 @@ profile.system.script.scriptLanguage.label = Skriptsprache profile.system.script.scriptLanguage.description = MIME-Typ ("application/vnd.openhab.dsl.rule") der Skriptsprache. -profile.system.script.toItemScript.label = Skript Zum Item -profile.system.script.toItemScript.description = Das Skript für die Umwandlung von States und Commands vom Thing Handler zum Item. -profile.system.script.toHandlerScript.label = Skript zum Thing Handler -profile.system.script.toHandlerScript.description = Das Skript für die Umwandlung von States und Commands vom Item zum Thing Handler. +profile.system.script.toItemScript.label = Transformation Thing -> Item +profile.system.script.toItemScript.description = Das Skript für die Transformtion von States und Commands vom Thing zum Item. +profile.system.script.toHandlerScript.label = Transformation Item -> Thing +profile.system.script.toHandlerScript.description = Das Skript für die Transformation von States und Commands vom Item zum Thing. diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_he.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_he.properties index fc6bb5aae6c..3274b2e7c51 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_he.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_he.properties @@ -1,6 +1,6 @@ profile.system.script.scriptLanguage.label = שפת תסריט profile.system.script.scriptLanguage.description = סוג MIME ("application/vnd.openhab.dsl.rule") של שפת הסקריפט -profile.system.script.toItemScript.label = אל סקריפט הפריט -profile.system.script.toItemScript.description = הסקריפט להמרת מצבים ופקודות מהאחראי על הפריט. -profile.system.script.toHandlerScript.label = לתסריט האחראי -profile.system.script.toHandlerScript.description = הסקריפט להמרת מצבים ופקודות מפריט לאחראי. +profile.system.script.toItemScript.label = המרת Thing לפריט +profile.system.script.toItemScript.description = הסקריפט להפיכת עדכוני מצב ופקודות מהמטפל ב-Thing לפריט. הסקריפט עשוי לחזור null כדי למחוק את העדכונים/פקודות ולא להעביר אותם. +profile.system.script.toHandlerScript.label = שינוי פריט ל-thing +profile.system.script.toHandlerScript.description = הסקריפט להמרת פקודות מהפריט ל-Thing handler. הסקריפט עשוי להחזיר null כדי למחוק את הפקודות ולא להעביר אותן. diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_it.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_it.properties index 4040707691d..ba5bdca5c3b 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_it.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_it.properties @@ -1,6 +1,6 @@ profile.system.script.scriptLanguage.label = Linguaggio Script profile.system.script.scriptLanguage.description = Tipo MIME ("application/vnd.openhab.dsl.rule") del linguaggio di scripting -profile.system.script.toItemScript.label = Allo Script dell'Item -profile.system.script.toItemScript.description = Lo script per trasformare stati e comandi dal gestore all'Item. -profile.system.script.toHandlerScript.label = Allo script dell'Handler -profile.system.script.toHandlerScript.description = Lo script per trasformare stati e comandi dal'Item al gestore. +profile.system.script.toItemScript.label = Trasformazione da Thing a Item +profile.system.script.toItemScript.description = Lo script per trasformare gli aggiornamenti dello stato e i comandi dal gestore Thing all'Item. Lo script può restituire null per scartare gli aggiornamenti/comandi e non passarli. +profile.system.script.toHandlerScript.label = Trasformazione da Item a Thing +profile.system.script.toHandlerScript.description = Lo script per trasformare i comandi dall'Item al gestore di Thing. Lo script può restituire null per scartare i comandi e non passarli. diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_pl.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_pl.properties index 899c18a77de..77de1604dd5 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_pl.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_pl.properties @@ -1,6 +1,6 @@ profile.system.script.scriptLanguage.label = Język Skryptu profile.system.script.scriptLanguage.description = MIME-Typ języka skryptu ("application/vnd.openhab.dsl.rule") -profile.system.script.toItemScript.label = Skrypt do elementu -profile.system.script.toItemScript.description = Skrypt przekształcania stanów i poleceń z kanału do elementu. -profile.system.script.toHandlerScript.label = Skrypt do kanału -profile.system.script.toHandlerScript.description = Skrypt przekształcania stanów i poleceń z elementu do kanału. +profile.system.script.toItemScript.label = Skrypt transformacji z kanału do elementu +profile.system.script.toItemScript.description = Skrypt do transformacji stanu lub wartości z kanału do elementu. Skrypt może zwrócić stan lub wartość *null* aby pominąć transformację i nie przekazać jej do elementu. +profile.system.script.toHandlerScript.label = Skrypt transformacji z elementu do kanału +profile.system.script.toHandlerScript.description = Skrypt do transformacji stanu lub wartości z elementu do kanału. Skrypt może zwrócić stan lub wartość *null* aby pominąć transformację i nie przekazać jej do kanału. diff --git a/bundles/org.openhab.core.io.http.auth/src/main/resources/messages_de.properties b/bundles/org.openhab.core.io.http.auth/src/main/resources/messages_de.properties index 99902399d3b..345d3c58412 100644 --- a/bundles/org.openhab.core.io.http.auth/src/main/resources/messages_de.properties +++ b/bundles/org.openhab.core.io.http.auth/src/main/resources/messages_de.properties @@ -1,4 +1,4 @@ -auth.login.prompt = Anmelden, um %s Zugriff auf %s zu gewähren\: +auth.login.prompt = Anmelden, um %s-Zugriff auf %s zu gewähren\: auth.login.fail = Bitte erneut versuchen. auth.createaccount.prompt = Erstelle ein Administrator-Konto, um fortzufahren. @@ -8,7 +8,7 @@ auth.createapitoken.prompt = Erstelle einen neuen Token, um externe Dienste zu a auth.createapitoken.name.unique.fail = Ein Token mit dem selben Namen existiert bereits. Bitte erneut versuchen. auth.createapitoken.name.format.fail = Ungültiger Token Name. Bitte nutzen Sie nur alphanumerische Zeichen. auth.createapitoken.success = Neuer Token erstellt\: -auth.createapitoken.success.footer = Bitte kopieren Sie es jetzt. Es wird anschließend nicht mehr angezeigt. +auth.createapitoken.success.footer = Bitte kopieren Sie ihn jetzt, da er anschließend nicht mehr angezeigt wird. auth.password.confirm.fail = Passwörter stimmen nicht überein. Bitte erneut versuchen. From 8af908302b41477d93432216878626450b313bf9 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Fri, 7 Apr 2023 09:44:28 +0200 Subject: [PATCH 022/126] Fix IllegalStateException in CommunityKaraAddonHAndler (#3528) Signed-off-by: Jan N. Klug --- .../karaf/internal/community/CommunityKarafAddonHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.addon.marketplace.karaf/src/main/java/org/openhab/core/addon/marketplace/karaf/internal/community/CommunityKarafAddonHandler.java b/bundles/org.openhab.core.addon.marketplace.karaf/src/main/java/org/openhab/core/addon/marketplace/karaf/internal/community/CommunityKarafAddonHandler.java index 4de98365d5c..6dc61c0efad 100644 --- a/bundles/org.openhab.core.addon.marketplace.karaf/src/main/java/org/openhab/core/addon/marketplace/karaf/internal/community/CommunityKarafAddonHandler.java +++ b/bundles/org.openhab.core.addon.marketplace.karaf/src/main/java/org/openhab/core/addon/marketplace/karaf/internal/community/CommunityKarafAddonHandler.java @@ -80,7 +80,8 @@ public boolean supports(String type, String contentType) { private Stream karFilesStream(Path addonDirectory) throws IOException { if (Files.isDirectory(addonDirectory)) { try (Stream files = Files.list(addonDirectory)) { - return files.map(Path::getFileName).filter(path -> path.toString().endsWith(KAR_EXTENSION)); + return files.map(Path::getFileName).filter(path -> path.toString().endsWith(KAR_EXTENSION)).toList() + .stream(); } } return Stream.empty(); From 633002c6473a48a209fcb889fae700f8b0d119eb Mon Sep 17 00:00:00 2001 From: Florian Hotze Date: Fri, 7 Apr 2023 14:26:19 +0200 Subject: [PATCH 023/126] Fix wrong event subscription in `CommunicationManager` (#3529) * Fix wrong event subscription in CommunicationManager Signed-off-by: Florian Hotze --- .../openhab/core/thing/internal/CommunicationManager.java | 3 +-- .../src/main/java/org/openhab/core/items/GroupItem.java | 2 +- .../org/openhab/core/items/events/ItemEventFactory.java | 8 ++++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java index e2b3a9b8b1d..608179f6459 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java @@ -43,7 +43,6 @@ import org.openhab.core.items.events.AbstractItemRegistryEvent; import org.openhab.core.items.events.GroupStateUpdatedEvent; import org.openhab.core.items.events.ItemCommandEvent; -import org.openhab.core.items.events.ItemStateEvent; import org.openhab.core.items.events.ItemStateUpdatedEvent; import org.openhab.core.library.items.NumberItem; import org.openhab.core.library.types.DecimalType; @@ -116,7 +115,7 @@ public void onStateUpdateFromItem(State state) { // the timeout to use for any item event processing public static final long THINGHANDLER_EVENT_TIMEOUT = TimeUnit.SECONDS.toMillis(30); - private static final Set SUBSCRIBED_EVENT_TYPES = Set.of(ItemStateEvent.TYPE, ItemCommandEvent.TYPE, + private static final Set SUBSCRIBED_EVENT_TYPES = Set.of(ItemStateUpdatedEvent.TYPE, ItemCommandEvent.TYPE, GroupStateUpdatedEvent.TYPE, ChannelTriggeredEvent.TYPE); private final Logger logger = LoggerFactory.getLogger(CommunicationManager.class); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GroupItem.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GroupItem.java index 360deb2bd7a..742ca416b99 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GroupItem.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GroupItem.java @@ -416,7 +416,7 @@ public void setUnitProvider(@Nullable UnitProvider unitProvider) { private void sendGroupStateUpdatedEvent(String memberName, State state) { EventPublisher eventPublisher1 = this.eventPublisher; if (eventPublisher1 != null) { - eventPublisher1.post(ItemEventFactory.createGroupStateEvent(getName(), memberName, state, null)); + eventPublisher1.post(ItemEventFactory.createGroupStateUpdatedEvent(getName(), memberName, state, null)); } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/ItemEventFactory.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/ItemEventFactory.java index ccf1cababa0..5f4bdbc460d 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/ItemEventFactory.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/events/ItemEventFactory.java @@ -95,14 +95,14 @@ protected Event createEventByType(String eventType, String topic, String payload } else if (ItemRemovedEvent.TYPE.equals(eventType)) { return createRemovedEvent(topic, payload); } else if (GroupStateUpdatedEvent.TYPE.equals(eventType)) { - return createGroupStateEvent(topic, payload); + return createGroupStateUpdatedEvent(topic, payload); } else if (GroupItemStateChangedEvent.TYPE.equals(eventType)) { return createGroupStateChangedEvent(topic, payload); } throw new IllegalArgumentException("The event type '" + eventType + "' is not supported by this factory."); } - private Event createGroupStateEvent(String topic, String payload) { + private Event createGroupStateUpdatedEvent(String topic, String payload) { String itemName = getItemName(topic); String memberName = getMemberName(topic); ItemEventPayloadBean bean = deserializePayload(payload, ItemEventPayloadBean.class); @@ -321,7 +321,7 @@ public static ItemStateUpdatedEvent createStateUpdatedEvent(String itemName, Sta } /** - * Creates an group item state updated event. + * Creates a group item state updated event. * * @param groupName the name of the group to report the state update for * @param member the name of the item that updated the group state @@ -330,7 +330,7 @@ public static ItemStateUpdatedEvent createStateUpdatedEvent(String itemName, Sta * @return the created group item state update event * @throws IllegalArgumentException if groupName or state is null */ - public static GroupStateUpdatedEvent createGroupStateEvent(String groupName, String member, State state, + public static GroupStateUpdatedEvent createGroupStateUpdatedEvent(String groupName, String member, State state, @Nullable String source) { assertValidArguments(groupName, member, state, "state"); String topic = buildGroupTopic(GROUP_STATE_EVENT_TOPIC, groupName, member); From 3047ed42a560d14d36618f921656f6446ca399f1 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Fri, 7 Apr 2023 14:47:08 +0200 Subject: [PATCH 024/126] Prevent unnecessary MODIFY events in WatchServiceImpl (#3524) Signed-off-by: Jan N. Klug --- .../internal/service/WatchServiceImpl.java | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/WatchServiceImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/WatchServiceImpl.java index e9aaa5b1856..d3faf761fc6 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/WatchServiceImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/WatchServiceImpl.java @@ -21,6 +21,7 @@ import java.util.Hashtable; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @@ -48,6 +49,7 @@ import io.methvin.watcher.DirectoryChangeEvent; import io.methvin.watcher.DirectoryChangeListener; import io.methvin.watcher.DirectoryWatcher; +import io.methvin.watcher.hashing.FileHash; /** * The {@link WatchServiceImpl} is the implementation of the {@link WatchService} @@ -70,6 +72,7 @@ public class WatchServiceImpl implements WatchService, DirectoryChangeListener { private final List dirPathListeners = new CopyOnWriteArrayList<>(); private final List subDirPathListeners = new CopyOnWriteArrayList<>(); + private final Map hashCache = new ConcurrentHashMap<>(); private final ExecutorService executor; private final ScheduledExecutorService scheduler; @@ -81,7 +84,7 @@ public class WatchServiceImpl implements WatchService, DirectoryChangeListener { private @Nullable ServiceRegistration reg; private final Map> scheduledEvents = new HashMap<>(); - private final Map> scheduledEventKinds = new ConcurrentHashMap<>(); + private final Map> scheduledEventKinds = new ConcurrentHashMap<>(); @Activate public WatchServiceImpl(WatchServiceConfiguration config, BundleContext bundleContext) throws IOException { @@ -170,6 +173,8 @@ private void closeWatcherAndUnregister() throws IOException { } this.reg = null; } + + hashCache.clear(); } @Override @@ -217,12 +222,6 @@ public void onEvent(@Nullable DirectoryChangeEvent directoryChangeEvent) throws } Path path = directoryChangeEvent.path(); - Kind kind = switch (directoryChangeEvent.eventType()) { - case CREATE -> Kind.CREATE; - case MODIFY -> Kind.MODIFY; - case DELETE -> Kind.DELETE; - case OVERFLOW -> Kind.OVERFLOW; - }; synchronized (scheduledEvents) { ScheduledFuture future = scheduledEvents.remove(path); @@ -230,39 +229,39 @@ public void onEvent(@Nullable DirectoryChangeEvent directoryChangeEvent) throws future.cancel(true); } future = scheduler.schedule(() -> notifyListeners(path), PROCESSING_TIME, TimeUnit.MILLISECONDS); - scheduledEventKinds.computeIfAbsent(path, k -> new CopyOnWriteArrayList<>()).add(kind); + scheduledEventKinds.computeIfAbsent(path, k -> new CopyOnWriteArrayList<>()).add(directoryChangeEvent); scheduledEvents.put(path, future); } } private void notifyListeners(Path path) { - List kinds = scheduledEventKinds.remove(path); - if (kinds == null || kinds.isEmpty()) { + List events = scheduledEventKinds.remove(path); + if (events == null || events.isEmpty()) { logger.debug("Tried to notify listeners of change events for '{}', but the event list is empty.", path); return; } - if (kinds.size() == 1) { - // we have only one event - doNotify(path, kinds.get(0)); - return; - } - - Kind firstElement = kinds.get(0); - Kind lastElement = kinds.get(kinds.size() - 1); + DirectoryChangeEvent firstElement = events.get(0); + DirectoryChangeEvent lastElement = events.get(events.size() - 1); // determine final event - if (lastElement == Kind.DELETE) { - if (firstElement == Kind.CREATE) { - logger.debug("Discarding events for '{}' because file was immediately deleted bafter creation", path); + if (lastElement.eventType() == DirectoryChangeEvent.EventType.DELETE) { + if (firstElement.eventType() == DirectoryChangeEvent.EventType.CREATE) { + logger.debug("Discarding events for '{}' because file was immediately deleted after creation", path); return; } + hashCache.remove(lastElement.path()); doNotify(path, Kind.DELETE); - } else if (firstElement == Kind.CREATE) { + } else if (firstElement.eventType() == DirectoryChangeEvent.EventType.CREATE) { + hashCache.put(lastElement.path(), lastElement.hash()); doNotify(path, Kind.CREATE); } else { - doNotify(path, Kind.MODIFY); + FileHash oldHash = hashCache.put(lastElement.path(), lastElement.hash()); + if (!Objects.equals(oldHash, lastElement.hash())) { + // only notify if hashes are different, otherwise the file content did not chnge + doNotify(path, Kind.MODIFY); + } } } From b0b8bb547bd6ead026f48b15561729b2f1c4d9bf Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Fri, 7 Apr 2023 18:43:01 +0200 Subject: [PATCH 025/126] Reduce rounding errors of RGB/HSB conversion and enhance ColorUtil (#3479) * HSBType: Reduce rounding errors of RGB/HSB conversion * Move RGB to HSV conversion to ColorUtil * Restructuring HSBType and ColorUtil - Move RBG/HSB conversion from HSBType to ColorUtil - Rename helper functions "hsv" to "hsb" to be consistent with HSBType - Add parameterized tests Co-authored-by: Jacob Laursen Signed-off-by: Holger Friedrich --- .../openhab/core/library/types/HSBType.java | 143 ++++-------- .../java/org/openhab/core/util/ColorUtil.java | 208 ++++++++++++++++-- .../core/library/types/HSBTypeTest.java | 21 +- .../org/openhab/core/util/ColorUtilTest.java | 168 ++++++++++++-- 4 files changed, 405 insertions(+), 135 deletions(-) diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/HSBType.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/HSBType.java index a3fefda2379..5744ae67b10 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/HSBType.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/HSBType.java @@ -34,6 +34,7 @@ * * @author Kai Kreuzer - Initial contribution * @author Chris Jackson - Added fromRGB + * @author Andrew Fiddian-Green - closeTo (copied from binding) */ @NonNullByDefault public class HSBType extends PercentType implements ComplexType, State, Command { @@ -113,50 +114,22 @@ public static HSBType valueOf(String value) { } /** - * Create HSB from RGB + * Create HSB from RGB. + * + * See also {@link ColorUtil#rgbToHsb(int[])}. * * @param r red 0-255 * @param g green 0-255 * @param b blue 0-255 + * @throws IllegalArgumentException when color values exceed allowed range */ - public static HSBType fromRGB(int r, int g, int b) { - float tmpHue, tmpSaturation, tmpBrightness; - int max = (r > g) ? r : g; - if (b > max) { - max = b; - } - int min = (r < g) ? r : g; - if (b < min) { - min = b; - } - tmpBrightness = max / 2.55f; - tmpSaturation = (max != 0 ? ((float) (max - min)) / ((float) max) : 0) * 100; - if (tmpSaturation == 0) { - tmpHue = 0; - } else { - float red = ((float) (max - r)) / ((float) (max - min)); - float green = ((float) (max - g)) / ((float) (max - min)); - float blue = ((float) (max - b)) / ((float) (max - min)); - if (r == max) { - tmpHue = blue - green; - } else if (g == max) { - tmpHue = 2.0f + red - blue; - } else { - tmpHue = 4.0f + green - red; - } - tmpHue = tmpHue / 6.0f * 360; - if (tmpHue < 0) { - tmpHue = tmpHue + 360.0f; - } - } - - return new HSBType(new DecimalType((int) tmpHue), new PercentType((int) tmpSaturation), - new PercentType((int) tmpBrightness)); + public static HSBType fromRGB(int r, int g, int b) throws IllegalArgumentException { + return ColorUtil.rgbToHsb(new int[] { r, g, b }); } /** - * @deprecated Use {@link ColorUtil#xyToHsv(double[])} or {@link ColorUtil#xyToHsv(double[], ColorUtil.Gamut)} - * instead + * @deprecated Use {@link ColorUtil#xyToHsb(double[])} or {@link ColorUtil#xyToHsb(double[], ColorUtil.Gamut)} + * instead. * * Returns a HSBType object representing the provided xy color values in CIE XY color model. * Conversion from CIE XY color model to sRGB using D65 reference white @@ -164,11 +137,11 @@ public static HSBType fromRGB(int r, int g, int b) { * * @param x, y color information 0.0 - 1.0 * @return new HSBType object representing the given CIE XY color, full brightness - * + * @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range */ @Deprecated - public static HSBType fromXY(float x, float y) { - return ColorUtil.xyToHsv(new double[] { x, y }); + public static HSBType fromXY(float x, float y) throws IllegalArgumentException { + return ColorUtil.xyToHsb(new double[] { x, y }); } @Override @@ -192,29 +165,36 @@ public PercentType getBrightness() { return new PercentType(value); } + /** @deprecated Use {@link ColorUtil#hsbToRgb(HSBType)} instead */ + @Deprecated public PercentType getRed() { return toRGB()[0]; } + /** @deprecated Use {@link ColorUtil#hsbToRgb(HSBType)} instead */ + @Deprecated public PercentType getGreen() { return toRGB()[1]; } + /** @deprecated Use {@link ColorUtil#hsbToRgb(HSBType)} instead */ + @Deprecated public PercentType getBlue() { return toRGB()[2]; } /** - * Returns the RGB value representing the color in the default sRGB - * color model. - * (Bits 24-31 are alpha, 16-23 are red, 8-15 are green, 0-7 are blue). + * @deprecated Use {@link ColorUtil#hsbTosRgb(HSBType)} instead. + * + * Returns the RGB value representing the color in the default sRGB + * color model. + * (Bits 24-31 are alpha, 16-23 are red, 8-15 are green, 0-7 are blue). * * @return the RGB value of the color in the default sRGB color model */ + @Deprecated public int getRGB() { - PercentType[] rgb = toRGB(); - return ((0xFF) << 24) | ((convertPercentToByte(rgb[0]) & 0xFF) << 16) - | ((convertPercentToByte(rgb[1]) & 0xFF) << 8) | ((convertPercentToByte(rgb[2]) & 0xFF) << 0); + return ColorUtil.hsbTosRgb(this); } @Override @@ -266,58 +246,10 @@ public boolean equals(@Nullable Object obj) { && getBrightness().equals(other.getBrightness()); } + /* @deprecated Use {@link ColorUtil#hsbToRgb(HSBType)} instead */ + @Deprecated public PercentType[] toRGB() { - PercentType red = null; - PercentType green = null; - PercentType blue = null; - - BigDecimal h = hue.divide(BIG_DECIMAL_HUNDRED, 10, RoundingMode.HALF_UP); - BigDecimal s = saturation.divide(BIG_DECIMAL_HUNDRED); - - int hInt = h.multiply(BigDecimal.valueOf(5)).divide(BigDecimal.valueOf(3), 10, RoundingMode.HALF_UP).intValue(); - BigDecimal f = h.multiply(BigDecimal.valueOf(5)).divide(BigDecimal.valueOf(3), 10, RoundingMode.HALF_UP) - .remainder(BigDecimal.ONE); - PercentType a = new PercentType(value.multiply(BigDecimal.ONE.subtract(s))); - PercentType b = new PercentType(value.multiply(BigDecimal.ONE.subtract(s.multiply(f)))); - PercentType c = new PercentType( - value.multiply(BigDecimal.ONE.subtract((BigDecimal.ONE.subtract(f)).multiply(s)))); - - switch (hInt) { - case 0: - case 6: - red = getBrightness(); - green = c; - blue = a; - break; - case 1: - red = b; - green = getBrightness(); - blue = a; - break; - case 2: - red = a; - green = getBrightness(); - blue = c; - break; - case 3: - red = a; - green = b; - blue = getBrightness(); - break; - case 4: - red = c; - green = a; - blue = getBrightness(); - break; - case 5: - red = getBrightness(); - green = a; - blue = b; - break; - default: - throw new IllegalArgumentException("Could not convert to RGB."); - } - return new PercentType[] { red, green, blue }; + return ColorUtil.hsbToRgbPercent(this); } /** @@ -334,7 +266,7 @@ public PercentType[] toXY() { } private int convertPercentToByte(PercentType percent) { - return percent.value.multiply(BigDecimal.valueOf(255)).divide(BIG_DECIMAL_HUNDRED, 2, RoundingMode.HALF_UP) + return percent.value.multiply(BigDecimal.valueOf(255)).divide(BIG_DECIMAL_HUNDRED, 0, RoundingMode.HALF_UP) .intValue(); } @@ -352,4 +284,21 @@ private int convertPercentToByte(PercentType percent) { return defaultConversion(target); } } + + /** + * Helper method for checking if two HSBType colors are close to each other. A maximum deviation is specifid in + * percent. + * + * @param other an HSBType containing the other color. + * @param maxPercentage the maximum allowed difference in percent (range 0.0..1.0). + * @throws IllegalArgumentException if percentage is out of range. + */ + public boolean closeTo(HSBType other, double maxPercentage) throws IllegalArgumentException { + if (maxPercentage <= 0.0 || maxPercentage > 1.0) { + throw new IllegalArgumentException("'maxPercentage' out of bounds, allowed range 0..1"); + } + double[] exp = ColorUtil.hsbToXY(this); + double[] act = ColorUtil.hsbToXY(other); + return ((Math.abs(exp[0] - act[0]) < maxPercentage) && (Math.abs(exp[1] - act[1]) < maxPercentage)); + } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/util/ColorUtil.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/util/ColorUtil.java index 7c1e7386eb8..229e6ddf113 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/util/ColorUtil.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/util/ColorUtil.java @@ -12,23 +12,31 @@ */ package org.openhab.core.util; +import java.math.BigDecimal; +import java.math.RoundingMode; + import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.PercentType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * The {@link ColorUtil} is responsible for converting HSB to CIE + * The {@link ColorUtil} is responsible for converting different color formats. * - * The class is based work from Erik Baauw for the Homebridge - * project + * The implementation of HSB/CIE conversion is based work from Erik Baauw for the + * Homebridge + * project. * * @author Jan N. Klug - Initial contribution + * @author Holger Friedrich - Transfer RGB color conversion from HSBType, improve RGB conversion, restructuring + * @author Chris Jackson - Added fromRGB (moved from HSBType) */ @NonNullByDefault public class ColorUtil { private static final Logger LOGGER = LoggerFactory.getLogger(ColorUtil.class); + protected static final BigDecimal BIG_DECIMAL_HUNDRED = BigDecimal.valueOf(100); public static final Gamut DEFAULT_GAMUT = new Gamut(new double[] { 0.9961, 0.0001 }, new double[] { 0, 0.9961 }, new double[] { 0, 0.0001 }); @@ -37,7 +45,100 @@ private ColorUtil() { } /** - * Transform sRGB based {@link HSBType} to + * Transform HSV based {@link HSBType} to + * sRGB. + * + * This function does rounding to integer valued components. It is the preferred way of doing HSB to RGB conversion. + * + * See also: {@link hsbToRgbPercent(HSBType)}, {@link hsbTosRGB(HSBType)} + */ + public static int[] hsbToRgb(HSBType hsb) { + final PercentType[] rgbPercent = hsbToRgbPercent(hsb); + return new int[] { convertColorPercentToByte(rgbPercent[0]), convertColorPercentToByte(rgbPercent[1]), + convertColorPercentToByte(rgbPercent[2]) }; + } + + /** + * Transform HSV based {@link HSBType} to + * sRGB. + * + * This function not round the components to integer values. Please consider consider + * using {@link hsbToRgb(HSBType)} whenever integer values are required. + * + * See also: {@link hsbToRgb(HSBType)}, {@link hsbTosRgb(HSBType)} + */ + public static PercentType[] hsbToRgbPercent(HSBType hsb) { + PercentType red = null; + PercentType green = null; + PercentType blue = null; + + final BigDecimal h = hsb.getHue().toBigDecimal().divide(BIG_DECIMAL_HUNDRED, 10, RoundingMode.HALF_UP); + final BigDecimal s = hsb.getSaturation().toBigDecimal().divide(BIG_DECIMAL_HUNDRED); + + int hInt = h.multiply(BigDecimal.valueOf(5)).divide(BigDecimal.valueOf(3), 0, RoundingMode.DOWN).intValue(); + final BigDecimal f = h.multiply(BigDecimal.valueOf(5)).divide(BigDecimal.valueOf(3), 10, RoundingMode.HALF_UP) + .remainder(BigDecimal.ONE); + final BigDecimal value = hsb.getBrightness().toBigDecimal(); + PercentType a = new PercentType(value.multiply(BigDecimal.ONE.subtract(s))); + PercentType b = new PercentType(value.multiply(BigDecimal.ONE.subtract(s.multiply(f)))); + PercentType c = new PercentType( + value.multiply(BigDecimal.ONE.subtract((BigDecimal.ONE.subtract(f)).multiply(s)))); + + switch (hInt) { + case 0: + case 6: + red = hsb.getBrightness(); + green = c; + blue = a; + break; + case 1: + red = b; + green = hsb.getBrightness(); + blue = a; + break; + case 2: + red = a; + green = hsb.getBrightness(); + blue = c; + break; + case 3: + red = a; + green = b; + blue = hsb.getBrightness(); + break; + case 4: + red = c; + green = a; + blue = hsb.getBrightness(); + break; + case 5: + red = hsb.getBrightness(); + green = a; + blue = b; + break; + default: + throw new IllegalArgumentException("Could not convert to RGB."); + } + return new PercentType[] { red, green, blue }; + } + + /** + * Transform HSV based {@link HSBType} + * to the RGB value representing the color in the default + * sRGB color model. + * (Bits 24-31 are alpha, 16-23 are red, 8-15 are green, 0-7 are blue). + * + * See also: {@link hsbToRgb(HSBType)}, {@link hsbToRgbPercent(HSBType)} + * + * @return the RGB value of the color in the default sRGB color model + */ + public static int hsbTosRgb(HSBType hsb) { + final int[] rgb = hsbToRgb(hsb); + return (0xFF << 24) | ((rgb[0] & 0xFF) << 16) | ((rgb[1] & 0xFF) << 8) | ((rgb[2] & 0xFF) << 0); + } + + /** + * Transform HSV based {@link HSBType} to * CIE 1931 `xy` format. * * See . * * @param hsbType a {@link HSBType} value - * @return double array with the closest matching CIE 1931 colour, x, y between 0.0000 and 1.0000. + * @return double array with the closest matching CIE 1931 color, x, y between 0.0000 and 1.0000. */ public static double[] hsbToXY(HSBType hsbType) { return hsbToXY(hsbType, DEFAULT_GAMUT); } /** - * Transform sRGB based {@link HSBType} to + * Transform HSV based {@link HSBType} to * CIE 1931 `xy` format. * * See sRGB color format to + * HSV based {@link HSBType}. + * + * Note: Conversion result is rounded and HSBType is created with integer valued components. + * + * @param rgb int array of length 3, all values are constrained to 0-255 + * @return the corresponding {@link HSBType}. + * @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range + */ + public static HSBType rgbToHsb(int[] rgb) throws IllegalArgumentException { + if (rgb.length != 3 || !inByteRange(rgb[0]) || !inByteRange(rgb[1]) || !inByteRange(rgb[2])) { + throw new IllegalArgumentException("RGB array only allows values between 0 and 255"); + } + final int r = rgb[0]; + final int g = rgb[1]; + final int b = rgb[2]; + + int max = (r > g) ? r : g; + if (b > max) { + max = b; + } + int min = (r < g) ? r : g; + if (b < min) { + min = b; + } + float tmpHue; + final float tmpBrightness = max / 2.55f; + final float tmpSaturation = (max != 0 ? ((float) (max - min)) / ((float) max) : 0) * 100.0f; + // smallest possible saturation: 0 (max=0 or max-min=0), other value closest to 0 is 100/255 (max=255, min=254) + // -> avoid float comparision to 0 + // if (tmpSaturation == 0) { + if (max == 0 || (max - min) == 0) { + tmpHue = 0; + } else { + float red = ((float) (max - r)) / ((float) (max - min)); + float green = ((float) (max - g)) / ((float) (max - min)); + float blue = ((float) (max - b)) / ((float) (max - min)); + if (r == max) { + tmpHue = blue - green; + } else if (g == max) { + tmpHue = 2.0f + red - blue; + } else { + tmpHue = 4.0f + green - red; + } + tmpHue = tmpHue / 6.0f * 360; + if (tmpHue < 0) { + tmpHue = tmpHue + 360.0f; + } + } + + // adding 0.5 and casting to int approximates rounding + return new HSBType(new DecimalType((int) (tmpHue + .5f)), new PercentType((int) (tmpSaturation + .5f)), + new PercentType((int) (tmpBrightness + .5f))); + } + /** * Transform CIE 1931 `xy` format to - * sRGB based {@link HSBType}. + * HSV based {@link HSBType}. * * See Hue * developer portal. * - * @param xy the CIE 1931 xy colour, x,y between 0.0000 and 1.0000. + * @param xy the CIE 1931 xy color, x,y between 0.0000 and 1.0000. * @return the corresponding {@link HSBType}. + * @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range */ - public static HSBType xyToHsv(double[] xy) { - return xyToHsv(xy, DEFAULT_GAMUT); + public static HSBType xyToHsb(double[] xy) throws IllegalArgumentException { + return xyToHsb(xy, DEFAULT_GAMUT); } /** * Transform CIE 1931 `xy` format to - * sRGB based {@link HSBType}. + * HSV based {@link HSBType}. * * See Hue * developer portal. * - * @param xy the CIE 1931 xy colour, x,y[,Y] between 0.0000 and 1.0000. Y value is optional. + * @param xy the CIE 1931 xy color, x,y[,Y] between 0.0000 and 1.0000. Y value is optional. * @param gamut the gamut supported by the light. * @return the corresponding {@link HSBType}. * @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range */ - public static HSBType xyToHsv(double[] xy, Gamut gamut) { + public static HSBType xyToHsb(double[] xy, Gamut gamut) throws IllegalArgumentException { if (xy.length < 2 || xy.length > 3 || !inRange(xy[0]) || !inRange(xy[1]) || (xy.length == 3 && !inRange(xy[2]))) { throw new IllegalArgumentException("xy array only allowes two or three values between 0.0 and 1.0."); @@ -156,8 +315,8 @@ public static HSBType xyToHsv(double[] xy, Gamut gamut) { b /= max; } - HSBType hsb = HSBType.fromRGB((int) Math.round(255.0 * r), (int) Math.round(255.0 * g), - (int) Math.round(255.0 * b)); + HSBType hsb = rgbToHsb( + new int[] { (int) Math.round(255.0 * r), (int) Math.round(255.0 * g), (int) Math.round(255.0 * b) }); LOGGER.trace("xy: {} - XYZ: {} {} {} - RGB: {} {} {} - HSB: {} ", xy, X, Y, Z, r, g, b, hsb); return hsb; @@ -295,7 +454,16 @@ public Point closest(Point p) { } } + private static boolean inByteRange(int val) { + return val >= 0 && val <= 255; + } + private static boolean inRange(double val) { return val >= 0.0 && val <= 1.0; } + + private static int convertColorPercentToByte(PercentType percent) { + return percent.toBigDecimal().multiply(BigDecimal.valueOf(255)) + .divide(BIG_DECIMAL_HUNDRED, 0, RoundingMode.HALF_UP).intValue(); + } } diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/HSBTypeTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/HSBTypeTest.java index b90aa505bb8..fc2df6716fa 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/HSBTypeTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/HSBTypeTest.java @@ -46,7 +46,7 @@ public void testFormat() { HSBType hsb = new HSBType("316,69,47"); assertEquals("color 316,69,47", hsb.format("color %hsb%")); - assertEquals("color 119,37,97", hsb.format("color %rgb%")); + assertEquals("color 120,37,98", hsb.format("color %rgb%")); assertEquals("color 316,69,47", hsb.format("color %s")); } @@ -85,8 +85,8 @@ public void testRgbToHsbConversion() { compareRgbToHsbValues("240,100,100", 0, 0, 255); // blue compareRgbToHsbValues("60,60,60", 153, 153, 61); // green compareRgbToHsbValues("300,100,40", 102, 0, 102); - compareRgbToHsbValues("228,37,61", 99, 110, 158); // blueish - compareRgbToHsbValues("316,68,46", 119, 37, 97); // purple + compareRgbToHsbValues("229,37,62", 99, 110, 158); // blueish + compareRgbToHsbValues("316,69,47", 119, 37, 97); // purple } private void compareRgbToHsbValues(String hsbValues, int red, int green, int blue) { @@ -198,4 +198,19 @@ public void testConstructorWithIllegalSaturationValue() { public void testConstructorWithIllegalBrightnessValue() { assertThrows(IllegalArgumentException.class, () -> new HSBType("5,85,151")); } + + @Test + public void testCloseTo() { + HSBType hsb1 = new HSBType("5,85,11"); + HSBType hsb2 = new HSBType("4,84,12"); + HSBType hsb3 = new HSBType("1,8,99"); + + assertThrows(IllegalArgumentException.class, () -> hsb1.closeTo(hsb2, 0.0)); + assertThrows(IllegalArgumentException.class, () -> hsb1.closeTo(hsb2, 1.1)); + assertDoesNotThrow(() -> hsb1.closeTo(hsb2, 0.1)); + + assertTrue(hsb1.closeTo(hsb2, 0.01)); + assertTrue(!hsb1.closeTo(hsb3, 0.01)); + assertTrue(hsb1.closeTo(hsb3, 0.5)); + } } diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/util/ColorUtilTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/util/ColorUtilTest.java index a371464a130..297f9cd4bab 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/util/ColorUtilTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/util/ColorUtilTest.java @@ -16,42 +16,40 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.lessThanOrEqualTo; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.provider.MethodSource; import org.openhab.core.library.types.HSBType; +import org.openhab.core.library.types.PercentType; /** * The {@link ColorUtilTest} is a test class for the color conversion * * @author Jan N. Klug - Initial contribution + * @author Holger Friedrich - Parameterized tests for RGB and HSB conversion */ @NonNullByDefault public class ColorUtilTest { - private static Stream colors() { - return Stream.of(HSBType.BLACK, HSBType.BLUE, HSBType.GREEN, HSBType.RED, HSBType.WHITE, - HSBType.fromRGB(127, 94, 19)).map(Arguments::of); - } - - private static Stream invalids() { - return Stream.of(new double[] { 0.0 }, new double[] { -1.0, 0.5 }, new double[] { 1.5, 0.5 }, - new double[] { 0.5, -1.0 }, new double[] { 0.5, 1.5 }, new double[] { 0.5, 0.5, -1.0 }, - new double[] { 0.5, 0.5, 1.5 }, new double[] { 0.0, 1.0, 0.0, 1.0 }).map(Arguments::of); - } @ParameterizedTest @MethodSource("colors") public void inversionTest(HSBType hsb) { - HSBType hsb2 = ColorUtil.xyToHsv(ColorUtil.hsbToXY(hsb)); + HSBType hsb2 = ColorUtil.xyToHsb(ColorUtil.hsbToXY(hsb)); double deltaHue = Math.abs(hsb.getHue().doubleValue() - hsb2.getHue().doubleValue()); - deltaHue = deltaHue > 180.0 ? Math.abs(deltaHue - 360) : deltaHue; // if deltaHue > 180, the "other direction" - // is shorter + // if deltaHue > 180, the "other direction" is shorter + deltaHue = deltaHue > 180.0 ? Math.abs(deltaHue - 360) : deltaHue; double deltaSat = Math.abs(hsb.getSaturation().doubleValue() - hsb2.getSaturation().doubleValue()); double deltaBri = Math.abs(hsb.getBrightness().doubleValue() - hsb2.getBrightness().doubleValue()); @@ -63,6 +61,146 @@ public void inversionTest(HSBType hsb) { @ParameterizedTest @MethodSource("invalids") public void invalidXyValues(double[] xy) { - assertThrows(IllegalArgumentException.class, () -> ColorUtil.xyToHsv(xy)); + assertThrows(IllegalArgumentException.class, () -> ColorUtil.xyToHsb(xy)); + } + + @Test + public void testConversionToXY() { + HSBType hsb = new HSBType("220,90,50"); + PercentType[] xy = hsb.toXY(); + assertEquals(14.65, xy[0].doubleValue(), 0.01); + assertEquals(11.56, xy[1].doubleValue(), 0.01); + } + + // test RGB -> HSB -> RGB conversion for different values, including the ones known to cause rounding error + @ParameterizedTest + @ArgumentsSource(RgbValueProvider.class) + public void testConversionRgbToHsbToRgb(int[] rgb, int maxSquaredSum) { + HSBType hsb = ColorUtil.rgbToHsb(rgb); + Assertions.assertNotNull(hsb); + + final int[] convertedRgb = ColorUtil.hsbToRgb(hsb); + assertRgbEquals(rgb, convertedRgb, maxSquaredSum); + } + + @ParameterizedTest + @ArgumentsSource(HsbRgbProvider.class) + public void testConversionHsbToRgb(int[] hsb, int[] rgb) { + final String hsbString = hsb[0] + ", " + hsb[1] + ", " + hsb[2]; + final HSBType hsbType = new HSBType(hsbString); + + final int[] converted = ColorUtil.hsbToRgb(hsbType); + assertRgbEquals(rgb, converted, 0); + } + + @ParameterizedTest + @ArgumentsSource(HsbRgbProvider.class) + public void testConversionRgbToRgb(int[] hsb, int[] rgb) { + final HSBType hsbType = ColorUtil.rgbToHsb(rgb); + + final int[] rgbConverted = ColorUtil.hsbToRgb(hsbType); + assertRgbEquals(rgb, rgbConverted, 0); + } + + @ParameterizedTest + @ArgumentsSource(HsbRgbProvider.class) + public void testConversionRgbToHsb(int[] hsb, int[] rgb) { + HSBType hsbType = ColorUtil.rgbToHsb(rgb); + + final String expected = hsb[0] + ", " + hsb[1] + ", " + hsb[2]; + + // compare in HSB space, threshold 1% difference + assertTrue(hsbType.closeTo(new HSBType(expected), 0.01)); + } + + /* Providers for parameterized tests */ + + private static Stream colors() { + return Stream.of(HSBType.BLACK, HSBType.BLUE, HSBType.GREEN, HSBType.RED, HSBType.WHITE, + ColorUtil.rgbToHsb(new int[] { 127, 94, 19 })).map(Arguments::of); + } + + private static Stream invalids() { + return Stream.of(new double[] { 0.0 }, new double[] { -1.0, 0.5 }, new double[] { 1.5, 0.5 }, + new double[] { 0.5, -1.0 }, new double[] { 0.5, 1.5 }, new double[] { 0.5, 0.5, -1.0 }, + new double[] { 0.5, 0.5, 1.5 }, new double[] { 0.0, 1.0, 0.0, 1.0 }).map(Arguments::of); + } + + /* + * return a stream of well known HSB - RGB pairs + */ + static class HsbRgbProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(@Nullable ExtensionContext context) throws Exception { + return Stream.of(Arguments.of(new int[] { 0, 0, 0 }, new int[] { 0, 0, 0 }), + Arguments.of(new int[] { 0, 0, 100 }, new int[] { 255, 255, 255 }), + Arguments.of(new int[] { 0, 100, 100 }, new int[] { 255, 0, 0 }), + Arguments.of(new int[] { 120, 100, 100 }, new int[] { 0, 255, 0 }), + Arguments.of(new int[] { 240, 100, 100 }, new int[] { 0, 0, 255 }), + Arguments.of(new int[] { 60, 100, 100 }, new int[] { 255, 255, 0 }), + Arguments.of(new int[] { 180, 100, 100 }, new int[] { 0, 255, 255 }), + Arguments.of(new int[] { 300, 100, 100 }, new int[] { 255, 0, 255 }), + Arguments.of(new int[] { 0, 0, 75 }, new int[] { 191, 191, 191 }), + Arguments.of(new int[] { 0, 0, 50 }, new int[] { 128, 128, 128 }), + Arguments.of(new int[] { 0, 100, 50 }, new int[] { 128, 0, 0 }), + Arguments.of(new int[] { 60, 100, 50 }, new int[] { 128, 128, 0 }), + Arguments.of(new int[] { 120, 100, 50 }, new int[] { 0, 128, 0 }), + Arguments.of(new int[] { 300, 100, 50 }, new int[] { 128, 0, 128 }), + Arguments.of(new int[] { 180, 100, 50 }, new int[] { 0, 128, 128 }), + Arguments.of(new int[] { 240, 100, 50 }, new int[] { 0, 0, 128 })); + } + } + + /* + * Return a stream RGB values together with allowed deviation (sum of squared differences). + * Differences in conversion are due to rounding errors as HSBType is created with integer numbers. + */ + + static class RgbValueProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(@Nullable ExtensionContext context) throws Exception { + return Stream.of(Arguments.of(new int[] { 0, 0, 0 }, 0), Arguments.of(new int[] { 255, 255, 255 }, 0), + Arguments.of(new int[] { 255, 0, 0 }, 0), Arguments.of(new int[] { 0, 255, 0 }, 0), + Arguments.of(new int[] { 0, 0, 255 }, 0), Arguments.of(new int[] { 255, 255, 0 }, 0), + Arguments.of(new int[] { 255, 0, 255 }, 0), Arguments.of(new int[] { 0, 255, 255 }, 0), + Arguments.of(new int[] { 191, 191, 191 }, 0), Arguments.of(new int[] { 128, 128, 128 }, 0), + Arguments.of(new int[] { 128, 0, 0 }, 0), Arguments.of(new int[] { 128, 128, 0 }, 0), + Arguments.of(new int[] { 0, 128, 0 }, 0), Arguments.of(new int[] { 128, 0, 128 }, 0), + Arguments.of(new int[] { 0, 128, 128 }, 0), Arguments.of(new int[] { 0, 0, 128 }, 0), + Arguments.of(new int[] { 0, 132, 255 }, 0), Arguments.of(new int[] { 1, 131, 254 }, 3), + Arguments.of(new int[] { 2, 130, 253 }, 6), Arguments.of(new int[] { 3, 129, 252 }, 4), + Arguments.of(new int[] { 4, 128, 251 }, 3), Arguments.of(new int[] { 5, 127, 250 }, 0)); + } + } + + /* Helper functions */ + + /** + * Helper method for checking if expected and actual RGB color parameters (int[3], 0..255) lie within a given + * percentage of each other. This method is required in order to eliminate integer rounding artifacts in JUnit tests + * when comparing RGB values. Asserts that the color parameters of expected and actual do not have a squared sum + * of differences which exceeds maxSquaredSum. + * + * When the test fails, both colors are printed. + * + * @param expected an HSBType containing the expected color. + * @param actual an HSBType containing the actual color. + * @param maxSquaredSum the maximum allowed squared sum of differences. + */ + private void assertRgbEquals(final int[] expected, final int[] actual, int maxSquaredSum) { + int squaredSum = 0; + if (expected[0] != actual[0] || expected[1] != actual[1] || expected[2] != actual[2]) { + // only proceed if both RGB colors are not idential + for (int i = 0; i < 3; i++) { + int diff = expected[i] - actual[i]; + squaredSum = squaredSum + diff * diff; + } + if (squaredSum > maxSquaredSum) { + // deviation too high, just prepare readable string compare and let it fail + final String expectedS = expected[0] + ", " + expected[1] + ", " + expected[2]; + final String actualS = actual[0] + ", " + actual[1] + ", " + actual[2]; + assertEquals(expectedS, actualS); + } + } } } From a39beed8dc1c831f6bfb1f8ed741d679802433bb Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Sat, 8 Apr 2023 03:47:01 +1000 Subject: [PATCH 026/126] EventLogger minor refactor (#3523) * EventLogger minor refactor Signed-off-by: Jimmy Tanagra * Fix null check Signed-off-by: Jimmy Tanagra --------- Signed-off-by: Jimmy Tanagra --- .../openhab/core/io/monitor/internal/EventLogger.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.core.io.monitor/src/main/java/org/openhab/core/io/monitor/internal/EventLogger.java b/bundles/org.openhab.core.io.monitor/src/main/java/org/openhab/core/io/monitor/internal/EventLogger.java index a60a2b07bc8..2f857b7ebc2 100644 --- a/bundles/org.openhab.core.io.monitor/src/main/java/org/openhab/core/io/monitor/internal/EventLogger.java +++ b/bundles/org.openhab.core.io.monitor/src/main/java/org/openhab/core/io/monitor/internal/EventLogger.java @@ -14,6 +14,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -73,13 +74,8 @@ public void receive(Event event) { } private Logger getLogger(String eventType) { - String loggerName = "openhab.event." + eventType; - Logger logger = eventLoggers.get(loggerName); - if (logger == null) { - logger = LoggerFactory.getLogger(loggerName); - eventLoggers.put(loggerName, logger); - } - return logger; + return Objects.requireNonNull( + eventLoggers.computeIfAbsent(eventType, type -> LoggerFactory.getLogger("openhab.event." + eventType))); } @Override From 26daabce9c482cd0e53a30ad3e677fbd1652c5b9 Mon Sep 17 00:00:00 2001 From: rimago Date: Sat, 8 Apr 2023 16:43:33 +0200 Subject: [PATCH 027/126] [persistence] fix time weighting in averageSince and averageBetween (#3439) * fix time weighting in averageSince * add getAllStatesBetweenWithBoundaries Signed-off-by: Florian Binder --- .../extensions/PersistenceExtensions.java | 144 +++++++++++----- .../extensions/PersistenceExtensionsTest.java | 157 +++++++++++++----- .../TestCachedValuesPersistenceService.java | 148 +++++++++++++++++ 3 files changed, 365 insertions(+), 84 deletions(-) create mode 100644 bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestCachedValuesPersistenceService.java diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java index 10d3ea86723..b0807792a33 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java @@ -14,8 +14,9 @@ import java.math.BigDecimal; import java.math.MathContext; -import java.time.Instant; +import java.time.Duration; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -449,7 +450,7 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi private static @Nullable HistoricItem internalMaximum(final Item item, ZonedDateTime begin, @Nullable ZonedDateTime end, String serviceId) { - Iterable result = getAllStatesBetween(item, begin, end, serviceId); + Iterable result = getAllStatesBetweenWithBoundaries(item, begin, end, serviceId); Iterator it = result.iterator(); HistoricItem maximumHistoricItem = null; // include current state only if no end time is given @@ -530,7 +531,7 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi private static @Nullable HistoricItem internalMinimum(final Item item, ZonedDateTime begin, @Nullable ZonedDateTime end, String serviceId) { - Iterable result = getAllStatesBetween(item, begin, end, serviceId); + Iterable result = getAllStatesBetweenWithBoundaries(item, begin, end, serviceId); Iterator it = result.iterator(); HistoricItem minimumHistoricItem = null; DecimalType minimum = end == null ? item.getStateAs(DecimalType.class) : null; @@ -610,9 +611,9 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi private static @Nullable DecimalType internalVariance(Item item, ZonedDateTime begin, @Nullable ZonedDateTime end, String serviceId) { - Iterable result = getAllStatesBetween(item, begin, end, serviceId); + Iterable result = getAllStatesBetweenWithBoundaries(item, begin, end, serviceId); Iterator it = result.iterator(); - DecimalType averageSince = internalAverage(item, it, true); + DecimalType averageSince = internalAverage(item, it, end); if (averageSince != null) { BigDecimal average = averageSince.toBigDecimal(), sum = BigDecimal.ZERO; @@ -767,9 +768,7 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * calculation. */ public static @Nullable DecimalType averageSince(Item item, ZonedDateTime timestamp, String serviceId) { - Iterable result = getAllStatesBetween(item, timestamp, null, serviceId); - Iterator it = result.iterator(); - return internalAverage(item, it, true); + return averageBetween(item, timestamp, null, serviceId); } /** @@ -786,54 +785,46 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi */ public static @Nullable DecimalType averageBetween(Item item, ZonedDateTime begin, ZonedDateTime end, String serviceId) { - Iterable result = getAllStatesBetween(item, begin, end, serviceId); + Iterable result = getAllStatesBetweenWithBoundaries(item, begin, end, serviceId); Iterator it = result.iterator(); - return internalAverage(item, it, false); + return internalAverage(item, it, end); } @SuppressWarnings("null") - private static @Nullable DecimalType internalAverage(Item item, Iterator it, boolean includeNow) { - BigDecimal total = BigDecimal.ZERO; + private static @Nullable DecimalType internalAverage(Item item, Iterator it, ZonedDateTime endTime) { + if (endTime == null) { + endTime = ZonedDateTime.now(); + } + + BigDecimal sum = BigDecimal.ZERO; - DecimalType lastState = null, thisState; - BigDecimal firstTimestamp = null, lastTimestamp = null, thisTimestamp = null; + HistoricItem lastItem = null; + ZonedDateTime firstTimestamp = null; while (it.hasNext()) { HistoricItem thisItem = it.next(); - thisState = thisItem.getState().as(DecimalType.class); - if (thisState != null) { - thisTimestamp = BigDecimal.valueOf(thisItem.getTimestamp().toInstant().toEpochMilli()); - if (firstTimestamp == null) { - firstTimestamp = thisTimestamp; - } else { - BigDecimal average = thisState.toBigDecimal().add(lastState.toBigDecimal()).divide(BIG_DECIMAL_TWO, - MathContext.DECIMAL64); - BigDecimal timeSpan = thisTimestamp.subtract(lastTimestamp, MathContext.DECIMAL64); - total = total.add(average.multiply(timeSpan, MathContext.DECIMAL64)); - } - lastTimestamp = thisTimestamp; - lastState = thisState; + if (lastItem != null) { + BigDecimal value = lastItem.getState().as(DecimalType.class).toBigDecimal(); + BigDecimal weight = BigDecimal + .valueOf(Duration.between(lastItem.getTimestamp(), thisItem.getTimestamp()).toMillis()); + sum = sum.add(value.multiply(weight)); } - } - if (lastState != null && includeNow) { - thisState = item.getStateAs(DecimalType.class); - if (thisState != null) { - thisTimestamp = BigDecimal.valueOf(Instant.now().toEpochMilli()); - BigDecimal average = thisState.toBigDecimal().add(lastState.toBigDecimal()).divide(BIG_DECIMAL_TWO, - MathContext.DECIMAL64); - BigDecimal timeSpan = thisTimestamp.subtract(lastTimestamp, MathContext.DECIMAL64); - total = total.add(average.multiply(timeSpan, MathContext.DECIMAL64)); + if (firstTimestamp == null) { + firstTimestamp = thisItem.getTimestamp(); } + lastItem = thisItem; } - if (thisTimestamp != null) { - BigDecimal timeSpan = thisTimestamp.subtract(firstTimestamp, MathContext.DECIMAL64); - // avoid ArithmeticException if timeSpan is zero - if (!BigDecimal.ZERO.equals(timeSpan)) { - BigDecimal average = total.divide(timeSpan, MathContext.DECIMAL64); - return new DecimalType(average); - } + if (lastItem != null) { + BigDecimal value = lastItem.getState().as(DecimalType.class).toBigDecimal(); + BigDecimal weight = BigDecimal.valueOf(Duration.between(lastItem.getTimestamp(), endTime).toMillis()); + sum = sum.add(value.multiply(weight)); + } + + if (firstTimestamp != null) { + BigDecimal totalDuration = BigDecimal.valueOf(Duration.between(firstTimestamp, endTime).toMillis()); + return new DecimalType(sum.divide(totalDuration, MathContext.DECIMAL64)); } return null; @@ -1257,6 +1248,7 @@ private static Iterable getAllStatesBetween(Item item, ZonedDateTi } filter.setItemName(item.getName()); filter.setOrdering(Ordering.ASCENDING); + return qService.query(filter); } else { LoggerFactory.getLogger(PersistenceExtensions.class) @@ -1265,6 +1257,41 @@ private static Iterable getAllStatesBetween(Item item, ZonedDateTi } } + private static Iterable getAllStatesBetweenWithBoundaries(Item item, ZonedDateTime begin, + @Nullable ZonedDateTime end, String serviceId) { + Iterable betweenItems = getAllStatesBetween(item, begin, end, serviceId); + + List betweenItemsList = new ArrayList<>(); + for (HistoricItem historicItem : betweenItems) { + betweenItemsList.add(historicItem); + } + + // add HistoricItem at begin + if (betweenItemsList.isEmpty() || !betweenItemsList.get(0).getTimestamp().equals(begin)) { + if (!begin.isAfter(ZonedDateTime.now())) { + HistoricItem first = historicState(item, begin, serviceId); + + if (first != null) { + betweenItemsList.add(0, new RetimedHistoricItem(first, begin)); + } + } + } + + // add HistoricItem at end + if (end != null && !end.isAfter(ZonedDateTime.now())) { + if (betweenItemsList.isEmpty() + || !betweenItemsList.get(betweenItemsList.size() - 1).getTimestamp().equals(end)) { + HistoricItem last = historicState(item, end, serviceId); + + if (last != null) { + betweenItemsList.add(new RetimedHistoricItem(last, end)); + } + } + } + + return betweenItemsList; + } + private static @Nullable HistoricItem historicItemOrCurrentState(Item item, HistoricItem historicItem, DecimalType value) { if (historicItem == null && value != null) { @@ -1290,4 +1317,35 @@ public String getName() { return historicItem; } } + + private static class RetimedHistoricItem implements HistoricItem { + + private final HistoricItem originItem; + private final ZonedDateTime timestamp; + + public RetimedHistoricItem(HistoricItem originItem, ZonedDateTime timestamp) { + this.originItem = originItem; + this.timestamp = timestamp; + } + + @Override + public ZonedDateTime getTimestamp() { + return timestamp; + } + + @Override + public State getState() { + return originItem.getState(); + } + + @Override + public String getName() { + return originItem.getName(); + } + + @Override + public String toString() { + return "RetimedHistoricItem [originItem=" + originItem + ", timestamp=" + timestamp + "]"; + } + } } diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/PersistenceExtensionsTest.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/PersistenceExtensionsTest.java index c8371f1fff6..6d8ea45d572 100644 --- a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/PersistenceExtensionsTest.java +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/PersistenceExtensionsTest.java @@ -12,17 +12,25 @@ */ package org.openhab.core.persistence.extensions; -import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.closeTo; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; -import java.time.Instant; +import java.time.Duration; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Set; +import java.util.stream.DoubleStream; import java.util.stream.IntStream; import javax.measure.quantity.Temperature; @@ -192,7 +200,7 @@ public void testMaximumSinceDecimalType() { ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); assertNotNull(historicItem); assertThat(historicItem.getState(), is(instanceOf(DecimalType.class))); - assertEquals("1", historicItem.getState().toString()); + assertEquals("2012", historicItem.getState().toString()); historicItem = PersistenceExtensions.maximumSince(numberItem, ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); @@ -229,7 +237,7 @@ public void testMaximumSinceQuantityType() { ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); assertThat(historicItem, is(notNullValue())); assertThat(historicItem.getState(), is(instanceOf(QuantityType.class))); - assertThat(historicItem.getState().toString(), is("1 °C")); + assertThat(historicItem.getState().toString(), is("2012 °C")); historicItem = PersistenceExtensions.maximumSince(quantityItem, ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); @@ -279,7 +287,7 @@ public void testMaximumSinceSwitch() { historicItem = PersistenceExtensions.maximumSince(switchItem, now, TestPersistenceService.ID); assertNotNull(historicItem); - assertEquals(OnOffType.OFF, historicItem.getState()); + assertEquals(OnOffType.ON, historicItem.getState()); historicItem = PersistenceExtensions.maximumSince(switchItem, now.plusHours(1), TestPersistenceService.ID); assertNotNull(historicItem); @@ -393,10 +401,12 @@ public void testVarianceSince() { ZonedDateTime startStored = ZonedDateTime.of(2003, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); ZonedDateTime endStored = ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - long storedInterval = endStored.toInstant().toEpochMilli() - startStored.toInstant().toEpochMilli(); - long recentInterval = Instant.now().toEpochMilli() - endStored.toInstant().toEpochMilli(); - double expectedAverage = (2007.4994 * storedInterval + 2518.5 * recentInterval) + + long storedInterval = Duration.between(startStored, endStored).toDays(); + long recentInterval = Duration.between(endStored, ZonedDateTime.now()).toDays(); + double expectedAverage = ((2003.0 + 2011.0) / 2.0 * storedInterval + 2012.0 * recentInterval) / (storedInterval + recentInterval); + double expected = IntStream.of(2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012) .mapToDouble(i -> Double.parseDouble(Integer.toString(i))).map(d -> Math.pow(d - expectedAverage, 2)) .sum() / 10d; @@ -413,10 +423,14 @@ public void testVarianceSince() { public void testVarianceBetween() { ZonedDateTime startStored = ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); ZonedDateTime endStored = ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + + double expected = DoubleStream.of(2005, 2006, 2007, 2008, 2009, 2010, 2011) + .map(d -> Math.pow(d - (2005.0 + 2010.0) / 2.0, 2)).sum() / 7d; + DecimalType variance = PersistenceExtensions.varianceBetween(numberItem, startStored, endStored, TestPersistenceService.ID); assertThat(variance, is(notNullValue())); - assertThat(variance.doubleValue(), is(closeTo(4, 0.01))); + assertThat(variance.doubleValue(), is(closeTo(expected, 0.01))); // default persistence service variance = PersistenceExtensions.varianceBetween(numberItem, startStored, endStored); @@ -425,17 +439,16 @@ public void testVarianceBetween() { @Test public void testDeviationSinceDecimalType() { - numberItem.setState(new DecimalType(3025)); - ZonedDateTime startStored = ZonedDateTime.of(2003, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); ZonedDateTime endStored = ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - long storedInterval = endStored.toInstant().toEpochMilli() - startStored.toInstant().toEpochMilli(); - long recentInterval = Instant.now().toEpochMilli() - endStored.toInstant().toEpochMilli(); - double expectedAverage = (2007.4994 * storedInterval + 2518.5 * recentInterval) + + long storedInterval = Duration.between(startStored, endStored).toDays(); + long recentInterval = Duration.between(endStored, ZonedDateTime.now()).toDays(); + double expectedAverage = ((2003.0 + 2011.0) / 2.0 * storedInterval + 2012.0 * recentInterval) / (storedInterval + recentInterval); - double expected = Math.sqrt(IntStream.of(2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012) - .mapToDouble(i -> Double.parseDouble(Integer.toString(i))).map(d -> Math.pow(d - expectedAverage, 2)) - .sum() / 10d); + + double expected = Math.sqrt(DoubleStream.of(2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012) + .map(d -> Math.pow(d - expectedAverage, 2)).sum() / 10d); DecimalType deviation = PersistenceExtensions.deviationSince(numberItem, startStored, TestPersistenceService.ID); assertNotNull(deviation); @@ -450,10 +463,14 @@ public void testDeviationSinceDecimalType() { public void testDeviationBetweenDecimalType() { ZonedDateTime startStored = ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); ZonedDateTime endStored = ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + + double expected = Math.sqrt(DoubleStream.of(2005, 2006, 2007, 2008, 2009, 2010, 2011) + .map(d -> Math.pow(d - (2005.0 + 2010.0) / 2.0, 2)).sum() / 7d); + DecimalType deviation = PersistenceExtensions.deviationBetween(numberItem, startStored, endStored, TestPersistenceService.ID); assertThat(deviation, is(notNullValue())); - assertThat(deviation.doubleValue(), is(closeTo(2, 0.01))); + assertThat(deviation.doubleValue(), is(closeTo(expected, 0.01))); // default persistence service deviation = PersistenceExtensions.deviationBetween(numberItem, startStored, endStored); @@ -466,13 +483,15 @@ public void testDeviationSinceQuantityType() { ZonedDateTime startStored = ZonedDateTime.of(2003, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); ZonedDateTime endStored = ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - long storedInterval = endStored.toInstant().toEpochMilli() - startStored.toInstant().toEpochMilli(); - long recentInterval = Instant.now().toEpochMilli() - endStored.toInstant().toEpochMilli(); - double expectedAverage = (2007.4994 * storedInterval + 2518.5 * recentInterval) + + long storedInterval = Duration.between(startStored, endStored).toDays(); + long recentInterval = Duration.between(endStored, ZonedDateTime.now()).toDays(); + double expectedAverage = ((2003.0 + 2011.0) / 2.0 * storedInterval + 2012.0 * recentInterval) / (storedInterval + recentInterval); - double expected = Math.sqrt(IntStream.of(2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012) - .mapToDouble(i -> Double.parseDouble(Integer.toString(i))).map(d -> Math.pow(d - expectedAverage, 2)) - .sum() / 10d); + + double expected = Math.sqrt(DoubleStream.of(2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012) + .map(d -> Math.pow(d - expectedAverage, 2)).sum() / 10d); + DecimalType deviation = PersistenceExtensions.deviationSince(quantityItem, startStored, TestPersistenceService.ID); assertNotNull(deviation); @@ -487,10 +506,14 @@ public void testDeviationSinceQuantityType() { public void testDeviationBetweenQuantityType() { ZonedDateTime startStored = ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); ZonedDateTime endStored = ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + + double expected = Math.sqrt(DoubleStream.of(2005, 2006, 2007, 2008, 2009, 2010, 2011) + .map(d -> Math.pow(d - (2005.0 + 2010.0) / 2.0, 2)).sum() / 7d); + DecimalType deviation = PersistenceExtensions.deviationBetween(quantityItem, startStored, endStored, TestPersistenceService.ID); assertThat(deviation, is(notNullValue())); - assertThat(deviation.doubleValue(), is(closeTo(2, 0.01))); + assertThat(deviation.doubleValue(), is(closeTo(expected, 0.01))); // default persistence service deviation = PersistenceExtensions.deviationBetween(quantityItem, startStored, endStored); @@ -499,17 +522,17 @@ public void testDeviationBetweenQuantityType() { @Test public void testAverageSinceDecimalType() { - numberItem.setState(new DecimalType(3025)); - ZonedDateTime startStored = ZonedDateTime.of(1940, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); DecimalType average = PersistenceExtensions.averageSince(numberItem, startStored, TestPersistenceService.ID); assertNull(average); startStored = ZonedDateTime.of(2003, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - Instant endStored = ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()).toInstant(); - long storedInterval = endStored.toEpochMilli() - startStored.toInstant().toEpochMilli(); - long recentInterval = Instant.now().toEpochMilli() - endStored.toEpochMilli(); - double expected = (2007.4994 * storedInterval + 2518.5 * recentInterval) / (storedInterval + recentInterval); + ZonedDateTime endStored = ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + long storedInterval = Duration.between(startStored, endStored).toDays(); + long recentInterval = Duration.between(endStored, ZonedDateTime.now()).toDays(); + double expected = ((2003.0 + 2011.0) / 2.0 * storedInterval + 2012.0 * recentInterval) + / (storedInterval + recentInterval); + average = PersistenceExtensions.averageSince(numberItem, startStored, TestPersistenceService.ID); assertNotNull(average); assertEquals(expected, average.doubleValue(), 0.01); @@ -519,6 +542,56 @@ public void testAverageSinceDecimalType() { assertNull(average); } + @Test + public void testAverageSinceDecimalTypeIrregularTimespans() { + TestCachedValuesPersistenceService persistenceService = new TestCachedValuesPersistenceService(); + new PersistenceExtensions(new PersistenceServiceRegistry() { + + @Override + public @Nullable String getDefaultId() { + // not available + return null; + } + + @Override + public @Nullable PersistenceService getDefault() { + // not available + return null; + } + + @Override + public Set getAll() { + return Set.of(persistenceService); + } + + @Override + public @Nullable PersistenceService get(@Nullable String serviceId) { + return TestCachedValuesPersistenceService.ID.equals(serviceId) ? persistenceService : null; + } + }); + + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime beginStored = now.minusHours(27); + + persistenceService.addHistoricItem(beginStored, new DecimalType(0), TEST_NUMBER); + persistenceService.addHistoricItem(beginStored.plusHours(1), new DecimalType(100), TEST_NUMBER); + persistenceService.addHistoricItem(beginStored.plusHours(2), new DecimalType(0), TEST_NUMBER); + persistenceService.addHistoricItem(beginStored.plusHours(25), new DecimalType(50), TEST_NUMBER); + persistenceService.addHistoricItem(beginStored.plusHours(26), new DecimalType(0), TEST_NUMBER); + + DecimalType average = PersistenceExtensions.averageSince(numberItem, beginStored, + TestCachedValuesPersistenceService.ID); + assertThat(average.doubleValue(), is(closeTo((100.0 + 50.0) / 27.0, 0.01))); + + average = PersistenceExtensions.averageSince(numberItem, beginStored.plusHours(3), + TestCachedValuesPersistenceService.ID); + assertThat(average.doubleValue(), is(closeTo(50.0 / 24.0, 0.01))); + + average = PersistenceExtensions.averageSince(numberItem, now.minusMinutes(30), + TestCachedValuesPersistenceService.ID); + assertThat(average.doubleValue(), is(closeTo(0, 0.01))); + } + @Test public void testAverageBetweenDecimalType() { ZonedDateTime beginStored = ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); @@ -527,7 +600,7 @@ public void testAverageBetweenDecimalType() { TestPersistenceService.ID); assertThat(average, is(notNullValue())); - assertThat(average.doubleValue(), is(closeTo(2008, 0.01))); + assertThat(average.doubleValue(), is(closeTo((2005.0 + 2010.0) / 2.0, 0.01))); // default persistence service average = PersistenceExtensions.averageBetween(quantityItem, beginStored, endStored); @@ -543,10 +616,12 @@ public void testAverageSinceQuantityType() { assertNull(average); startStored = ZonedDateTime.of(2003, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - Instant endStored = ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()).toInstant(); - long storedInterval = endStored.toEpochMilli() - startStored.toInstant().toEpochMilli(); - long recentInterval = Instant.now().toEpochMilli() - endStored.toEpochMilli(); - double expected = (2007.4994 * storedInterval + 2518.5 * recentInterval) / (storedInterval + recentInterval); + ZonedDateTime endStored = ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + long storedInterval = Duration.between(startStored, endStored).toDays(); + long recentInterval = Duration.between(endStored, ZonedDateTime.now()).toDays(); + double expected = ((2003.0 + 2011.0) / 2.0 * storedInterval + 2012.0 * recentInterval) + / (storedInterval + recentInterval); + average = PersistenceExtensions.averageSince(quantityItem, startStored, TestPersistenceService.ID); assertNotNull(average); assertEquals(expected, average.doubleValue(), 0.01); @@ -564,7 +639,7 @@ public void testAverageBetweenQuantityType() { TestPersistenceService.ID); assertThat(average, is(notNullValue())); - assertThat(average.doubleValue(), is(closeTo(2008, 0.01))); + assertThat(average.doubleValue(), is(closeTo((2005.0 + 2010.0) / 2, 0.01))); // default persistence service average = PersistenceExtensions.averageBetween(quantityItem, beginStored, endStored); @@ -573,17 +648,17 @@ public void testAverageBetweenQuantityType() { @Test public void testAverageSinceSwitch() { - switchItem.setState(OnOffType.ON); + // switch is 5h ON, 6h OFF, and 4h ON (until now) ZonedDateTime now = ZonedDateTime.now().truncatedTo(ChronoUnit.MINUTES); DecimalType average = PersistenceExtensions.averageSince(switchItem, now.minusHours(15), TestPersistenceService.ID); assertThat(average, is(notNullValue())); - assertThat(average.doubleValue(), is(closeTo(0.625, 0.04))); + assertThat(average.doubleValue(), is(closeTo(9.0 / 15.0, 0.01))); average = PersistenceExtensions.averageSince(switchItem, now.minusHours(7), TestPersistenceService.ID); assertThat(average, is(notNullValue())); - assertThat(average.doubleValue(), is(closeTo(0.714, 0.1))); + assertThat(average.doubleValue(), is(closeTo(4.0 / 7.0, 0.01))); average = PersistenceExtensions.averageSince(switchItem, now.minusHours(6), TestPersistenceService.ID); assertThat(average, is(notNullValue())); diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestCachedValuesPersistenceService.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestCachedValuesPersistenceService.java new file mode 100644 index 00000000000..27563dd7b7d --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestCachedValuesPersistenceService.java @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.extensions; + +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.items.Item; +import org.openhab.core.persistence.FilterCriteria; +import org.openhab.core.persistence.FilterCriteria.Ordering; +import org.openhab.core.persistence.HistoricItem; +import org.openhab.core.persistence.PersistenceItemInfo; +import org.openhab.core.persistence.QueryablePersistenceService; +import org.openhab.core.persistence.strategy.PersistenceStrategy; +import org.openhab.core.types.State; + +/** + * A simple persistence service working with cached HistoricItems used for unit tests. + * + * @author Florian Binder - Initial contribution + */ +@NonNullByDefault +public class TestCachedValuesPersistenceService implements QueryablePersistenceService { + + public static final String ID = "testCachedHistoricItems"; + + private final List historicItems = new ArrayList<>(); + + public TestCachedValuesPersistenceService() { + } + + public void addHistoricItem(ZonedDateTime timestamp, State state, String itemName) { + historicItems.add(new CachedHistoricItem(timestamp, state, itemName)); + } + + @Override + public String getId() { + return ID; + } + + @Override + public void store(Item item) { + } + + @Override + public void store(Item item, @Nullable String alias) { + } + + @Override + public Iterable query(FilterCriteria filter) { + Stream stream = historicItems.stream(); + + if (filter.getState() != null) { + throw new UnsupportedOperationException("state filtering is not supported yet"); + } + + if (filter.getItemName() != null) { + stream = stream.filter(hi -> filter.getItemName().equals(hi.getName())); + } + + if (filter.getBeginDate() != null) { + stream = stream.filter(hi -> !filter.getBeginDate().isAfter(hi.getTimestamp())); + } + + if (filter.getEndDate() != null) { + stream = stream.filter(hi -> !filter.getEndDate().isBefore(hi.getTimestamp())); + } + + if (filter.getOrdering() == Ordering.ASCENDING) { + stream = stream.sorted(((o1, o2) -> o1.getTimestamp().compareTo(o2.getTimestamp()))); + } else if (filter.getOrdering() == Ordering.DESCENDING) { + stream = stream.sorted(((o1, o2) -> -o1.getTimestamp().compareTo(o2.getTimestamp()))); + } + + if (filter.getPageNumber() > 0) { + stream = stream.skip(filter.getPageSize() * filter.getPageNumber()); + } + + if (filter.getPageSize() != Integer.MAX_VALUE) { + stream = stream.limit(filter.getPageSize()); + } + + return stream.toList(); + } + + @Override + public Set getItemInfo() { + return Set.of(); + } + + @Override + public String getLabel(@Nullable Locale locale) { + return "Test Label"; + } + + @Override + public List getDefaultStrategies() { + return List.of(); + } + + private static class CachedHistoricItem implements HistoricItem { + private final ZonedDateTime timestamp; + private final State state; + private final String name; + + public CachedHistoricItem(ZonedDateTime timestamp, State state, String name) { + this.timestamp = timestamp; + this.state = state; + this.name = name; + } + + @Override + public ZonedDateTime getTimestamp() { + return timestamp; + } + + @Override + public State getState() { + return state; + } + + @Override + public String getName() { + return name; + } + + @Override + public String toString() { + return "CachedHistoricItem [timestamp=" + timestamp + ", state=" + state + ", name=" + name + "]"; + } + } +} From 643fe49f8ad5fb1c67756650583eb1a936add805 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 9 Apr 2023 11:45:00 +0200 Subject: [PATCH 028/126] Improve log messages in ItemStateConditionHandler (#3535) Signed-off-by: Jan N. Klug --- .../handler/ItemStateConditionHandler.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateConditionHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateConditionHandler.java index fcbbb366c64..aca6f670ef2 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateConditionHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateConditionHandler.java @@ -126,12 +126,13 @@ public boolean isSatisfied(Map inputs) { String state = (String) module.getConfiguration().get(STATE); String operator = (String) module.getConfiguration().get(OPERATOR); if (operator == null || state == null || itemName == null) { - logger.error("Module is not well configured: itemName={} operator={} state = {}", itemName, operator, - state); + logger.error("Module is not well configured: itemName={} operator={} state = {} for rule {}", itemName, + operator, state, ruleUID); return false; } try { - logger.debug("ItemStateCondition '{}' checking if {} {} {}", module.getId(), itemName, operator, state); + logger.debug("ItemStateCondition '{}' checking if {} {} {} for rule {}", module.getId(), itemName, operator, + state, ruleUID); switch (operator) { case "=": return equalsToItemState(itemName, state); @@ -149,7 +150,7 @@ public boolean isSatisfied(Map inputs) { return greaterThanOrEqualsToItemState(itemName, state); } } catch (ItemNotFoundException e) { - logger.error("Item with name {} not found in ItemRegistry.", itemName); + logger.error("Item with name {} not found in ItemRegistry for condition of rule {}.", itemName, ruleUID); } return false; } @@ -170,8 +171,8 @@ private boolean lessThanOrEqualsToItemState(String itemName, String state) throw // state, but warn the user if (!Units.ONE.equals(qtState.getUnit())) { logger.warn( - "Received a QuantityType state '{}' with unit for item {}, but the condition is defined as a plain number without unit ({}), please consider adding a unit to the condition.", - qtState, itemName, state); + "Received a QuantityType state '{}' with unit for item {}, but the condition is defined as a plain number without unit ({}), please consider adding a unit to the condition for rule {}.", + qtState, itemName, state, ruleUID); } return qtState.compareTo( new QuantityType<>(((DecimalType) compareState).toBigDecimal(), qtState.getUnit())) <= 0; @@ -209,8 +210,8 @@ private boolean greaterThanOrEqualsToItemState(String itemName, String state) th // state, but warn the user if (!Units.ONE.equals(qtState.getUnit())) { logger.warn( - "Received a QuantityType state '{}' with unit for item {}, but the condition is defined as a plain number without unit ({}), please consider adding a unit to the condition.", - qtState, itemName, state); + "Received a QuantityType state '{}' with unit for item {}, but the condition is defined as a plain number without unit ({}), please consider adding a unit to the condition for rule {}.", + qtState, itemName, state, ruleUID); } return qtState.compareTo( new QuantityType<>(((DecimalType) compareState).toBigDecimal(), qtState.getUnit())) >= 0; @@ -245,8 +246,8 @@ private boolean equalsToItemState(String itemName, String state) throws ItemNotF } else { // log a warning if the unit of the state differs from ONE logger.warn( - "Received a QuantityType state '{}' with unit for item {}, but the condition is defined as a plain number without unit ({}), comparison will fail unless a unit is added to the condition.", - itemState, itemName, state); + "Received a QuantityType state '{}' with unit for item {}, but the condition is defined as a plain number without unit ({}), comparison will fail unless a unit is added to the condition for rule {}.", + itemState, itemName, state, ruleUID); return false; } } From 953c08c367f010acfa8838274ccd2f9b9084e832 Mon Sep 17 00:00:00 2001 From: lolodomo Date: Wed, 12 Apr 2023 17:40:24 +0200 Subject: [PATCH 029/126] Extend item syntax for category/icon (#3539) The icon value can now contain until 3 segments separated by a semi-column. First segment is the icon source. Example: oh, if, iconify, material, f7, ... Second segment is the icon set. Example: classic Third segment is the icon name (and can contain hyphen). Example: temperature In case only two segments are provided, the first segment is the icon source and the second the icon name. "classic" icon set is assumed if icon source is "oh". In case only one segment is provided, the icon source is assumed to be the openHAB server and its classic icon set and the value is then the icon name. Ability to use a string containing anything has been removed. Related to #3052 Signed-off-by: Laurent Garnier --- .../src/org/openhab/core/model/Items.xtext | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.model.item/src/org/openhab/core/model/Items.xtext b/bundles/org.openhab.core.model.item/src/org/openhab/core/model/Items.xtext index 02ca3871528..ce2d709d1fa 100644 --- a/bundles/org.openhab.core.model.item/src/org/openhab/core/model/Items.xtext +++ b/bundles/org.openhab.core.model.item/src/org/openhab/core/model/Items.xtext @@ -16,7 +16,7 @@ ItemModel: ModelItem: (ModelNormalItem | ModelGroupItem) name=ID (label=STRING)? - ('<' icon=(ID|STRING) '>')? + ('<' icon=Icon '>')? ('(' groups+=ID (',' groups+=ID)* ')')? ('[' tags+=(ID|STRING) (',' tags+=(ID|STRING))* ']')? ('{' bindings+=ModelBinding (',' bindings+=ModelBinding)* '}')? @@ -64,6 +64,10 @@ NUMBER returns ecore::EBigDecimal: ('-')? ID ('.' ID )? ; +Icon: + (ID ':' (ID ':')?)? ID +; + terminal ID: '^'?('a'..'z'|'A'..'Z'|'_'|'0'..'9') ('a'..'z'|'A'..'Z'|'_'|'-'|'0'..'9')*; terminal STRING: From fa37a467acf9454af7c86bcacbeca9ee95cfc0f8 Mon Sep 17 00:00:00 2001 From: lolodomo Date: Wed, 12 Apr 2023 17:42:06 +0200 Subject: [PATCH 030/126] Extend sitemap syntax for icon (#3378) The icon value can now contain until 3 segments separated by a semi-column. First segment is the icon source. Example: oh, if, iconify, material, f7, ... Second segment is the icon set (and can be empty). Example: classic Third segment is the icon name (and can contain hyphen). Example: temperature In case only two segments are provided, the first segment is the icon source and the second the icon name. "classic" icon set is assumed if icon source is "oh". In case only one segment is provided, the icon source is assumed to be the openHAB server and its classic icon set and the value is then the icon name. Ability to surround the value with simple or double quotes is kept for better backward compatibility. Signed-off-by: Laurent Garnier --- .../src/org/openhab/core/model/sitemap/Sitemap.xtext | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/Sitemap.xtext b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/Sitemap.xtext index 4b4376ac0a3..101297bf8bf 100644 --- a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/Sitemap.xtext +++ b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/Sitemap.xtext @@ -156,7 +156,11 @@ GroupItemRef: ID; Icon returns ecore::EString: - STRING | ID; + STRING | (ID ':' (ID ':')?)? IconName; + +// Allow hyphen inside an icon name +IconName: + (ID '-')* ID; ColorArray: ((item=ID)? (condition=('==' | '>' | '<' | '>=' | '<=' | '!='))? (sign=('-' | '+'))? (state=XState) '=')? From fbaf99266622ab42acbc58fe36881a5e82f180d7 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Thu, 13 Apr 2023 05:56:06 +1000 Subject: [PATCH 031/126] Add dynamic scripting-language transformation service (#3487) * Add dynamic scripting language transformation service This replaced SCRIPT transformation with one specific to each language e.g. JS, RB, GROOVY, etc. Co-authored-by: Jan N. Klug Signed-off-by: Jimmy Tanagra --- .../script/ScriptTransformationService.java | 155 +++++++++++------- .../ScriptTransformationServiceFactory.java | 97 +++++++++++ .../internal/ScriptEngineFactoryHelper.java | 8 + .../module/script/profile/ScriptProfile.java | 18 +- .../script/profile/ScriptProfileFactory.java | 54 +++--- .../OH-INF/config/script-profile.xml | 4 - .../OH-INF/i18n/scriptprofile.properties | 2 - .../OH-INF/i18n/scriptprofile_da.properties | 2 - .../OH-INF/i18n/scriptprofile_de.properties | 2 - .../OH-INF/i18n/scriptprofile_fi.properties | 2 - .../OH-INF/i18n/scriptprofile_he.properties | 2 - .../OH-INF/i18n/scriptprofile_it.properties | 2 - .../OH-INF/i18n/scriptprofile_pl.properties | 2 - .../ScriptTransformationServiceTest.java | 67 ++++---- .../script/profile/ScriptProfileTest.java | 86 ++++------ .../internal/TransformationResource.java | 5 +- .../core/transform/TransformationHelper.java | 2 +- .../core/transform/TransformationService.java | 6 +- 18 files changed, 306 insertions(+), 210 deletions(-) create mode 100644 bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationServiceFactory.java diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationService.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationService.java index 38b474ab431..c5f22eb8d81 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationService.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationService.java @@ -12,8 +12,13 @@ */ package org.openhab.core.automation.module.script; +import static org.openhab.core.automation.module.script.profile.ScriptProfileFactory.PROFILE_CONFIG_URI_PREFIX; + import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; @@ -34,11 +39,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.automation.module.script.internal.ScriptEngineFactoryHelper; import org.openhab.core.automation.module.script.profile.ScriptProfile; import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.common.registry.RegistryChangeListener; +import org.openhab.core.config.core.ConfigDescription; +import org.openhab.core.config.core.ConfigDescriptionBuilder; +import org.openhab.core.config.core.ConfigDescriptionProvider; +import org.openhab.core.config.core.ConfigDescriptionRegistry; import org.openhab.core.config.core.ConfigOptionProvider; +import org.openhab.core.config.core.ConfigParser; import org.openhab.core.config.core.ParameterOption; import org.openhab.core.transform.Transformation; import org.openhab.core.transform.TransformationException; @@ -48,8 +57,6 @@ import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,35 +66,50 @@ * * @author Jan N. Klug - Initial contribution */ -@Component(service = { TransformationService.class, ScriptTransformationService.class, - ConfigOptionProvider.class }, property = { "openhab.transform=SCRIPT" }) @NonNullByDefault -public class ScriptTransformationService - implements TransformationService, RegistryChangeListener, ConfigOptionProvider { +@Component(factory = "org.openhab.core.automation.module.script.transformation.factory", service = { + TransformationService.class, ScriptTransformationService.class, ConfigOptionProvider.class, + ConfigDescriptionProvider.class }) +public class ScriptTransformationService implements TransformationService, ConfigOptionProvider, + ConfigDescriptionProvider, RegistryChangeListener { + public static final String SCRIPT_TYPE_PROPERTY_NAME = "openhab.transform.script.scriptType"; public static final String OPENHAB_TRANSFORMATION_SCRIPT = "openhab-transformation-script-"; - private static final String PROFILE_CONFIG_URI = "profile:transform:SCRIPT"; - public static final String SUPPORTED_CONFIGURATION_TYPE = "script"; - private static final Pattern SCRIPT_CONFIG_PATTERN = Pattern - .compile("(?.*?):(?.*?)(\\?(?.*?))?"); + private static final URI CONFIG_DESCRIPTION_TEMPLATE_URI = URI.create(PROFILE_CONFIG_URI_PREFIX + "SCRIPT"); + + private static final Pattern INLINE_SCRIPT_CONFIG_PATTERN = Pattern.compile("\\|(?.+)"); + + private static final Pattern SCRIPT_CONFIG_PATTERN = Pattern.compile("(?.+?)(\\?(?.*?))?"); private final Logger logger = LoggerFactory.getLogger(ScriptTransformationService.class); private final ScheduledExecutorService scheduler = ThreadPoolManager .getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON); + private final String scriptType; + private final URI profileConfigUri; + private final Map scriptCache = new ConcurrentHashMap<>(); private final TransformationRegistry transformationRegistry; - private final Map supportedScriptTypes = new ConcurrentHashMap<>(); - private final ScriptEngineManager scriptEngineManager; + private final ConfigDescriptionRegistry configDescRegistry; @Activate public ScriptTransformationService(@Reference TransformationRegistry transformationRegistry, - @Reference ScriptEngineManager scriptEngineManager) { + @Reference ConfigDescriptionRegistry configDescRegistry, @Reference ScriptEngineManager scriptEngineManager, + Map config) { + String scriptType = ConfigParser.valueAs(config.get(SCRIPT_TYPE_PROPERTY_NAME), String.class); + if (scriptType == null) { + throw new IllegalStateException( + "'" + SCRIPT_TYPE_PROPERTY_NAME + "' must not be null in service configuration"); + } + this.transformationRegistry = transformationRegistry; + this.configDescRegistry = configDescRegistry; this.scriptEngineManager = scriptEngineManager; + this.scriptType = scriptType; + this.profileConfigUri = URI.create(PROFILE_CONFIG_URI_PREFIX + scriptType.toUpperCase()); transformationRegistry.addRegistryChangeListener(this); } @@ -101,28 +123,34 @@ public void deactivate() { @Override public @Nullable String transform(String function, String source) throws TransformationException { - Matcher configMatcher = SCRIPT_CONFIG_PATTERN.matcher(function); - if (!configMatcher.matches()) { - throw new TransformationException("Script Type must be prepended to transformation UID."); + String scriptUid; + String inlineScript = null; + String params = null; + + Matcher configMatcher = INLINE_SCRIPT_CONFIG_PATTERN.matcher(function); + if (configMatcher.matches()) { + inlineScript = configMatcher.group("inlineScript"); + // prefix with | to avoid clashing with a real filename + scriptUid = "|" + Integer.toString(inlineScript.hashCode()); + } else { + configMatcher = SCRIPT_CONFIG_PATTERN.matcher(function); + if (!configMatcher.matches()) { + throw new TransformationException("Invalid syntax for the script transformation: '" + function + "'"); + } + scriptUid = configMatcher.group("scriptUid"); + params = configMatcher.group("params"); } - String scriptType = configMatcher.group("scriptType"); - String scriptUid = configMatcher.group("scriptUid"); ScriptRecord scriptRecord = scriptCache.computeIfAbsent(scriptUid, k -> new ScriptRecord()); scriptRecord.lock.lock(); try { if (scriptRecord.script.isBlank()) { - if (scriptUid.startsWith("|")) { - // inline script -> strip inline-identifier - scriptRecord.script = scriptUid.substring(1); + if (inlineScript != null) { + scriptRecord.script = inlineScript; } else { // get script from transformation registry Transformation transformation = transformationRegistry.get(scriptUid); if (transformation != null) { - if (!SUPPORTED_CONFIGURATION_TYPE.equals(transformation.getType())) { - throw new TransformationException("Configuration does not have correct type 'script' but '" - + transformation.getType() + "'."); - } scriptRecord.script = transformation.getConfiguration().getOrDefault(Transformation.FUNCTION, ""); } @@ -160,7 +188,6 @@ public void deactivate() { ScriptContext executionContext = engine.getContext(); executionContext.setAttribute("input", source, ScriptContext.ENGINE_SCOPE); - String params = configMatcher.group("params"); if (params != null) { for (String param : params.split("&")) { String[] splitString = param.split("="); @@ -169,7 +196,9 @@ public void deactivate() { "Parameter '{}' does not consist of two parts for configuration UID {}, skipping.", param, scriptUid); } else { - executionContext.setAttribute(splitString[0], splitString[1], ScriptContext.ENGINE_SCOPE); + param = URLDecoder.decode(splitString[0], StandardCharsets.UTF_8); + String value = URLDecoder.decode(splitString[1], StandardCharsets.UTF_8); + executionContext.setAttribute(param, value, ScriptContext.ENGINE_SCOPE); } } } @@ -208,6 +237,44 @@ public void updated(Transformation oldElement, Transformation element) { clearCache(element.getUID()); } + @Override + public @Nullable Collection getParameterOptions(URI uri, String param, @Nullable String context, + @Nullable Locale locale) { + if (!uri.equals(profileConfigUri)) { + return null; + } + + if (ScriptProfile.CONFIG_TO_HANDLER_SCRIPT.equals(param) || ScriptProfile.CONFIG_TO_ITEM_SCRIPT.equals(param)) { + return transformationRegistry.getTransformations(List.of(scriptType.toLowerCase())).stream() + .map(c -> new ParameterOption(c.getUID(), c.getLabel())).collect(Collectors.toList()); + } + return null; + } + + @Override + public Collection getConfigDescriptions(@Nullable Locale locale) { + ConfigDescription configDescription = getConfigDescription(profileConfigUri, locale); + if (configDescription != null) { + return List.of(configDescription); + } + + return Collections.emptyList(); + } + + @Override + public @Nullable ConfigDescription getConfigDescription(URI uri, @Nullable Locale locale) { + if (!uri.equals(profileConfigUri)) { + return null; + } + + ConfigDescription template = configDescRegistry.getConfigDescription(CONFIG_DESCRIPTION_TEMPLATE_URI, locale); + if (template == null) { + return null; + } + return ConfigDescriptionBuilder.create(uri).withParameters(template.getParameters()) + .withParameterGroups(template.getParameterGroups()).build(); + } + private void clearCache(String uid) { ScriptRecord scriptRecord = scriptCache.remove(uid); if (scriptRecord != null) { @@ -243,38 +310,6 @@ private void disposeScriptEngine(ScriptEngine scriptEngine) { } } - @Override - public @Nullable Collection getParameterOptions(URI uri, String param, @Nullable String context, - @Nullable Locale locale) { - if (PROFILE_CONFIG_URI.equals(uri.toString())) { - if (ScriptProfile.CONFIG_TO_HANDLER_SCRIPT.equals(param) - || ScriptProfile.CONFIG_TO_ITEM_SCRIPT.equals(param)) { - return transformationRegistry.getTransformations(List.of(SUPPORTED_CONFIGURATION_TYPE)).stream() - .map(c -> new ParameterOption(c.getUID(), c.getLabel())).collect(Collectors.toList()); - } - if (ScriptProfile.CONFIG_SCRIPT_LANGUAGE.equals(param)) { - return supportedScriptTypes.entrySet().stream().map(e -> new ParameterOption(e.getKey(), e.getValue())) - .collect(Collectors.toList()); - } - } - return null; - } - - /** - * As {@link ScriptEngineFactory}s are added/removed, this method will cache all available script types - */ - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void setScriptEngineFactory(ScriptEngineFactory engineFactory) { - Map.Entry parameterOption = ScriptEngineFactoryHelper.getParameterOption(engineFactory); - if (parameterOption != null) { - supportedScriptTypes.put(parameterOption.getKey(), parameterOption.getValue()); - } - } - - public void unsetScriptEngineFactory(ScriptEngineFactory engineFactory) { - supportedScriptTypes.remove(ScriptEngineFactoryHelper.getPreferredMimeType(engineFactory)); - } - private static class ScriptRecord { public String script = ""; public @Nullable ScriptEngineContainer scriptEngineContainer; diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationServiceFactory.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationServiceFactory.java new file mode 100644 index 00000000000..fdcd21adc52 --- /dev/null +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationServiceFactory.java @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.automation.module.script; + +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +import javax.script.ScriptEngine; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.automation.module.script.internal.ScriptEngineFactoryHelper; +import org.openhab.core.transform.TransformationService; +import org.osgi.service.component.ComponentFactory; +import org.osgi.service.component.ComponentInstance; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; + +/** + * The {@link ScriptTransformationServiceFactory} registers a {@link ScriptTransformationService} + * for each newly added script engine. + * + * @author Jimmy Tanagra - Initial contribution + */ +@Component(immediate = true, service = { ScriptTransformationServiceFactory.class }) +@NonNullByDefault +public class ScriptTransformationServiceFactory { + + private final ComponentFactory scriptTransformationFactory; + + private final Map> scriptTransformations = new ConcurrentHashMap<>(); + + @Activate + public ScriptTransformationServiceFactory( + @Reference(target = "(component.factory=org.openhab.core.automation.module.script.transformation.factory)") ComponentFactory factory) { + this.scriptTransformationFactory = factory; + } + + @Deactivate + public void deactivate() { + scriptTransformations.values().forEach(this::unregisterService); + scriptTransformations.clear(); + } + + /** + * As {@link ScriptEngineFactory}s are added/removed, this method will cache all available script types + * and registers a transformation service for the script engine. + */ + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void setScriptEngineFactory(ScriptEngineFactory engineFactory) { + Optional scriptType = ScriptEngineFactoryHelper.getPreferredExtension(engineFactory); + if (scriptType.isEmpty()) { + return; + } + + scriptTransformations.computeIfAbsent(engineFactory, factory -> { + ScriptEngine scriptEngine = engineFactory.createScriptEngine(scriptType.get()); + if (scriptEngine == null) { + return null; + } + String languageName = ScriptEngineFactoryHelper.getLanguageName(scriptEngine.getFactory()); + Dictionary properties = new Hashtable<>(); + properties.put(TransformationService.SERVICE_PROPERTY_NAME, scriptType.get().toUpperCase()); + properties.put(TransformationService.SERVICE_PROPERTY_LABEL, "SCRIPT " + languageName); + properties.put(ScriptTransformationService.SCRIPT_TYPE_PROPERTY_NAME, scriptType.get()); + return scriptTransformationFactory.newInstance(properties); + }); + } + + public void unsetScriptEngineFactory(ScriptEngineFactory engineFactory) { + ComponentInstance toBeUnregistered = scriptTransformations.remove(engineFactory); + if (toBeUnregistered != null) { + unregisterService(toBeUnregistered); + } + } + + private void unregisterService(ComponentInstance instance) { + instance.getInstance().deactivate(); + instance.dispose(); + } +} diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryHelper.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryHelper.java index 12ffaeb4c93..6acbfeac2d0 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryHelper.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryHelper.java @@ -13,8 +13,10 @@ package org.openhab.core.automation.module.script.internal; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Optional; import javax.script.ScriptEngine; @@ -67,4 +69,10 @@ public static String getLanguageName(javax.script.ScriptEngineFactory factory) { factory.getLanguageName().substring(0, 1).toUpperCase() + factory.getLanguageName().substring(1), factory.getLanguageVersion()); } + + public static Optional getPreferredExtension(ScriptEngineFactory factory) { + // return an Optional because GenericScriptEngineFactory has no scriptTypes + return factory.getScriptTypes().stream().filter(type -> !type.contains("/")) + .min(Comparator.comparing(String::length)); + } } diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfile.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfile.java index 68d405b021e..cb2634afc67 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfile.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfile.java @@ -41,7 +41,6 @@ @NonNullByDefault public class ScriptProfile implements StateProfile { - public static final String CONFIG_SCRIPT_LANGUAGE = "scriptLanguage"; public static final String CONFIG_TO_ITEM_SCRIPT = "toItemScript"; public static final String CONFIG_TO_HANDLER_SCRIPT = "toHandlerScript"; @@ -54,14 +53,15 @@ public class ScriptProfile implements StateProfile { private final List> acceptedCommandTypes; private final List> handlerAcceptedCommandTypes; - private final String scriptLanguage; private final String toItemScript; private final String toHandlerScript; + private final ProfileTypeUID profileTypeUID; private final boolean isConfigured; - public ScriptProfile(ProfileCallback callback, ProfileContext profileContext, + public ScriptProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback, ProfileContext profileContext, TransformationService transformationService) { + this.profileTypeUID = profileTypeUID; this.callback = callback; this.transformationService = transformationService; @@ -69,19 +69,11 @@ public ScriptProfile(ProfileCallback callback, ProfileContext profileContext, this.acceptedDataTypes = profileContext.getAcceptedDataTypes(); this.handlerAcceptedCommandTypes = profileContext.getHandlerAcceptedCommandTypes(); - this.scriptLanguage = ConfigParser.valueAsOrElse(profileContext.getConfiguration().get(CONFIG_SCRIPT_LANGUAGE), - String.class, ""); this.toItemScript = ConfigParser.valueAsOrElse(profileContext.getConfiguration().get(CONFIG_TO_ITEM_SCRIPT), String.class, ""); this.toHandlerScript = ConfigParser .valueAsOrElse(profileContext.getConfiguration().get(CONFIG_TO_HANDLER_SCRIPT), String.class, ""); - if (scriptLanguage.isBlank()) { - logger.error("Script language is not defined. Profile will discard all states and commands."); - isConfigured = false; - return; - } - if (toItemScript.isBlank() && toHandlerScript.isBlank()) { logger.error( "Neither 'toItem' nor 'toHandler' script defined. Profile will discard all states and commands."); @@ -94,7 +86,7 @@ public ScriptProfile(ProfileCallback callback, ProfileContext profileContext, @Override public ProfileTypeUID getProfileTypeUID() { - return ScriptProfileFactory.SCRIPT_PROFILE_UID; + return profileTypeUID; } @Override @@ -149,7 +141,7 @@ public void onStateUpdateFromHandler(State state) { private @Nullable String executeScript(String script, Type input) { if (!script.isBlank()) { try { - return transformationService.transform(scriptLanguage + ":" + script, input.toFullString()); + return transformationService.transform(script, input.toFullString()); } catch (TransformationException e) { if (e.getCause() instanceof ScriptException) { logger.error("Failed to process script '{}': {}", script, e.getCause().getMessage()); diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfileFactory.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfileFactory.java index adbf5c9710f..6e46afa5f3a 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfileFactory.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfileFactory.java @@ -14,7 +14,9 @@ import java.util.Collection; import java.util.Locale; -import java.util.Set; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -28,48 +30,58 @@ import org.openhab.core.thing.profiles.ProfileTypeProvider; import org.openhab.core.thing.profiles.ProfileTypeUID; import org.openhab.core.transform.TransformationService; -import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; /** * The {@link ScriptProfileFactory} creates {@link ScriptProfile} instances * * @author Jan N. Klug - Initial contribution */ -@Component(service = { ScriptProfileFactory.class, ProfileFactory.class, ProfileTypeProvider.class }) @NonNullByDefault +@Component(service = { ProfileFactory.class, ProfileTypeProvider.class }) public class ScriptProfileFactory implements ProfileFactory, ProfileTypeProvider { + public static final String PROFILE_CONFIG_URI_PREFIX = "profile:transform:"; - public static final ProfileTypeUID SCRIPT_PROFILE_UID = new ProfileTypeUID( - TransformationService.TRANSFORM_PROFILE_SCOPE, "SCRIPT"); - - private static final ProfileType PROFILE_TYPE_SCRIPT = ProfileTypeBuilder.newState(SCRIPT_PROFILE_UID, "Script") - .build(); - - private final ScriptTransformationService transformationService; - - @Activate - public ScriptProfileFactory(final @Reference ScriptTransformationService transformationService) { - this.transformationService = transformationService; - } + private final Map services = new ConcurrentHashMap<>(); @Override public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback, ProfileContext profileContext) { - if (SCRIPT_PROFILE_UID.equals(profileTypeUID)) { - return new ScriptProfile(callback, profileContext, transformationService); - } - return null; + String serviceId = profileTypeUID.getId(); + ScriptTransformationService transformationService = services.get(serviceId).service(); + return new ScriptProfile(profileTypeUID, callback, profileContext, transformationService); } @Override public Collection getSupportedProfileTypeUIDs() { - return Set.of(SCRIPT_PROFILE_UID); + return services.keySet().stream() + .map(id -> new ProfileTypeUID(TransformationService.TRANSFORM_PROFILE_SCOPE, id)).toList(); } @Override public Collection getProfileTypes(@Nullable Locale locale) { - return Set.of(PROFILE_TYPE_SCRIPT); + return getSupportedProfileTypeUIDs().stream().map(uid -> { + String id = uid.getId(); + String label = services.get(id).serviceLabel(); + return ProfileTypeBuilder.newState(uid, label).build(); + }).collect(Collectors.toList()); + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void bindScriptTransformationService(ScriptTransformationService service, Map properties) { + String serviceId = (String) properties.get(TransformationService.SERVICE_PROPERTY_NAME); + String serviceLabel = (String) properties.get(TransformationService.SERVICE_PROPERTY_LABEL); + services.put(serviceId, new ServiceRecord(service, serviceLabel)); + } + + public void unbindScriptTransformationService(ScriptTransformationService service, Map properties) { + String serviceId = (String) properties.get(TransformationService.SERVICE_PROPERTY_NAME); + services.remove(serviceId); + } + + private record ServiceRecord(ScriptTransformationService service, String serviceLabel) { } } diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/config/script-profile.xml b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/config/script-profile.xml index cbbfad5bb02..6e1670c5295 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/config/script-profile.xml +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/config/script-profile.xml @@ -5,10 +5,6 @@ xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd"> - - - MIME-type ("application/vnd.openhab.dsl.rule") of the scripting language - The Script for transforming state updates and commands from the Thing handler to the item. The script diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile.properties index ba168b38021..8c0323b52b6 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile.properties @@ -1,5 +1,3 @@ -profile.system.script.scriptLanguage.label = Script Language -profile.system.script.scriptLanguage.description = MIME-type ("application/vnd.openhab.dsl.rule") of the scripting language profile.system.script.toItemScript.label = Thing To Item Transformation profile.system.script.toItemScript.description = The Script for transforming state updates and commands from the Thing handler to the item. The script may return null to discard the updates/commands and not pass them through. profile.system.script.toHandlerScript.label = Item To Thing Transformation diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_da.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_da.properties index 6e1e3e64cc6..b484a6da08d 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_da.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_da.properties @@ -1,5 +1,3 @@ -profile.system.script.scriptLanguage.label = Script-sprog -profile.system.script.scriptLanguage.description = MIME-type ("application/vnd.openhab.dsl.rule") for script-sproget profile.system.script.toItemScript.label = Til item-script profile.system.script.toItemScript.description = Scriptet til at transformere tilstande og kommandoer fra handler til item. profile.system.script.toHandlerScript.label = Til handler-script diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_de.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_de.properties index a645a911802..fdd90001389 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_de.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_de.properties @@ -1,5 +1,3 @@ -profile.system.script.scriptLanguage.label = Skriptsprache -profile.system.script.scriptLanguage.description = MIME-Typ ("application/vnd.openhab.dsl.rule") der Skriptsprache. profile.system.script.toItemScript.label = Transformation Thing -> Item profile.system.script.toItemScript.description = Das Skript für die Transformtion von States und Commands vom Thing zum Item. profile.system.script.toHandlerScript.label = Transformation Item -> Thing diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_fi.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_fi.properties index b7ee809f7a0..454984afb8d 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_fi.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_fi.properties @@ -1,5 +1,3 @@ -profile.system.script.scriptLanguage.label = Skriptin kieli -profile.system.script.scriptLanguage.description = Skriptin kielen MIME-tyyppi ("application/vnd.openhab.dsl.rule") profile.system.script.toItemScript.label = Item-skriptiksi profile.system.script.toItemScript.description = Skripti, jota käytetään tilojen ja komentojen muuntoon käsittelijästä itemiksi. profile.system.script.toHandlerScript.label = Käsittelijäskriptiksi diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_he.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_he.properties index 3274b2e7c51..79b93afbb94 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_he.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_he.properties @@ -1,5 +1,3 @@ -profile.system.script.scriptLanguage.label = שפת תסריט -profile.system.script.scriptLanguage.description = סוג MIME ("application/vnd.openhab.dsl.rule") של שפת הסקריפט profile.system.script.toItemScript.label = המרת Thing לפריט profile.system.script.toItemScript.description = הסקריפט להפיכת עדכוני מצב ופקודות מהמטפל ב-Thing לפריט. הסקריפט עשוי לחזור null כדי למחוק את העדכונים/פקודות ולא להעביר אותם. profile.system.script.toHandlerScript.label = שינוי פריט ל-thing diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_it.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_it.properties index ba5bdca5c3b..810fcea2836 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_it.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_it.properties @@ -1,5 +1,3 @@ -profile.system.script.scriptLanguage.label = Linguaggio Script -profile.system.script.scriptLanguage.description = Tipo MIME ("application/vnd.openhab.dsl.rule") del linguaggio di scripting profile.system.script.toItemScript.label = Trasformazione da Thing a Item profile.system.script.toItemScript.description = Lo script per trasformare gli aggiornamenti dello stato e i comandi dal gestore Thing all'Item. Lo script può restituire null per scartare gli aggiornamenti/comandi e non passarli. profile.system.script.toHandlerScript.label = Trasformazione da Item a Thing diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_pl.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_pl.properties index 77de1604dd5..7320cb082ce 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_pl.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_pl.properties @@ -1,5 +1,3 @@ -profile.system.script.scriptLanguage.label = Język Skryptu -profile.system.script.scriptLanguage.description = MIME-Typ języka skryptu ("application/vnd.openhab.dsl.rule") profile.system.script.toItemScript.label = Skrypt transformacji z kanału do elementu profile.system.script.toItemScript.description = Skrypt do transformacji stanu lub wartości z kanału do elementu. Skrypt może zwrócić stan lub wartość *null* aby pominąć transformację i nie przekazać jej do elementu. profile.system.script.toHandlerScript.label = Skrypt transformacji z elementu do kanału diff --git a/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/ScriptTransformationServiceTest.java b/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/ScriptTransformationServiceTest.java index 832b444dc27..b73010e6aa8 100644 --- a/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/ScriptTransformationServiceTest.java +++ b/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/ScriptTransformationServiceTest.java @@ -19,6 +19,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -36,6 +37,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.openhab.core.config.core.ConfigDescriptionRegistry; import org.openhab.core.transform.Transformation; import org.openhab.core.transform.TransformationException; import org.openhab.core.transform.TransformationRegistry; @@ -50,7 +52,7 @@ @MockitoSettings(strictness = Strictness.LENIENT) public class ScriptTransformationServiceTest { private static final String SCRIPT_LANGUAGE = "customDsl"; - private static final String SCRIPT_UID = "scriptUid"; + private static final String SCRIPT_UID = "scriptUid." + SCRIPT_LANGUAGE; private static final String INVALID_SCRIPT_UID = "invalidScriptUid"; private static final String INLINE_SCRIPT = "|inlineScript"; @@ -59,7 +61,7 @@ public class ScriptTransformationServiceTest { private static final String SCRIPT_OUTPUT = "output"; private static final Transformation TRANSFORMATION_CONFIGURATION = new Transformation(SCRIPT_UID, "label", - ScriptTransformationService.SUPPORTED_CONFIGURATION_TYPE, Map.of(Transformation.FUNCTION, SCRIPT)); + SCRIPT_LANGUAGE, Map.of(Transformation.FUNCTION, SCRIPT)); private static final Transformation INVALID_TRANSFORMATION_CONFIGURATION = new Transformation(INVALID_SCRIPT_UID, "label", "invalid", Map.of(Transformation.FUNCTION, SCRIPT)); @@ -73,7 +75,10 @@ public class ScriptTransformationServiceTest { @BeforeEach public void setUp() throws ScriptException { - service = new ScriptTransformationService(transformationRegistry, scriptEngineManager); + Map properties = new HashMap<>(); + properties.put(ScriptTransformationService.SCRIPT_TYPE_PROPERTY_NAME, SCRIPT_LANGUAGE); + service = new ScriptTransformationService(transformationRegistry, mock(ConfigDescriptionRegistry.class), + scriptEngineManager, properties); when(scriptEngineManager.createScriptEngine(eq(SCRIPT_LANGUAGE), any())).thenReturn(scriptEngineContainer); when(scriptEngineManager.isSupported(anyString())) @@ -96,14 +101,14 @@ public void setUp() throws ScriptException { @Test public void success() throws TransformationException { - String returnValue = Objects.requireNonNull(service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input")); + String returnValue = Objects.requireNonNull(service.transform(SCRIPT_UID, "input")); assertThat(returnValue, is(SCRIPT_OUTPUT)); } @Test public void scriptExecutionParametersAreInjectedIntoEngineContext() throws TransformationException { - service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID + "?param1=value1¶m2=value2", "input"); + service.transform(SCRIPT_UID + "?param1=value1¶m2=value2", "input"); verify(scriptContext).setAttribute(eq("input"), eq("input"), eq(ScriptContext.ENGINE_SCOPE)); verify(scriptContext).setAttribute(eq("param1"), eq("value1"), eq(ScriptContext.ENGINE_SCOPE)); @@ -111,6 +116,16 @@ public void scriptExecutionParametersAreInjectedIntoEngineContext() throws Trans verifyNoMoreInteractions(scriptContext); } + @Test + public void scriptExecutionParametersAreDecoded() throws TransformationException { + service.transform(SCRIPT_UID + "?param1=%26amp;¶m2=%3dvalue", "input"); + + verify(scriptContext).setAttribute(eq("input"), eq("input"), eq(ScriptContext.ENGINE_SCOPE)); + verify(scriptContext).setAttribute(eq("param1"), eq("&"), eq(ScriptContext.ENGINE_SCOPE)); + verify(scriptContext).setAttribute(eq("param2"), eq("=value"), eq(ScriptContext.ENGINE_SCOPE)); + verifyNoMoreInteractions(scriptContext); + } + @Test public void scriptSetAttributesBeforeCompiling() throws TransformationException, ScriptException { abstract class CompilableScriptEngine implements ScriptEngine, Compilable { @@ -122,7 +137,7 @@ abstract class CompilableScriptEngine implements ScriptEngine, Compilable { InOrder inOrder = inOrder(scriptContext, scriptEngine); - service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID + "?param1=value1", "input"); + service.transform(SCRIPT_UID + "?param1=value1", "input"); inOrder.verify(scriptContext, times(2)).setAttribute(anyString(), anyString(), eq(ScriptContext.ENGINE_SCOPE)); inOrder.verify((Compilable) scriptEngine).compile(SCRIPT); @@ -132,7 +147,7 @@ abstract class CompilableScriptEngine implements ScriptEngine, Compilable { @Test public void invalidScriptExecutionParametersAreDiscarded() throws TransformationException { - service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID + "?param1=value1&invalid", "input"); + service.transform(SCRIPT_UID + "?param1=value1&invalid", "input"); verify(scriptContext).setAttribute(eq("input"), eq("input"), eq(ScriptContext.ENGINE_SCOPE)); verify(scriptContext).setAttribute(eq("param1"), eq("value1"), eq(ScriptContext.ENGINE_SCOPE)); @@ -141,41 +156,25 @@ public void invalidScriptExecutionParametersAreDiscarded() throws Transformation @Test public void scriptsAreCached() throws TransformationException { - service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input"); - service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input"); + service.transform(SCRIPT_UID, "input"); + service.transform(SCRIPT_UID, "input"); verify(transformationRegistry).get(SCRIPT_UID); } @Test public void scriptCacheInvalidatedAfterChange() throws TransformationException { - service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input"); + service.transform(SCRIPT_UID, "input"); service.updated(TRANSFORMATION_CONFIGURATION, TRANSFORMATION_CONFIGURATION); - service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input"); + service.transform(SCRIPT_UID, "input"); verify(transformationRegistry, times(2)).get(SCRIPT_UID); } - @Test - public void noScriptTypeThrowsException() { - TransformationException e = assertThrows(TransformationException.class, - () -> service.transform(SCRIPT_UID, "input")); - - assertThat(e.getMessage(), is("Script Type must be prepended to transformation UID.")); - } - - @Test - public void unknownScriptTypeThrowsException() { - TransformationException e = assertThrows(TransformationException.class, - () -> service.transform("foo" + ":" + SCRIPT_UID, "input")); - - assertThat(e.getMessage(), is("Script type 'foo' is not supported by any available script engine.")); - } - @Test public void unknownScriptUidThrowsException() { TransformationException e = assertThrows(TransformationException.class, - () -> service.transform(SCRIPT_LANGUAGE + ":" + "foo", "input")); + () -> service.transform("foo", "input")); assertThat(e.getMessage(), is("Could not get script for UID 'foo'.")); } @@ -185,24 +184,16 @@ public void scriptExceptionResultsInTransformationException() throws ScriptExcep when(scriptEngine.eval(SCRIPT)).thenThrow(new ScriptException("exception")); TransformationException e = assertThrows(TransformationException.class, - () -> service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input")); + () -> service.transform(SCRIPT_UID, "input")); assertThat(e.getMessage(), is("Failed to execute script.")); assertThat(e.getCause(), instanceOf(ScriptException.class)); assertThat(e.getCause().getMessage(), is("exception")); } - @Test - public void invalidConfigurationTypeThrowsTransformationException() { - TransformationException e = assertThrows(TransformationException.class, - () -> service.transform(SCRIPT_LANGUAGE + ":" + INVALID_SCRIPT_UID, "input")); - - assertThat(e.getMessage(), is("Configuration does not have correct type 'script' but 'invalid'.")); - } - @Test public void inlineScriptProperlyProcessed() throws TransformationException, ScriptException { - service.transform(SCRIPT_LANGUAGE + ":" + INLINE_SCRIPT, "input"); + service.transform(INLINE_SCRIPT, "input"); verify(scriptEngine).eval(INLINE_SCRIPT.substring(1)); } diff --git a/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/profile/ScriptProfileTest.java b/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/profile/ScriptProfileTest.java index 8b26a0c0ed2..11111ec4499 100644 --- a/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/profile/ScriptProfileTest.java +++ b/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/profile/ScriptProfileTest.java @@ -13,11 +13,11 @@ package org.openhab.core.automation.module.script.profile; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.openhab.core.automation.module.script.profile.ScriptProfile.CONFIG_SCRIPT_LANGUAGE; import static org.openhab.core.automation.module.script.profile.ScriptProfile.CONFIG_TO_HANDLER_SCRIPT; import static org.openhab.core.automation.module.script.profile.ScriptProfile.CONFIG_TO_ITEM_SCRIPT; @@ -42,6 +42,7 @@ import org.openhab.core.test.java.JavaTest; import org.openhab.core.thing.profiles.ProfileCallback; import org.openhab.core.thing.profiles.ProfileContext; +import org.openhab.core.thing.profiles.ProfileTypeUID; import org.openhab.core.transform.TransformationException; import org.openhab.core.transform.TransformationService; import org.openhab.core.types.Command; @@ -67,11 +68,12 @@ public void setUp() throws TransformationException { @Test public void testScriptNotExecutedAndNoValueForwardedToCallbackIfNoScriptDefined() throws TransformationException { - ProfileContext profileContext = ProfileContextBuilder.create().withScriptLanguage("customDSL").build(); + ProfileContext profileContext = ProfileContextBuilder.create().build(); setupInterceptedLogger(ScriptProfile.class, LogLevel.ERROR); - ScriptProfile scriptProfile = new ScriptProfile(profileCallback, profileContext, transformationServiceMock); + ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext, + transformationServiceMock); scriptProfile.onCommandFromHandler(OnOffType.ON); scriptProfile.onStateUpdateFromHandler(OnOffType.ON); @@ -86,40 +88,16 @@ public void testScriptNotExecutedAndNoValueForwardedToCallbackIfNoScriptDefined( "Neither 'toItem' nor 'toHandler' script defined. Profile will discard all states and commands."); } - @Test - public void testScriptNotExecutedAndNoValueForwardedToCallbackIfNoScriptLanguageDefined() - throws TransformationException { - ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript") - .withToHandlerScript("outScript").withAcceptedCommandTypes(List.of(DecimalType.class)) - .withAcceptedDataTypes(List.of(PercentType.class)) - .withHandlerAcceptedCommandTypes(List.of(HSBType.class)).build(); - - setupInterceptedLogger(ScriptProfile.class, LogLevel.ERROR); - - ScriptProfile scriptProfile = new ScriptProfile(profileCallback, profileContext, transformationServiceMock); - - scriptProfile.onCommandFromHandler(OnOffType.ON); - scriptProfile.onStateUpdateFromHandler(OnOffType.ON); - scriptProfile.onCommandFromItem(OnOffType.ON); - - verify(transformationServiceMock, never()).transform(any(), any()); - verify(profileCallback, never()).handleCommand(any()); - verify(profileCallback, never()).sendUpdate(any()); - verify(profileCallback, never()).sendCommand(any()); - - assertLogMessage(ScriptProfile.class, LogLevel.ERROR, - "Script language is not defined. Profile will discard all states and commands."); - } - @Test public void scriptExecutionErrorForwardsNoValueToCallback() throws TransformationException { - ProfileContext profileContext = ProfileContextBuilder.create().withScriptLanguage("customDSL") - .withToItemScript("inScript").withToHandlerScript("outScript").build(); + ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript") + .withToHandlerScript("outScript").build(); when(transformationServiceMock.transform(any(), any())) .thenThrow(new TransformationException("intentional failure")); - ScriptProfile scriptProfile = new ScriptProfile(profileCallback, profileContext, transformationServiceMock); + ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext, + transformationServiceMock); scriptProfile.onCommandFromHandler(OnOffType.ON); scriptProfile.onStateUpdateFromHandler(OnOffType.ON); @@ -133,12 +111,13 @@ public void scriptExecutionErrorForwardsNoValueToCallback() throws Transformatio @Test public void scriptExecutionResultNullForwardsNoValueToCallback() throws TransformationException { - ProfileContext profileContext = ProfileContextBuilder.create().withScriptLanguage("customDSL") - .withToItemScript("inScript").withToHandlerScript("outScript").build(); + ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript") + .withToHandlerScript("outScript").build(); when(transformationServiceMock.transform(any(), any())).thenReturn(null); - ScriptProfile scriptProfile = new ScriptProfile(profileCallback, profileContext, transformationServiceMock); + ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext, + transformationServiceMock); scriptProfile.onCommandFromHandler(OnOffType.ON); scriptProfile.onStateUpdateFromHandler(OnOffType.ON); @@ -152,14 +131,15 @@ public void scriptExecutionResultNullForwardsNoValueToCallback() throws Transfor @Test public void scriptExecutionResultForwardsTransformedValueToCallback() throws TransformationException { - ProfileContext profileContext = ProfileContextBuilder.create().withScriptLanguage("customDSL") - .withToItemScript("inScript").withToHandlerScript("outScript") - .withAcceptedCommandTypes(List.of(OnOffType.class)).withAcceptedDataTypes(List.of(OnOffType.class)) + ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript") + .withToHandlerScript("outScript").withAcceptedCommandTypes(List.of(OnOffType.class)) + .withAcceptedDataTypes(List.of(OnOffType.class)) .withHandlerAcceptedCommandTypes(List.of(OnOffType.class)).build(); when(transformationServiceMock.transform(any(), any())).thenReturn(OnOffType.OFF.toString()); - ScriptProfile scriptProfile = new ScriptProfile(profileCallback, profileContext, transformationServiceMock); + ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext, + transformationServiceMock); scriptProfile.onCommandFromHandler(DecimalType.ZERO); scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO); @@ -173,14 +153,14 @@ public void scriptExecutionResultForwardsTransformedValueToCallback() throws Tra @Test public void onlyToItemScriptDoesNotForwardOutboundCommands() throws TransformationException { - ProfileContext profileContext = ProfileContextBuilder.create().withScriptLanguage("customDSL") - .withToItemScript("inScript").withAcceptedCommandTypes(List.of(OnOffType.class)) - .withAcceptedDataTypes(List.of(OnOffType.class)) + ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript") + .withAcceptedCommandTypes(List.of(OnOffType.class)).withAcceptedDataTypes(List.of(OnOffType.class)) .withHandlerAcceptedCommandTypes(List.of(DecimalType.class)).build(); when(transformationServiceMock.transform(any(), any())).thenReturn(OnOffType.OFF.toString()); - ScriptProfile scriptProfile = new ScriptProfile(profileCallback, profileContext, transformationServiceMock); + ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext, + transformationServiceMock); scriptProfile.onCommandFromHandler(DecimalType.ZERO); scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO); @@ -194,14 +174,14 @@ public void onlyToItemScriptDoesNotForwardOutboundCommands() throws Transformati @Test public void onlyToHandlerScriptDoesNotForwardInboundCommands() throws TransformationException { - ProfileContext profileContext = ProfileContextBuilder.create().withScriptLanguage("customDSL") - .withToHandlerScript("outScript").withAcceptedCommandTypes(List.of(DecimalType.class)) - .withAcceptedDataTypes(List.of(DecimalType.class)) + ProfileContext profileContext = ProfileContextBuilder.create().withToHandlerScript("outScript") + .withAcceptedCommandTypes(List.of(DecimalType.class)).withAcceptedDataTypes(List.of(DecimalType.class)) .withHandlerAcceptedCommandTypes(List.of(OnOffType.class)).build(); when(transformationServiceMock.transform(any(), any())).thenReturn(OnOffType.OFF.toString()); - ScriptProfile scriptProfile = new ScriptProfile(profileCallback, profileContext, transformationServiceMock); + ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext, + transformationServiceMock); scriptProfile.onCommandFromHandler(DecimalType.ZERO); scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO); @@ -215,14 +195,15 @@ public void onlyToHandlerScriptDoesNotForwardInboundCommands() throws Transforma @Test public void incompatibleStateOrCommandNotForwardedToCallback() throws TransformationException { - ProfileContext profileContext = ProfileContextBuilder.create().withScriptLanguage("customDSL") - .withToItemScript("inScript").withToHandlerScript("outScript") - .withAcceptedCommandTypes(List.of(DecimalType.class)).withAcceptedDataTypes(List.of(PercentType.class)) + ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript") + .withToHandlerScript("outScript").withAcceptedCommandTypes(List.of(DecimalType.class)) + .withAcceptedDataTypes(List.of(PercentType.class)) .withHandlerAcceptedCommandTypes(List.of(HSBType.class)).build(); when(transformationServiceMock.transform(any(), any())).thenReturn(OnOffType.OFF.toString()); - ScriptProfile scriptProfile = new ScriptProfile(profileCallback, profileContext, transformationServiceMock); + ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext, + transformationServiceMock); scriptProfile.onCommandFromHandler(DecimalType.ZERO); scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO); @@ -244,11 +225,6 @@ public static ProfileContextBuilder create() { return new ProfileContextBuilder(); } - public ProfileContextBuilder withScriptLanguage(String scriptLanguage) { - configuration.put(CONFIG_SCRIPT_LANGUAGE, scriptLanguage); - return this; - } - public ProfileContextBuilder withToItemScript(String toItem) { configuration.put(CONFIG_TO_ITEM_SCRIPT, toItem); return this; diff --git a/bundles/org.openhab.core.io.rest.transform/src/main/java/org/openhab/core/io/rest/transform/internal/TransformationResource.java b/bundles/org.openhab.core.io.rest.transform/src/main/java/org/openhab/core/io/rest/transform/internal/TransformationResource.java index fb4197b0792..9e9942864ca 100644 --- a/bundles/org.openhab.core.io.rest.transform/src/main/java/org/openhab/core/io/rest/transform/internal/TransformationResource.java +++ b/bundles/org.openhab.core.io.rest.transform/src/main/java/org/openhab/core/io/rest/transform/internal/TransformationResource.java @@ -117,8 +117,9 @@ public Response getTransformationServices() { try { Collection> refs = bundleContext .getServiceReferences(TransformationService.class, null); - Stream services = refs.stream().map(ref -> (String) ref.getProperty("openhab.transform")) - .filter(Objects::nonNull).map(Objects::requireNonNull); + Stream services = refs.stream() + .map(ref -> (String) ref.getProperty(TransformationService.SERVICE_PROPERTY_NAME)) + .filter(Objects::nonNull).map(Objects::requireNonNull).sorted(); return Response.ok(new Stream2JSONInputStream(services)).build(); } catch (InvalidSyntaxException e) { return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); diff --git a/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationHelper.java b/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationHelper.java index 04eb909b43e..e39202afe96 100644 --- a/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationHelper.java +++ b/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationHelper.java @@ -61,7 +61,7 @@ public static boolean isTransform(String pattern) { public static @Nullable TransformationService getTransformationService(@Nullable BundleContext context, String transformationType) { if (context != null) { - String filter = "(openhab.transform=" + transformationType + ")"; + String filter = "(" + TransformationService.SERVICE_PROPERTY_NAME + "=" + transformationType + ")"; try { Collection> refs = context .getServiceReferences(TransformationService.class, filter); diff --git a/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationService.java b/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationService.java index 60be09cdbe7..f018a3a4aee 100644 --- a/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationService.java +++ b/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationService.java @@ -32,8 +32,10 @@ @NonNullByDefault public interface TransformationService { - public static final String TRANSFORM_FOLDER_NAME = "transform"; - public static final String TRANSFORM_PROFILE_SCOPE = "transform"; + String SERVICE_PROPERTY_NAME = "openhab.transform"; + String SERVICE_PROPERTY_LABEL = "openhab.transform.label"; + String TRANSFORM_FOLDER_NAME = "transform"; + String TRANSFORM_PROFILE_SCOPE = "transform"; /** * Transforms the input source by means of the given function and returns the transformed From e53551048327e7d3d78c097096e2d23aec4902fd Mon Sep 17 00:00:00 2001 From: spacemanspiff2007 <10754716+spacemanspiff2007@users.noreply.github.com> Date: Wed, 12 Apr 2023 22:16:59 +0200 Subject: [PATCH 032/126] Return item metadata by default (#3311) --- .../openhab/core/io/rest/core/internal/item/ItemResource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java index 21d69b05886..292376c4d21 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java @@ -210,7 +210,7 @@ public Response getItems(final @Context UriInfo uriInfo, final @Context HttpHead @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, @QueryParam("type") @Parameter(description = "item type filter") @Nullable String type, @QueryParam("tags") @Parameter(description = "item tag filter") @Nullable String tags, - @QueryParam("metadata") @Parameter(description = "metadata selector - a comma separated list or a regular expression (suppressed if no value given)") @Nullable String namespaceSelector, + @DefaultValue(".*") @QueryParam("metadata") @Parameter(description = "metadata selector - a comma separated list or a regular expression (returns all if no value given)") @Nullable String namespaceSelector, @DefaultValue("false") @QueryParam("recursive") @Parameter(description = "get member items recursively") boolean recursive, @QueryParam("fields") @Parameter(description = "limit output to the given fields (comma separated)") @Nullable String fields) { final Locale locale = localeService.getLocale(language); @@ -259,7 +259,7 @@ public Response getItemNamespaces(@PathParam("itemname") @Parameter(description @ApiResponse(responseCode = "404", description = "Item not found") }) public Response getItemData(final @Context UriInfo uriInfo, final @Context HttpHeaders httpHeaders, @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, - @QueryParam("metadata") @Parameter(description = "metadata selector - a comma separated list or a regular expression (suppressed if no value given)") @Nullable String namespaceSelector, + @DefaultValue(".*") @QueryParam("metadata") @Parameter(description = "metadata selector - a comma separated list or a regular expression (returns all if no value given)") @Nullable String namespaceSelector, @DefaultValue("true") @QueryParam("recursive") @Parameter(description = "get member items if the item is a group item") boolean recursive, @PathParam("itemname") @Parameter(description = "item name") String itemname) { final Locale locale = localeService.getLocale(language); From 44a146f5a81704aafb375cfec0553f98eb92ae63 Mon Sep 17 00:00:00 2001 From: Mark Herwege Date: Thu, 13 Apr 2023 20:04:47 +0200 Subject: [PATCH 033/126] Add Input widget to main UI sitemap configuration and REST API (#3431) * add input widget to UI SitemapProviderImpl * inputHint in UIComponentSitemapProvider * add input element to REST Signed-off-by: Mark Herwege --- .../core/io/rest/sitemap/internal/SitemapResource.java | 5 +++++ .../openhab/core/io/rest/sitemap/internal/WidgetDTO.java | 1 + .../ui/internal/components/UIComponentSitemapProvider.java | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java index a0597d54f60..2991d9b6ac2 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java @@ -75,6 +75,7 @@ import org.openhab.core.model.sitemap.sitemap.ColorArray; import org.openhab.core.model.sitemap.sitemap.Frame; import org.openhab.core.model.sitemap.sitemap.Image; +import org.openhab.core.model.sitemap.sitemap.Input; import org.openhab.core.model.sitemap.sitemap.LinkableWidget; import org.openhab.core.model.sitemap.sitemap.Mapping; import org.openhab.core.model.sitemap.sitemap.Mapview; @@ -558,6 +559,10 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null bean.mappings.add(mappingBean); } } + if (widget instanceof Input) { + Input inputWidget = (Input) widget; + bean.inputHint = inputWidget.getInputHint(); + } if (widget instanceof Slider) { Slider sliderWidget = (Slider) widget; bean.sendFrequency = sliderWidget.getFrequency(); diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/WidgetDTO.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/WidgetDTO.java index 61e79b7c347..db47ddd27e8 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/WidgetDTO.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/WidgetDTO.java @@ -47,6 +47,7 @@ public class WidgetDTO { public BigDecimal minValue; public BigDecimal maxValue; public BigDecimal step; + public String inputHint; public String url; public String encoding; public String service; diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentSitemapProvider.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentSitemapProvider.java index 47d54996b8c..e274c23bbce 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentSitemapProvider.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentSitemapProvider.java @@ -45,6 +45,7 @@ import org.openhab.core.model.sitemap.sitemap.impl.FrameImpl; import org.openhab.core.model.sitemap.sitemap.impl.GroupImpl; import org.openhab.core.model.sitemap.sitemap.impl.ImageImpl; +import org.openhab.core.model.sitemap.sitemap.impl.InputImpl; import org.openhab.core.model.sitemap.sitemap.impl.MappingImpl; import org.openhab.core.model.sitemap.sitemap.impl.MapviewImpl; import org.openhab.core.model.sitemap.sitemap.impl.SelectionImpl; @@ -235,6 +236,11 @@ protected Sitemap buildSitemap(RootUIComponent rootComponent) { addWidgetMappings(selectionWidget.getMappings(), component); widget = selectionWidget; break; + case "Input": + InputImpl inputWidget = (InputImpl) SitemapFactory.eINSTANCE.createInput(); + widget = inputWidget; + setWidgetPropertyFromComponentConfig(widget, component, "inputHint", SitemapPackage.INPUT__INPUT_HINT); + break; case "Setpoint": SetpointImpl setpointWidget = (SetpointImpl) SitemapFactory.eINSTANCE.createSetpoint(); widget = setpointWidget; From 9610404ceb15a59f01c39940396a396cb483c6d1 Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Thu, 13 Apr 2023 20:06:03 +0200 Subject: [PATCH 034/126] New Crowdin updates (#3541) * New translations SystemProfiles.properties (German) * New translations scriptprofile.properties (German) * New translations scriptprofile.properties (Hebrew) * New translations scriptprofile.properties (Italian) * New translations scriptprofile.properties (Polish) --- .../src/main/resources/OH-INF/i18n/SystemProfiles_de.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.thing/src/main/resources/OH-INF/i18n/SystemProfiles_de.properties b/bundles/org.openhab.core.thing/src/main/resources/OH-INF/i18n/SystemProfiles_de.properties index 08495c13951..9d3c7f09df1 100644 --- a/bundles/org.openhab.core.thing/src/main/resources/OH-INF/i18n/SystemProfiles_de.properties +++ b/bundles/org.openhab.core.thing/src/main/resources/OH-INF/i18n/SystemProfiles_de.properties @@ -20,7 +20,7 @@ profile.config.system.range.inverted.description = Invertiert die resultierende profile-type.system.timestamp-change.label = Zeitstempel bei Änderung profile-type.system.timestamp-offset.label = Zeitstempel Versatz profile.config.system.timestamp-offset.offset.label = Versatz -profile.config.system.timestamp-offset.offset.description = Versatz (numerischer Wert), welcher auf den Zeitstempel addiert wird. Ein negativer Versatz wird vom Zeitstempel abgezogen. +profile.config.system.timestamp-offset.offset.description = Versatz (numerischer Wert in Sekunden), welcher auf den Zeitstempel addiert wird. Ein negativer Versatz wird vom Zeitstempel abgezogen. profile.config.system.timestamp-offset.timezone.label = Zeitzone profile.config.system.timestamp-offset.timezone.description = Die Zeitzone, in die der Zeitstempel verändert werden soll. profile-type.system.timestamp-trigger.label = Zeitstempel bei Auslöser From 4f2af88e73194d7b04747528fc4c13dd50f1b385 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Fri, 14 Apr 2023 22:35:43 +1000 Subject: [PATCH 035/126] Add dynamic creation of semantic tags (#3519) Signed-off-by: Jimmy Tanagra --- .../model/generateTagClasses.groovy | 16 ++ bundles/org.openhab.core.semantics/pom.xml | 6 + .../openhab/core/semantics/SemanticTags.java | 144 ++++++++++++++++++ .../semantics/model/equipment/Equipments.java | 4 + .../semantics/model/location/Locations.java | 4 + .../core/semantics/model/point/Points.java | 4 + .../semantics/model/property/Properties.java | 4 + .../core/semantics/SemanticTagsTest.java | 119 +++++++++++++++ .../openhab-core/src/main/feature/feature.xml | 1 + 9 files changed, 302 insertions(+) diff --git a/bundles/org.openhab.core.semantics/model/generateTagClasses.groovy b/bundles/org.openhab.core.semantics/model/generateTagClasses.groovy index 557d9a36801..20f9cb58e79 100755 --- a/bundles/org.openhab.core.semantics/model/generateTagClasses.groovy +++ b/bundles/org.openhab.core.semantics/model/generateTagClasses.groovy @@ -134,6 +134,10 @@ public class Locations { public static Stream> stream() { return LOCATIONS.stream(); } + + public static boolean add(Class tag) { + return LOCATIONS.add(tag); + } } """) file.close() @@ -172,6 +176,10 @@ public class Equipments { public static Stream> stream() { return EQUIPMENTS.stream(); } + + public static boolean add(Class tag) { + return EQUIPMENTS.add(tag); + } } """) file.close() @@ -210,6 +218,10 @@ public class Points { public static Stream> stream() { return POINTS.stream(); } + + public static boolean add(Class tag) { + return POINTS.add(tag); + } } """) file.close() @@ -248,6 +260,10 @@ public class Properties { public static Stream> stream() { return PROPERTIES.stream(); } + + public static boolean add(Class tag) { + return PROPERTIES.add(tag); + } } """) file.close() diff --git a/bundles/org.openhab.core.semantics/pom.xml b/bundles/org.openhab.core.semantics/pom.xml index 92a08fd1236..54962c3b240 100644 --- a/bundles/org.openhab.core.semantics/pom.xml +++ b/bundles/org.openhab.core.semantics/pom.xml @@ -20,6 +20,12 @@ org.openhab.core ${project.version} + + org.ow2.asm + asm + 9.2 + provided + diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTags.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTags.java index e0a8d5db7b2..a6087ad1e0b 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTags.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTags.java @@ -26,6 +26,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; import org.openhab.core.items.Item; import org.openhab.core.semantics.model.equipment.Equipments; import org.openhab.core.semantics.model.location.Locations; @@ -33,12 +36,15 @@ import org.openhab.core.semantics.model.point.Points; import org.openhab.core.semantics.model.property.Properties; import org.openhab.core.types.StateDescription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This is a class that gives static access to the semantic tag library. * For everything that is not static, the {@link SemanticsService} should be used instead. * * @author Kai Kreuzer - Initial contribution + * @author Jimmy Tanagra - Add the ability to add new tags at runtime */ @NonNullByDefault public class SemanticTags { @@ -47,6 +53,9 @@ public class SemanticTags { private static final Map> TAGS = new TreeMap<>(); + private static final Logger LOGGER = LoggerFactory.getLogger(SemanticTags.class); + private static final SemanticClassLoader CLASS_LOADER = new SemanticClassLoader(); + static { Locations.stream().forEach(location -> addTagSet(location)); Equipments.stream().forEach(equipment -> addTagSet(equipment)); @@ -203,6 +212,117 @@ public static String getLabel(Class tag, Locale locale) { return null; } + /** + * Adds a new semantic tag with inferred label, empty synonyms and description. + * + * The label will be inferred from the tag name by splitting the CamelCase with a space. + * + * @param name the tag name to add + * @param parent the parent tag that the new tag should belong to + * @return the created semantic tag class, or null if it was already added. + */ + public static @Nullable Class add(String name, String parent) { + return add(name, parent, null, null, null); + } + + /** + * Adds a new semantic tag. + * + * @param name the tag name to add + * @param parent the parent tag that the new tag should belong to + * @param label an optional label. When null, the label will be inferred from the tag name, + * splitting the CamelCase with a space. + * @param synonyms a comma separated list of synonyms + * @param description the tag description + * @return the created semantic tag class, or null if it was already added. + */ + public static @Nullable Class add(String name, String parent, @Nullable String label, + @Nullable String synonyms, @Nullable String description) { + Class parentClass = getById(parent); + if (parentClass == null) { + LOGGER.warn("Adding semantic tag '{}' failed because parent tag '{}' is not found.", name, parent); + return null; + } + return add(name, parentClass, label, synonyms, description); + } + + /** + * Adds a new semantic tag with inferred label, empty synonyms and description. + * + * The label will be inferred from the tag name by splitting the CamelCase with a space. + * + * @param name the tag name to add + * @param parent the parent tag that the new tag should belong to + * @return the created semantic tag class, or null if it was already added. + */ + public static @Nullable Class add(String name, Class parent) { + return add(name, parent, null, null, null); + } + + /** + * Adds a new semantic tag. + * + * @param name the tag name to add + * @param parent the parent tag that the new tag should belong to + * @param label an optional label. When null, the label will be inferred from the tag name, + * splitting the CamelCase with a space. + * @param synonyms a comma separated list of synonyms + * @param description the tag description + * @return the created semantic tag class, or null if it was already added. + */ + public static @Nullable Class add(String name, Class parent, @Nullable String label, + @Nullable String synonyms, @Nullable String description) { + if (getById(name) != null) { + return null; + } + + if (!name.matches("[A-Z][a-zA-Z0-9]+")) { + throw new IllegalArgumentException( + "The tag name '" + name + "' must start with a capital letter and contain only alphanumerics."); + } + + String parentId = parent.getAnnotation(TagInfo.class).id(); + String type = parentId.split("_")[0]; + String className = "org.openhab.core.semantics.model." + type.toLowerCase() + "." + name; + + // Infer label from name, splitting up CamelCaseALL99 -> Camel Case ALL 99 + label = Optional.ofNullable(label).orElseGet(() -> name.replaceAll("([A-Z][a-z]+|[A-Z][A-Z]+|[0-9]+)", " $1")) + .trim(); + synonyms = Optional.ofNullable(synonyms).orElse("").replaceAll("\\s*,\\s*", ",").trim(); + + // Create the tag interface + ClassWriter classWriter = new ClassWriter(0); + classWriter.visit(Opcodes.V11, Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT + Opcodes.ACC_INTERFACE, + className.replace('.', '/'), null, "java/lang/Object", + new String[] { parent.getName().replace('.', '/') }); + + // Add TagInfo Annotation + classWriter.visitSource("Status.java", null); + + AnnotationVisitor annotation = classWriter.visitAnnotation("Lorg/openhab/core/semantics/TagInfo;", true); + annotation.visit("id", parentId + "_" + name); + annotation.visit("label", label); + annotation.visit("synonyms", synonyms); + annotation.visit("description", Optional.ofNullable(description).orElse("").trim()); + annotation.visitEnd(); + + classWriter.visitEnd(); + byte[] byteCode = classWriter.toByteArray(); + Class newTag = null; + try { + newTag = CLASS_LOADER.defineClass(className, byteCode); + } catch (Exception e) { + LOGGER.warn("Failed creating a new semantic tag '{}': {}", className, e.getMessage()); + return null; + } + addToModel(newTag); + addTagSet(newTag); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("'{}' semantic {} tag added.", className, type); + } + return newTag; + } + private static void addTagSet(Class tagSet) { String id = tagSet.getAnnotation(TagInfo.class).id(); while (id.indexOf("_") != -1) { @@ -211,4 +331,28 @@ private static void addTagSet(Class tagSet) { } TAGS.put(id, tagSet); } + + private static boolean addToModel(Class tag) { + if (Location.class.isAssignableFrom(tag)) { + return Locations.add((Class) tag); + } else if (Equipment.class.isAssignableFrom(tag)) { + return Equipments.add((Class) tag); + } else if (Point.class.isAssignableFrom(tag)) { + return Points.add((Class) tag); + } else if (Property.class.isAssignableFrom(tag)) { + return Properties.add((Class) tag); + } + throw new IllegalArgumentException("Unknown type of tag " + tag); + } + + private static class SemanticClassLoader extends ClassLoader { + public SemanticClassLoader() { + super(SemanticTags.class.getClassLoader()); + } + + public Class defineClass(String className, byte[] byteCode) { + // defineClass is protected in the normal ClassLoader + return defineClass(className, byteCode, 0, byteCode.length); + } + } } diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Equipments.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Equipments.java index 30b6bbe0836..d99172b88c6 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Equipments.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Equipments.java @@ -89,4 +89,8 @@ public class Equipments { public static Stream> stream() { return EQUIPMENTS.stream(); } + + public static boolean add(Class tag) { + return EQUIPMENTS.add(tag); + } } diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Locations.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Locations.java index df755f38251..6c5063dfb1d 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Locations.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Locations.java @@ -72,4 +72,8 @@ public class Locations { public static Stream> stream() { return LOCATIONS.stream(); } + + public static boolean add(Class tag) { + return LOCATIONS.add(tag); + } } diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Points.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Points.java index 44f78811272..d7950695f1e 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Points.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Points.java @@ -47,4 +47,8 @@ public class Points { public static Stream> stream() { return POINTS.stream(); } + + public static boolean add(Class tag) { + return POINTS.add(tag); + } } diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Properties.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Properties.java index 7a747b23830..06de5ebba85 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Properties.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Properties.java @@ -63,4 +63,8 @@ public class Properties { public static Stream> stream() { return PROPERTIES.stream(); } + + public static boolean add(Class tag) { + return PROPERTIES.add(tag); + } } diff --git a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticTagsTest.java b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticTagsTest.java index 51bc065197e..c6c37d81834 100644 --- a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticTagsTest.java +++ b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticTagsTest.java @@ -23,10 +23,14 @@ import org.openhab.core.items.GroupItem; import org.openhab.core.library.CoreItemFactory; import org.openhab.core.semantics.model.equipment.CleaningRobot; +import org.openhab.core.semantics.model.equipment.Equipments; import org.openhab.core.semantics.model.location.Bathroom; import org.openhab.core.semantics.model.location.Kitchen; +import org.openhab.core.semantics.model.location.Locations; import org.openhab.core.semantics.model.location.Room; import org.openhab.core.semantics.model.point.Measurement; +import org.openhab.core.semantics.model.point.Points; +import org.openhab.core.semantics.model.property.Properties; import org.openhab.core.semantics.model.property.Temperature; /** @@ -105,4 +109,119 @@ public void testGetPoint() { public void testGetProperty() { assertEquals(Temperature.class, SemanticTags.getProperty(pointItem)); } + + @Test + public void testAddLocation() { + String tagName = "CustomLocation"; + Class customTag = SemanticTags.add(tagName, Location.class); + assertNotNull(customTag); + assertEquals(customTag, SemanticTags.getById(tagName)); + assertEquals(customTag, SemanticTags.getByLabel("Custom Location", Locale.getDefault())); + assertTrue(Locations.stream().toList().contains(customTag)); + + GroupItem myItem = new GroupItem("MyLocation"); + myItem.addTag(tagName); + + assertEquals(customTag, SemanticTags.getLocation(myItem)); + } + + @Test + public void testAddLocationWithParentString() { + String tagName = "CustomLocationParentString"; + Class customTag = SemanticTags.add(tagName, "Location"); + assertNotNull(customTag); + assertTrue(Locations.stream().toList().contains(customTag)); + } + + @Test + public void testAddEquipment() { + String tagName = "CustomEquipment"; + Class customTag = SemanticTags.add(tagName, Equipment.class); + assertNotNull(customTag); + assertEquals(customTag, SemanticTags.getById(tagName)); + assertEquals(customTag, SemanticTags.getByLabel("Custom Equipment", Locale.getDefault())); + assertTrue(Equipments.stream().toList().contains(customTag)); + + GroupItem myItem = new GroupItem("MyEquipment"); + myItem.addTag(tagName); + + assertEquals(customTag, SemanticTags.getEquipment(myItem)); + } + + @Test + public void testAddEquipmentWithParentString() { + String tagName = "CustomEquipmentParentString"; + Class customTag = SemanticTags.add(tagName, "Television"); + assertNotNull(customTag); + assertTrue(Equipments.stream().toList().contains(customTag)); + } + + @Test + public void testAddPoint() { + String tagName = "CustomPoint"; + Class customTag = SemanticTags.add(tagName, Point.class); + assertNotNull(customTag); + assertEquals(customTag, SemanticTags.getById(tagName)); + assertEquals(customTag, SemanticTags.getByLabel("Custom Point", Locale.getDefault())); + assertTrue(Points.stream().toList().contains(customTag)); + + GroupItem myItem = new GroupItem("MyItem"); + myItem.addTag(tagName); + + assertEquals(customTag, SemanticTags.getPoint(myItem)); + } + + @Test + public void testAddPointParentString() { + String tagName = "CustomPointParentString"; + Class customTag = SemanticTags.add(tagName, "Control"); + assertNotNull(customTag); + assertTrue(Points.stream().toList().contains(customTag)); + } + + @Test + public void testAddProperty() { + String tagName = "CustomProperty"; + Class customTag = SemanticTags.add(tagName, Property.class); + assertNotNull(customTag); + assertEquals(customTag, SemanticTags.getById(tagName)); + assertEquals(customTag, SemanticTags.getByLabel("Custom Property", Locale.getDefault())); + assertTrue(Properties.stream().toList().contains(customTag)); + + GroupItem myItem = new GroupItem("MyItem"); + myItem.addTag(tagName); + + assertEquals(customTag, SemanticTags.getProperty(myItem)); + } + + @Test + public void testAddPropertyParentString() { + String tagName = "CustomPropertyParentString"; + Class customTag = SemanticTags.add(tagName, "Property"); + assertNotNull(customTag); + assertTrue(Properties.stream().toList().contains(customTag)); + } + + @Test + public void testAddingExistingTagShouldFail() { + assertNull(SemanticTags.add("Room", Location.class)); + + assertNotNull(SemanticTags.add("CustomLocation1", Location.class)); + assertNull(SemanticTags.add("CustomLocation1", Location.class)); + } + + @Test + public void testAddWithCustomLabel() { + Class tag = SemanticTags.add("CustomProperty2", Property.class, " Custom Label ", null, null); + assertEquals(tag, SemanticTags.getByLabel("Custom Label", Locale.getDefault())); + } + + @Test + public void testAddWithSynonyms() { + String synonyms = " Synonym1, Synonym2 , Synonym With Space "; + Class tag = SemanticTags.add("CustomProperty3", Property.class, null, synonyms, null); + assertEquals(tag, SemanticTags.getByLabelOrSynonym("Synonym1", Locale.getDefault()).get(0)); + assertEquals(tag, SemanticTags.getByLabelOrSynonym("Synonym2", Locale.getDefault()).get(0)); + assertEquals(tag, SemanticTags.getByLabelOrSynonym("Synonym With Space", Locale.getDefault()).get(0)); + } } diff --git a/features/karaf/openhab-core/src/main/feature/feature.xml b/features/karaf/openhab-core/src/main/feature/feature.xml index 1b3d69887ef..5fe065535e4 100644 --- a/features/karaf/openhab-core/src/main/feature/feature.xml +++ b/features/karaf/openhab-core/src/main/feature/feature.xml @@ -54,6 +54,7 @@ mvn:org.openhab.core.bundles/org.openhab.core.id/${project.version} mvn:org.openhab.core.bundles/org.openhab.core.persistence/${project.version} mvn:org.openhab.core.bundles/org.openhab.core.semantics/${project.version} + openhab.tp-asm mvn:org.openhab.core.bundles/org.openhab.core.thing/${project.version} mvn:org.openhab.core.bundles/org.openhab.core.transform/${project.version} mvn:org.openhab.core.bundles/org.openhab.core.audio/${project.version} From 5d46377d224970e1261a1e9edcc5f57cd5936961 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Sat, 15 Apr 2023 02:39:20 +1000 Subject: [PATCH 036/126] Fix IndexOutOfBoundsException in ScriptEngineFactoryHelper (#3549) * Fix IndexOutOfBoundsException in ScriptEngineFactoryHelper Signed-off-by: Jimmy Tanagra --- .../script/internal/ScriptEngineFactoryHelper.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryHelper.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryHelper.java index 6acbfeac2d0..46871bc9cc4 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryHelper.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryHelper.java @@ -59,9 +59,14 @@ private ScriptEngineFactoryHelper() { } public static String getPreferredMimeType(ScriptEngineFactory factory) { - List mimeTypes = new ArrayList<>(factory.getScriptTypes()); + List scriptTypes = factory.getScriptTypes(); + if (scriptTypes.isEmpty()) { + throw new IllegalStateException( + factory.getClass().getName() + " does not support any scriptTypes. Please report it as a bug."); + } + List mimeTypes = new ArrayList<>(scriptTypes); mimeTypes.removeIf(mimeType -> !mimeType.contains("application") || "application/python".equals(mimeType)); - return mimeTypes.isEmpty() ? factory.getScriptTypes().get(0) : mimeTypes.get(0); + return mimeTypes.isEmpty() ? scriptTypes.get(0) : mimeTypes.get(0); } public static String getLanguageName(javax.script.ScriptEngineFactory factory) { From eb6b6b9677c628cf5ae85459759f742dd5a46bd8 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Fri, 14 Apr 2023 21:19:08 +0200 Subject: [PATCH 037/126] [oauth] Add support for custom deserialization of AccessTokenResponse (#3537) * Add possibility to inject custom GsonBuilder Reverts #1891 Fixes #1888 Signed-off-by: Jacob Laursen --- .../internal/OAuthClientServiceImpl.java | 38 ++++++++++++------- .../oauth2client/internal/OAuthConnector.java | 24 ++++-------- .../internal/PersistedParams.java | 12 ------ .../client/oauth2/OAuthClientService.java | 12 +++--- .../core/auth/client/oauth2/OAuthFactory.java | 22 +++++------ 5 files changed, 49 insertions(+), 59 deletions(-) diff --git a/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/OAuthClientServiceImpl.java b/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/OAuthClientServiceImpl.java index fceb10b0856..1d8ce4cb125 100644 --- a/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/OAuthClientServiceImpl.java +++ b/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/OAuthClientServiceImpl.java @@ -35,7 +35,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.JsonDeserializer; +import com.google.gson.GsonBuilder; /** * Implementation of OAuthClientService. @@ -68,16 +68,19 @@ public class OAuthClientServiceImpl implements OAuthClientService { private final String handle; private final int tokenExpiresInSeconds; private final HttpClientFactory httpClientFactory; + private final @Nullable GsonBuilder gsonBuilder; private final List accessTokenRefreshListeners = new ArrayList<>(); private PersistedParams persistedParams = new PersistedParams(); private volatile boolean closed = false; - private OAuthClientServiceImpl(String handle, int tokenExpiresInSeconds, HttpClientFactory httpClientFactory) { + private OAuthClientServiceImpl(String handle, int tokenExpiresInSeconds, HttpClientFactory httpClientFactory, + @Nullable GsonBuilder gsonBuilder) { this.handle = handle; this.tokenExpiresInSeconds = tokenExpiresInSeconds; this.httpClientFactory = httpClientFactory; + this.gsonBuilder = gsonBuilder; } /** @@ -103,7 +106,7 @@ private OAuthClientServiceImpl(String handle, int tokenExpiresInSeconds, HttpCli return null; } OAuthClientServiceImpl clientService = new OAuthClientServiceImpl(handle, tokenExpiresInSeconds, - httpClientFactory); + httpClientFactory, null); clientService.storeHandler = storeHandler; clientService.persistedParams = persistedParamsFromStore; @@ -118,13 +121,13 @@ private OAuthClientServiceImpl(String handle, int tokenExpiresInSeconds, HttpCli * {@link org.openhab.core.auth.client.oauth2.OAuthFactory#createOAuthClientService}* * @param storeHandler Storage handler * @param httpClientFactory Http client factory - * @param persistedParams These parameters are static with respect to the oauth provider and thus can be persisted. + * @param persistedParams These parameters are static with respect to the OAuth provider and thus can be persisted. * @return OAuthClientServiceImpl an instance */ static OAuthClientServiceImpl createInstance(String handle, OAuthStoreHandler storeHandler, HttpClientFactory httpClientFactory, PersistedParams params) { OAuthClientServiceImpl clientService = new OAuthClientServiceImpl(handle, params.tokenExpiresInSeconds, - httpClientFactory); + httpClientFactory, null); clientService.storeHandler = storeHandler; clientService.persistedParams = params; @@ -153,7 +156,9 @@ public String getAuthorizationUrl(@Nullable String redirectURI, @Nullable String throw new OAuthException("Missing client ID"); } - OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName); + GsonBuilder gsonBuilder = this.gsonBuilder; + OAuthConnector connector = gsonBuilder == null ? new OAuthConnector(httpClientFactory) + : new OAuthConnector(httpClientFactory, gsonBuilder); return connector.getAuthorizationUrl(authorizationUrl, clientId, redirectURI, persistedParams.state, scopeToUse); } @@ -207,7 +212,9 @@ public AccessTokenResponse getAccessTokenResponseByAuthorizationCode(String auth throw new OAuthException("Missing client ID"); } - OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName); + GsonBuilder gsonBuilder = this.gsonBuilder; + OAuthConnector connector = gsonBuilder == null ? new OAuthConnector(httpClientFactory) + : new OAuthConnector(httpClientFactory, gsonBuilder); AccessTokenResponse accessTokenResponse = connector.grantTypeAuthorizationCode(tokenUrl, authorizationCode, clientId, persistedParams.clientSecret, redirectURI, Boolean.TRUE.equals(persistedParams.supportsBasicAuth)); @@ -239,7 +246,9 @@ public AccessTokenResponse getAccessTokenByResourceOwnerPasswordCredentials(Stri throw new OAuthException("Missing token url"); } - OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName); + GsonBuilder gsonBuilder = this.gsonBuilder; + OAuthConnector connector = gsonBuilder == null ? new OAuthConnector(httpClientFactory) + : new OAuthConnector(httpClientFactory, gsonBuilder); AccessTokenResponse accessTokenResponse = connector.grantTypePassword(tokenUrl, username, password, persistedParams.clientId, persistedParams.clientSecret, scope, Boolean.TRUE.equals(persistedParams.supportsBasicAuth)); @@ -264,7 +273,9 @@ public AccessTokenResponse getAccessTokenByClientCredentials(@Nullable String sc throw new OAuthException("Missing client ID"); } - OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName); + GsonBuilder gsonBuilder = this.gsonBuilder; + OAuthConnector connector = gsonBuilder == null ? new OAuthConnector(httpClientFactory) + : new OAuthConnector(httpClientFactory, gsonBuilder); // depending on usage, cannot guarantee every parameter is not null at the beginning AccessTokenResponse accessTokenResponse = connector.grantTypeClientCredentials(tokenUrl, clientId, persistedParams.clientSecret, scope, Boolean.TRUE.equals(persistedParams.supportsBasicAuth)); @@ -298,7 +309,9 @@ public AccessTokenResponse refreshToken() throws OAuthException, IOException, OA throw new OAuthException("tokenUrl is required but null"); } - OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName); + GsonBuilder gsonBuilder = this.gsonBuilder; + OAuthConnector connector = gsonBuilder == null ? new OAuthConnector(httpClientFactory) + : new OAuthConnector(httpClientFactory, gsonBuilder); AccessTokenResponse accessTokenResponse = connector.grantTypeRefreshToken(tokenUrl, lastAccessToken.getRefreshToken(), persistedParams.clientId, persistedParams.clientSecret, persistedParams.scope, Boolean.TRUE.equals(persistedParams.supportsBasicAuth)); @@ -400,10 +413,9 @@ private String createNewState() { } @Override - public > OAuthClientService withDeserializer(Class deserializerClass) { + public OAuthClientService withGsonBuilder(GsonBuilder gsonBuilder) { OAuthClientServiceImpl clientService = new OAuthClientServiceImpl(handle, persistedParams.tokenExpiresInSeconds, - httpClientFactory); - persistedParams.deserializerClassName = deserializerClass.getName(); + httpClientFactory, gsonBuilder); clientService.persistedParams = persistedParams; clientService.storeHandler = storeHandler; diff --git a/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/OAuthConnector.java b/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/OAuthConnector.java index 9bda7aeeb06..00d5a28d879 100644 --- a/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/OAuthConnector.java +++ b/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/OAuthConnector.java @@ -15,7 +15,6 @@ import static org.openhab.core.auth.oauth2client.internal.Keyword.*; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.Instant; @@ -67,29 +66,20 @@ public class OAuthConnector { private final Logger logger = LoggerFactory.getLogger(OAuthConnector.class); private final Gson gson; - public OAuthConnector(HttpClientFactory httpClientFactory, @Nullable String deserializerClassName) { + public OAuthConnector(HttpClientFactory httpClientFactory) { + this(httpClientFactory, new GsonBuilder()); + } + + public OAuthConnector(HttpClientFactory httpClientFactory, GsonBuilder gsonBuilder) { this.httpClientFactory = httpClientFactory; - GsonBuilder gsonBuilder = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + gson = gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .registerTypeAdapter(Instant.class, (JsonDeserializer) (json, typeOfT, context) -> { try { return Instant.parse(json.getAsString()); } catch (DateTimeParseException e) { return LocalDateTime.parse(json.getAsString()).atZone(ZoneId.systemDefault()).toInstant(); } - }); - - if (deserializerClassName != null) { - try { - Class deserializerClass = Class.forName(deserializerClassName); - gsonBuilder = gsonBuilder.registerTypeAdapter(AccessTokenResponse.class, - deserializerClass.getConstructor().newInstance()); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException | NoSuchMethodException | SecurityException - | ClassNotFoundException e) { - logger.error("Unable to construct custom deserializer '{}'", deserializerClassName, e); - } - } - gson = gsonBuilder.create(); + }).create(); } /** diff --git a/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/PersistedParams.java b/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/PersistedParams.java index e1705d9e801..861c3aed9cf 100644 --- a/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/PersistedParams.java +++ b/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/PersistedParams.java @@ -33,8 +33,6 @@ class PersistedParams { String state; String redirectUri; int tokenExpiresInSeconds = 60; - @Nullable - String deserializerClassName; /** * Default constructor needed for json serialization. @@ -59,7 +57,6 @@ public PersistedParams() { * of the access tokens. This allows the access token to expire earlier than the * official stated expiry time; thus prevents the caller obtaining a valid token at the time of invoke, * only to find the token immediately expired. - * @param deserializerClass (optional) if a specific deserializer is needed */ public PersistedParams(String handle, String tokenUrl, String authorizationUrl, String clientId, String clientSecret, String scope, Boolean supportsBasicAuth, int tokenExpiresInSeconds, @@ -72,7 +69,6 @@ public PersistedParams(String handle, String tokenUrl, String authorizationUrl, this.scope = scope; this.supportsBasicAuth = supportsBasicAuth; this.tokenExpiresInSeconds = tokenExpiresInSeconds; - this.deserializerClassName = deserializerClassName; } @Override @@ -89,7 +85,6 @@ public int hashCode() { result = prime * result + ((supportsBasicAuth == null) ? 0 : supportsBasicAuth.hashCode()); result = prime * result + tokenExpiresInSeconds; result = prime * result + ((tokenUrl == null) ? 0 : tokenUrl.hashCode()); - result = prime * result + ((deserializerClassName != null) ? deserializerClassName.hashCode() : 0); return result; } @@ -168,13 +163,6 @@ public boolean equals(@Nullable Object obj) { } else if (!tokenUrl.equals(other.tokenUrl)) { return false; } - if (deserializerClassName == null) { - if (other.deserializerClassName != null) { - return false; - } - } else if (deserializerClassName != null && !deserializerClassName.equals(other.deserializerClassName)) { - return false; - } return true; } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/auth/client/oauth2/OAuthClientService.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/auth/client/oauth2/OAuthClientService.java index 2f509a614e9..b278343643d 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/auth/client/oauth2/OAuthClientService.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/auth/client/oauth2/OAuthClientService.java @@ -17,11 +17,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import com.google.gson.JsonDeserializer; +import com.google.gson.GsonBuilder; /** * This is the service factory to produce an OAuth2 service client that authenticates using OAUTH2. - * This is a service factory pattern; the OAuthe2 service client is not shared between bundles. + * This is a service factory pattern; the OAuth2 service client is not shared between bundles. * *

* The basic uses of this OAuthClient are as follows: @@ -291,10 +291,10 @@ AccessTokenResponse getAccessTokenByImplicit(@Nullable String redirectURI, @Null boolean removeAccessTokenRefreshListener(AccessTokenRefreshListener listener); /** - * Adds a personalized deserializer to a given oauth service. + * Adds a custom GsonBuilder to be used with the OAuth service instance. * - * @param deserializeClass the deserializer class that should be used to deserialize AccessTokenResponse - * @return the oauth service + * @param gsonBuilder the custom GsonBuilder instance + * @return the OAuth service */ - > OAuthClientService withDeserializer(Class deserializerClass); + OAuthClientService withGsonBuilder(GsonBuilder gsonBuilder); } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/auth/client/oauth2/OAuthFactory.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/auth/client/oauth2/OAuthFactory.java index a4b62501e98..b2b962b78c1 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/auth/client/oauth2/OAuthFactory.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/auth/client/oauth2/OAuthFactory.java @@ -26,12 +26,12 @@ public interface OAuthFactory { /** - * Creates a new oauth service. Use this method only once to obtain a handle and store + * Creates a new OAuth service. Use this method only once to obtain a handle and store * this handle for further in a persistent storage container. * - * @param handle the handle to the oauth service - * @param tokenUrl the token url of the oauth provider. This is used for getting access token. - * @param authorizationUrl the authorization url of the oauth provider. This is used purely for generating + * @param handle the handle to the OAuth service + * @param tokenUrl the token url of the OAuth provider. This is used for getting access token. + * @param authorizationUrl the authorization url of the OAuth provider. This is used purely for generating * authorization code/ url. * @param clientId the client id * @param clientSecret the client secret (optional) @@ -39,23 +39,23 @@ public interface OAuthFactory { * @param supportsBasicAuth whether the OAuth provider supports basic authorization or the client id and client * secret should be passed as form params. true - use http basic authentication, false - do not use http * basic authentication, null - unknown (default to do not use) - * @return the oauth service + * @return the OAuth service */ OAuthClientService createOAuthClientService(String handle, String tokenUrl, @Nullable String authorizationUrl, String clientId, @Nullable String clientSecret, @Nullable String scope, @Nullable Boolean supportsBasicAuth); /** - * Gets the oauth service for a given handle + * Gets the OAuth service for a given handle * - * @param handle the handle to the oauth service - * @return the oauth service or null if it doesn't exist + * @param handle the handle to the OAuth service + * @return the OAuth service or null if it doesn't exist */ @Nullable OAuthClientService getOAuthClientService(String handle); /** - * Unget an oauth service, this unget/unregister the service, and frees the resources. + * Unget an OAuth service, this unget/unregister the service, and frees the resources. * The existing tokens/ configurations (persisted parameters) are still saved * in the store. It will internally call {@code OAuthClientService#close()}. * @@ -64,7 +64,7 @@ OAuthClientService createOAuthClientService(String handle, String tokenUrl, @Nul * If OAuth service is closed directly, without using {@code #ungetOAuthService(String)}, * then a small residual footprint is left in the cache. * - * @param handle the handle to the oauth service + * @param handle the handle to the OAuth service */ void ungetOAuthService(String handle); @@ -72,7 +72,7 @@ OAuthClientService createOAuthClientService(String handle, String tokenUrl, @Nul * This method is for unget/unregister the service, * then DELETE access token, configuration data from the store * - * @param handle + * @param handle the handle to the OAuth service */ void deleteServiceAndAccessToken(String handle); } From 3fc4a4c9d28ddcc95c265cc7760effa21c5cee32 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Sat, 15 Apr 2023 17:04:37 +1000 Subject: [PATCH 038/126] Improve logging to identify the offending link (#3554) Signed-off-by: Jimmy Tanagra --- .../org/openhab/core/thing/internal/CommunicationManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java index 608179f6459..c47737a6af4 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java @@ -322,7 +322,7 @@ private String normalizeProfileName(String profileName) { } } } - logger.warn("No ProfileFactory found which supports profile '{}'", profileTypeUID); + logger.warn("No ProfileFactory found which supports profile '{}' for link '{}'", profileTypeUID, link); return null; } From 5ca849ed883c602ed7c8cfc09243082e83573688 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 15 Apr 2023 09:11:03 +0200 Subject: [PATCH 039/126] Fix hidden files showing up in TransformationRegistry (#3532) Signed-off-by: Jan N. Klug --- .../transform/FileTransformationProvider.java | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/FileTransformationProvider.java b/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/FileTransformationProvider.java index a5df5441d90..4fd1213ef8b 100644 --- a/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/FileTransformationProvider.java +++ b/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/FileTransformationProvider.java @@ -67,7 +67,8 @@ public FileTransformationProvider( watchService.registerListener(this, transformationPath); // read initial contents try (Stream files = Files.walk(transformationPath)) { - files.filter(Files::isRegularFile).map(transformationPath::relativize).forEach(f -> processPath(CREATE, f)); + files.filter(Files::isRegularFile).map(transformationPath::relativize) + .forEach(f -> processWatchEvent(CREATE, f)); } catch (IOException e) { logger.warn("Could not list files in '{}', transformation configurations might be missing: {}", transformationPath, e.getMessage()); @@ -94,16 +95,18 @@ public Collection getAll() { return transformationConfigurations.values(); } - private void processPath(WatchService.Kind kind, Path path) { + @Override + public void processWatchEvent(WatchService.Kind kind, Path path) { Path finalPath = transformationPath.resolve(path); - if (kind == DELETE) { - Transformation oldElement = transformationConfigurations.remove(path); - if (oldElement != null) { - logger.trace("Removed configuration from file '{}", path); - listeners.forEach(listener -> listener.removed(this, oldElement)); - } - } else if (Files.isRegularFile(finalPath) && ((kind == CREATE) || (kind == MODIFY))) { - try { + try { + if (kind == DELETE) { + Transformation oldElement = transformationConfigurations.remove(path); + if (oldElement != null) { + logger.trace("Removed configuration from file '{}", path); + listeners.forEach(listener -> listener.removed(this, oldElement)); + } + } else if (Files.isRegularFile(finalPath) && !Files.isHidden(finalPath) + && ((kind == CREATE) || (kind == MODIFY))) { String fileName = path.getFileName().toString(); Matcher m = FILENAME_PATTERN.matcher(fileName); if (!m.matches()) { @@ -131,16 +134,11 @@ private void processPath(WatchService.Kind kind, Path path) { logger.trace("Updated new configuration from file '{}'", path); listeners.forEach(listener -> listener.updated(this, oldElement, newElement)); } - } catch (IOException e) { - logger.warn("Skipping {} event for '{}' - failed to read content: {}", kind, path, e.getMessage()); + } else { + logger.trace("Skipping {} event for '{}' - not a regular file", kind, path); } - } else { - logger.trace("Skipping {} event for '{}' - not a regular file", kind, path); + } catch (IOException e) { + logger.warn("Skipping {} event for '{}' - failed to process it: {}", kind, path, e.getMessage()); } } - - @Override - public void processWatchEvent(WatchService.Kind kind, Path path) { - processPath(kind, path); - } } From 016828cceeeb05c20b857b830fc2596b51934a41 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 15 Apr 2023 09:16:32 +0200 Subject: [PATCH 040/126] Fix thing reloading from things file (#3526) It was found that things from textual configuration are not properly updated if changes are only made in configuration or label of a channel. The reason is that for equality only uid and accepted item-type where checked. Signed-off-by: Jan N. Klug --- .../thing/internal/GenericThingProvider.xtend | 18 +++++++------- .../java/org/openhab/core/thing/Channel.java | 24 +++++++++++++++++++ .../openhab/core/thing/util/ThingHelper.java | 22 +++-------------- .../core/thing/util/ThingHelperTest.java | 18 ++++++++++++++ 4 files changed, 54 insertions(+), 28 deletions(-) diff --git a/bundles/org.openhab.core.model.thing/src/org/openhab/core/model/thing/internal/GenericThingProvider.xtend b/bundles/org.openhab.core.model.thing/src/org/openhab/core/model/thing/internal/GenericThingProvider.xtend index b8559cc88da..e07b8fd5dc4 100644 --- a/bundles/org.openhab.core.model.thing/src/org/openhab/core/model/thing/internal/GenericThingProvider.xtend +++ b/bundles/org.openhab.core.model.thing/src/org/openhab/core/model/thing/internal/GenericThingProvider.xtend @@ -602,19 +602,20 @@ class GenericThingProvider extends AbstractProviderLazyNullness implement } def private createThingsFromModelForThingHandlerFactory(String modelName, ThingHandlerFactory factory) { - if (!loadedXmlThingTypes.contains(factory.bundleName)) { + if (!loadedXmlThingTypes.contains(factory.bundleName) || modelRepository == null) { return } val oldThings = thingsMap.get(modelName).clone val newThings = newArrayList() - if (modelRepository !== null) { - val model = modelRepository.getModel(modelName) as ThingModel - if (model !== null) { - flattenModelThings(model.things).forEach [ - createThing(newThings, factory) - ] - } + + val model = modelRepository.getModel(modelName) as ThingModel + if (model !== null) { + flattenModelThings(model.things).forEach [ + createThing(newThings, factory) + ] } + thingsMap.put(modelName, newThings) + newThings.forEach [ newThing | val oldThing = oldThings.findFirst[it.UID == newThing.UID] if (oldThing !== null) { @@ -624,7 +625,6 @@ class GenericThingProvider extends AbstractProviderLazyNullness implement } } else { logger.debug("Adding thing '{}' from model '{}'.", newThing.UID, modelName); - thingsMap.get(modelName).add(newThing) newThing.notifyListenersAboutAddedElement } ] diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/Channel.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/Channel.java index 9a3ec7df81e..ee1e8c9a001 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/Channel.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/Channel.java @@ -14,6 +14,7 @@ import java.util.LinkedHashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -178,4 +179,27 @@ public Set getDefaultTags() { public @Nullable AutoUpdatePolicy getAutoUpdatePolicy() { return autoUpdatePolicy; } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Channel channel = (Channel) o; + return Objects.equals(acceptedItemType, channel.acceptedItemType) && kind == channel.kind + && Objects.equals(uid, channel.uid) && Objects.equals(channelTypeUID, channel.channelTypeUID) + && Objects.equals(label, channel.label) && Objects.equals(description, channel.description) + && Objects.equals(configuration, channel.configuration) + && Objects.equals(properties, channel.properties) && Objects.equals(defaultTags, channel.defaultTags) + && autoUpdatePolicy == channel.autoUpdatePolicy; + } + + @Override + public int hashCode() { + return Objects.hash(acceptedItemType, kind, uid, channelTypeUID, label, description, configuration, properties, + defaultTags, autoUpdatePolicy); + } } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/util/ThingHelper.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/util/ThingHelper.java index 0fdfd8e122b..c43b7d4de7f 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/util/ThingHelper.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/util/ThingHelper.java @@ -12,10 +12,8 @@ */ package org.openhab.core.thing.util; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -79,24 +77,10 @@ public static boolean equals(Thing a, Thing b) { return false; } // channels - List channelsOfA = a.getChannels(); - List channelsOfB = b.getChannels(); - if (channelsOfA.size() != channelsOfB.size()) { - return false; - } - if (!toString(channelsOfA).equals(toString(channelsOfB))) { - return false; - } - return true; - } + Set channelsOfA = new HashSet<>(a.getChannels()); + Set channelsOfB = new HashSet<>(b.getChannels()); - private static String toString(List channels) { - List strings = new ArrayList<>(channels.size()); - for (Channel channel : channels) { - strings.add(channel.getUID().toString() + '#' + channel.getAcceptedItemType() + '#' + channel.getKind()); - } - Collections.sort(strings); - return String.join(",", strings); + return channelsOfA.equals(channelsOfB); } public static void addChannelsToThing(Thing thing, Collection channels) { diff --git a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/util/ThingHelperTest.java b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/util/ThingHelperTest.java index 71934b6635a..3a5f96e0e85 100644 --- a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/util/ThingHelperTest.java +++ b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/util/ThingHelperTest.java @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.Assertions.*; import java.util.List; +import java.util.Map; import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -142,4 +143,21 @@ public void assertThatNoDuplicateChannelsCanBeAdded() { ChannelBuilder.create(new ChannelUID(thingUID, "channel3"), "").build()) .collect(toList()))); } + + @Test + public void asserThatChannelsWithDifferentConfigurationAreDetectedAsDifferent() { + Thing thingA = ThingBuilder.create(THING_TYPE_UID, THING_UID) + .withChannels(ChannelBuilder.create(new ChannelUID("binding:type:thingId:channel1"), "itemType") + .withConfiguration(new Configuration(Map.of("key", "v1"))).build()) + .withConfiguration(new Configuration()).build(); + + assertTrue(ThingHelper.equals(thingA, thingA)); + + Thing thingB = ThingBuilder.create(THING_TYPE_UID, THING_UID) + .withChannels(ChannelBuilder.create(new ChannelUID("binding:type:thingId:channel1"), "itemType") + .withConfiguration(new Configuration(Map.of("key", "v2"))).build()) + .withConfiguration(new Configuration()).build(); + + assertFalse(ThingHelper.equals(thingA, thingB)); + } } From 9bbe4567f8d2f1dbfb72a9bf19903122f40989ad Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 15 Apr 2023 09:25:14 +0200 Subject: [PATCH 041/126] Fix wrong event subscription in GroupStateTrigger and ItemStateTrigger (#3533) The `GroupStateTrigger` subscibed to `ItemStateEvent` instead of `ItemStateUpdatedEvent` (while `ItemStateTrigger` uses `ItemStateUpdatedEvent`), this is confusiing and results in different behavior of both triggers. The `ItemStateTrigger` did not subscribe to `GroupStateUpdatedEvent` which results in the trigger not firing for group state UPDATES. This is wrong because it fires for item UPDATES and for both, group and item CHANGES. Signed-off-by: Jan N. Klug --- .../internal/module/handler/GroupStateTriggerHandler.java | 8 ++++---- .../internal/module/handler/ItemStateTriggerHandler.java | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupStateTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupStateTriggerHandler.java index 8507ad5f4fa..75a2f0aca06 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupStateTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupStateTriggerHandler.java @@ -29,7 +29,7 @@ import org.openhab.core.items.events.ItemAddedEvent; import org.openhab.core.items.events.ItemRemovedEvent; import org.openhab.core.items.events.ItemStateChangedEvent; -import org.openhab.core.items.events.ItemStateEvent; +import org.openhab.core.items.events.ItemStateUpdatedEvent; import org.openhab.core.types.State; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; @@ -72,7 +72,7 @@ public GroupStateTriggerHandler(Trigger module, String ruleUID, BundleContext bu this.state = (String) module.getConfiguration().get(CFG_STATE); this.previousState = (String) module.getConfiguration().get(CFG_PREVIOUS_STATE); if (UPDATE_MODULE_TYPE_ID.equals(module.getTypeUID())) { - this.types = Set.of(ItemStateEvent.TYPE, ItemAddedEvent.TYPE, ItemRemovedEvent.TYPE); + this.types = Set.of(ItemStateUpdatedEvent.TYPE, ItemAddedEvent.TYPE, ItemRemovedEvent.TYPE); } else { this.types = Set.of(ItemStateChangedEvent.TYPE, GroupItemStateChangedEvent.TYPE, ItemAddedEvent.TYPE, ItemRemovedEvent.TYPE); @@ -113,8 +113,8 @@ public void receive(Event event) { TriggerHandlerCallback cb = (TriggerHandlerCallback) callback; logger.trace("Received Event: Source: {} Topic: {} Type: {} Payload: {}", event.getSource(), event.getTopic(), event.getType(), event.getPayload()); - if (event instanceof ItemStateEvent && UPDATE_MODULE_TYPE_ID.equals(module.getTypeUID())) { - ItemStateEvent isEvent = (ItemStateEvent) event; + if (event instanceof ItemStateUpdatedEvent && UPDATE_MODULE_TYPE_ID.equals(module.getTypeUID())) { + ItemStateUpdatedEvent isEvent = (ItemStateUpdatedEvent) event; String itemName = isEvent.getItemName(); Item item = itemRegistry.get(itemName); if (item != null && item.getGroupNames().contains(groupName)) { diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateTriggerHandler.java index c931b4ca6ec..15b4d0a572f 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateTriggerHandler.java @@ -28,6 +28,7 @@ import org.openhab.core.events.TopicPrefixEventFilter; import org.openhab.core.items.ItemRegistry; import org.openhab.core.items.events.GroupItemStateChangedEvent; +import org.openhab.core.items.events.GroupStateUpdatedEvent; import org.openhab.core.items.events.ItemAddedEvent; import org.openhab.core.items.events.ItemRemovedEvent; import org.openhab.core.items.events.ItemStateChangedEvent; @@ -77,7 +78,8 @@ public ItemStateTriggerHandler(Trigger module, String ruleUID, BundleContext bun this.previousState = (String) module.getConfiguration().get(CFG_PREVIOUS_STATE); this.ruleUID = ruleUID; if (UPDATE_MODULE_TYPE_ID.equals(module.getTypeUID())) { - this.types = Set.of(ItemStateUpdatedEvent.TYPE, ItemAddedEvent.TYPE, ItemRemovedEvent.TYPE); + this.types = Set.of(ItemStateUpdatedEvent.TYPE, GroupStateUpdatedEvent.TYPE, ItemAddedEvent.TYPE, + ItemRemovedEvent.TYPE); } else { this.types = Set.of(ItemStateChangedEvent.TYPE, GroupItemStateChangedEvent.TYPE, ItemAddedEvent.TYPE, ItemRemovedEvent.TYPE); From 9e334497f9b25789acd8d956d834fd1475fb0cc6 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Sat, 15 Apr 2023 19:24:12 +1000 Subject: [PATCH 042/126] Add methods to get Semantic tag synonyms and description (#3553) Signed-off-by: Jimmy Tanagra --- .../openhab/core/semantics/SemanticTags.java | 32 ++++++++++++++++--- .../core/semantics/SemanticTagsTest.java | 21 ++++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTags.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTags.java index a6087ad1e0b..58982dab6ce 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTags.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTags.java @@ -90,13 +90,13 @@ public static List> getByLabelOrSynonym(String tagLabelOrSy public static List getLabelAndSynonyms(Class tag, Locale locale) { ResourceBundle rb = ResourceBundle.getBundle(TAGS_BUNDLE_NAME, locale, Control.getNoFallbackControl(Control.FORMAT_PROPERTIES)); + TagInfo tagInfo = tag.getAnnotation(TagInfo.class); try { - String entry = rb.getString(tag.getAnnotation(TagInfo.class).id()); + String entry = rb.getString(tagInfo.id()); return List.of(entry.toLowerCase(locale).split(",")); } catch (MissingResourceException e) { - TagInfo tagInfo = tag.getAnnotation(TagInfo.class); Stream label = Stream.of(tagInfo.label()); - Stream synonyms = Stream.of(tagInfo.synonyms().split(",")); + Stream synonyms = Stream.of(tagInfo.synonyms().split(",")).map(String::trim); return Stream.concat(label, synonyms).map(s -> s.toLowerCase(locale)).distinct().toList(); } } @@ -104,18 +104,40 @@ public static List getLabelAndSynonyms(Class tag, Locale public static String getLabel(Class tag, Locale locale) { ResourceBundle rb = ResourceBundle.getBundle(TAGS_BUNDLE_NAME, locale, Control.getNoFallbackControl(Control.FORMAT_PROPERTIES)); + TagInfo tagInfo = tag.getAnnotation(TagInfo.class); try { - String entry = rb.getString(tag.getAnnotation(TagInfo.class).id()); + String entry = rb.getString(tagInfo.id()); if (entry.contains(",")) { return entry.substring(0, entry.indexOf(",")); } else { return entry; } } catch (MissingResourceException e) { - return tag.getAnnotation(TagInfo.class).label(); + return tagInfo.label(); } } + public static List getSynonyms(Class tag, Locale locale) { + ResourceBundle rb = ResourceBundle.getBundle(TAGS_BUNDLE_NAME, locale, + Control.getNoFallbackControl(Control.FORMAT_PROPERTIES)); + String synonyms = ""; + TagInfo tagInfo = tag.getAnnotation(TagInfo.class); + try { + String entry = rb.getString(tagInfo.id()); + int start = entry.indexOf(",") + 1; + if (start > 0 && entry.length() > start) { + synonyms = entry.substring(start); + } + } catch (MissingResourceException e) { + synonyms = tagInfo.synonyms(); + } + return Stream.of(synonyms.split(",")).map(String::trim).toList(); + } + + public static String getDescription(Class tag, Locale locale) { + return tag.getAnnotation(TagInfo.class).description(); + } + /** * Determines the semantic type of an {@link Item} i.e. a sub-type of {@link Location}, {@link Equipment} or * {@link Point}. diff --git a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticTagsTest.java b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticTagsTest.java index c6c37d81834..67c44ce5b6f 100644 --- a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticTagsTest.java +++ b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticTagsTest.java @@ -12,6 +12,8 @@ */ package org.openhab.core.semantics; +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.*; import java.util.Locale; @@ -30,7 +32,9 @@ import org.openhab.core.semantics.model.location.Room; import org.openhab.core.semantics.model.point.Measurement; import org.openhab.core.semantics.model.point.Points; +import org.openhab.core.semantics.model.property.Light; import org.openhab.core.semantics.model.property.Properties; +import org.openhab.core.semantics.model.property.SoundVolume; import org.openhab.core.semantics.model.property.Temperature; /** @@ -83,6 +87,23 @@ public void testByLabelOrSynonym() { assertEquals(Bathroom.class, SemanticTags.getByLabelOrSynonym("Badezimmer", Locale.GERMAN).iterator().next()); } + @Test + public void testGetLabel() { + assertEquals("Kitchen", SemanticTags.getLabel(Kitchen.class, Locale.ENGLISH)); + assertEquals("Sound Volume", SemanticTags.getLabel(SoundVolume.class, Locale.ENGLISH)); + } + + @Test + public void testGetSynonyms() { + assertThat(SemanticTags.getSynonyms(Light.class, Locale.ENGLISH), hasItems("Lights", "Lighting")); + } + + @Test + public void testGetDescription() { + Class tag = SemanticTags.add("TestDesc", Light.class, null, null, "Test Description"); + assertEquals("Test Description", SemanticTags.getDescription(tag, Locale.ENGLISH)); + } + @Test public void testGetSemanticType() { assertEquals(Bathroom.class, SemanticTags.getSemanticType(locationItem)); From 7a9b76dfdfedaa3b5bd3016961378bcc01ca654d Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Sun, 16 Apr 2023 00:03:45 +1000 Subject: [PATCH 043/126] Fix division-by-zero error in Persistence average (#3556) * Fix division-by-zero error in Persistence average * Add test Signed-off-by: Jimmy Tanagra --- .../persistence/extensions/PersistenceExtensions.java | 3 ++- .../extensions/PersistenceExtensionsTest.java | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java index b0807792a33..1b8984c8109 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java @@ -824,7 +824,8 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi if (firstTimestamp != null) { BigDecimal totalDuration = BigDecimal.valueOf(Duration.between(firstTimestamp, endTime).toMillis()); - return new DecimalType(sum.divide(totalDuration, MathContext.DECIMAL64)); + return totalDuration.signum() == 0 ? null + : new DecimalType(sum.divide(totalDuration, MathContext.DECIMAL64)); } return null; diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/PersistenceExtensionsTest.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/PersistenceExtensionsTest.java index 6d8ea45d572..9730380063f 100644 --- a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/PersistenceExtensionsTest.java +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/PersistenceExtensionsTest.java @@ -18,6 +18,7 @@ import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.closeTo; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -680,6 +681,15 @@ public void testAverageSinceSwitch() { assertThat(average, is(nullValue())); } + @Test + public void testAverageBetweenZeroDuration() { + ZonedDateTime now = ZonedDateTime.now(); + assertDoesNotThrow( + () -> PersistenceExtensions.averageBetween(quantityItem, now, now, TestPersistenceService.ID)); + assertThat(PersistenceExtensions.averageBetween(quantityItem, now, now, TestPersistenceService.ID), + is(nullValue())); + } + @Test public void testSumSinceDecimalType() { DecimalType sum = PersistenceExtensions.sumSince(numberItem, From fdea66ae7573db0f028965138c8cdb8a433e1a38 Mon Sep 17 00:00:00 2001 From: Sami Salonen Date: Sat, 15 Apr 2023 17:29:36 +0300 Subject: [PATCH 044/126] [modbus] Fix possible NPE when connection construction fails (#3490) Signed-off-by: Sami Salonen --- .../modbus/internal/ModbusConnectionPool.java | 7 +-- .../modbus/internal/ModbusManagerImpl.java | 14 +++--- .../modbus/internal/ModbusPoolConfig.java | 2 +- .../ModbusSlaveConnectionFactoryImpl.java | 44 ++++++++++++------- 4 files changed, 40 insertions(+), 27 deletions(-) diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusConnectionPool.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusConnectionPool.java index d084a105145..8f4e8373210 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusConnectionPool.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusConnectionPool.java @@ -30,14 +30,15 @@ * */ @NonNullByDefault -public class ModbusConnectionPool extends GenericKeyedObjectPool { +public class ModbusConnectionPool extends GenericKeyedObjectPool { - public ModbusConnectionPool(KeyedPooledObjectFactory factory) { + public ModbusConnectionPool( + KeyedPooledObjectFactory factory) { super(factory, new ModbusPoolConfig()); } @Override - public void setConfig(@Nullable GenericKeyedObjectPoolConfig conf) { + public void setConfig(@Nullable GenericKeyedObjectPoolConfig<@Nullable ModbusSlaveConnection> conf) { if (conf == null) { return; } else if (!(conf instanceof ModbusPoolConfig)) { diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusManagerImpl.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusManagerImpl.java index 6af5e320582..b0ef72b900f 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusManagerImpl.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusManagerImpl.java @@ -347,7 +347,7 @@ public void accept(AggregateStopWatch timer, WriteTask task, ModbusSlaveConnecti * - https://community.openhab.org/t/connection-pooling-in-modbus-binding/5246/ */ - private volatile @Nullable KeyedObjectPool connectionPool; + private volatile @Nullable KeyedObjectPool connectionPool; private volatile @Nullable ModbusSlaveConnectionFactoryImpl connectionFactory; private volatile Map> scheduledPollTasks = new ConcurrentHashMap<>(); /** @@ -360,7 +360,7 @@ public void accept(AggregateStopWatch timer, WriteTask task, ModbusSlaveConnecti private void constructConnectionPool() { ModbusSlaveConnectionFactoryImpl connectionFactory = new ModbusSlaveConnectionFactoryImpl( DEFAULT_POOL_CONFIGURATION); - GenericKeyedObjectPool genericKeyedObjectPool = new ModbusConnectionPool( + GenericKeyedObjectPool genericKeyedObjectPool = new ModbusConnectionPool( connectionFactory); genericKeyedObjectPool.setSwallowedExceptionListener(new SwallowedExceptionListener() { @@ -379,7 +379,7 @@ public void onSwallowException(@Nullable Exception e) { private Optional borrowConnection(ModbusSlaveEndpoint endpoint) { Optional connection = Optional.empty(); - KeyedObjectPool pool = connectionPool; + KeyedObjectPool pool = connectionPool; if (pool == null) { return connection; } @@ -405,7 +405,7 @@ private Optional borrowConnection(ModbusSlaveEndpoint end } private void invalidate(ModbusSlaveEndpoint endpoint, Optional connection) { - KeyedObjectPool pool = connectionPool; + KeyedObjectPool pool = connectionPool; if (pool == null) { return; } @@ -423,7 +423,7 @@ private void invalidate(ModbusSlaveEndpoint endpoint, Optional connection) { - KeyedObjectPool pool = connectionPool; + KeyedObjectPool pool = connectionPool; if (pool == null) { return; } @@ -454,7 +454,7 @@ private void returnConnection(ModbusSlaveEndpoint endpoint, Optional, T extends TaskWithEndpoint> Optional getConnection( AggregateStopWatch timer, boolean oneOffTask, @NonNull T task) throws PollTaskUnregistered { - KeyedObjectPool connectionPool = this.connectionPool; + KeyedObjectPool connectionPool = this.connectionPool; if (connectionPool == null) { return Optional.empty(); } @@ -983,7 +983,7 @@ protected void activate(Map configProperties) { @Deactivate protected void deactivate() { synchronized (this) { - KeyedObjectPool connectionPool = this.connectionPool; + KeyedObjectPool connectionPool = this.connectionPool; if (connectionPool != null) { for (ModbusCommunicationInterface commInterface : this.communicationInterfaces) { try { diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusPoolConfig.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusPoolConfig.java index 6e8c61c7616..8707e30191d 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusPoolConfig.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusPoolConfig.java @@ -32,7 +32,7 @@ * */ @NonNullByDefault -public class ModbusPoolConfig extends GenericKeyedObjectPoolConfig { +public class ModbusPoolConfig extends GenericKeyedObjectPoolConfig<@Nullable ModbusSlaveConnection> { @SuppressWarnings("unused") private EvictionPolicy evictionPolicy = new DefaultEvictionPolicy<>(); diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/pooling/ModbusSlaveConnectionFactoryImpl.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/pooling/ModbusSlaveConnectionFactoryImpl.java index 1c56f17ae57..2ffca70177d 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/pooling/ModbusSlaveConnectionFactoryImpl.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/pooling/ModbusSlaveConnectionFactoryImpl.java @@ -59,14 +59,14 @@ */ @NonNullByDefault public class ModbusSlaveConnectionFactoryImpl - extends BaseKeyedPooledObjectFactory { + extends BaseKeyedPooledObjectFactory { - class PooledConnection extends DefaultPooledObject { + class PooledConnection extends DefaultPooledObject<@Nullable ModbusSlaveConnection> { private volatile long lastConnected; private volatile @Nullable ModbusSlaveEndpoint endpoint; - public PooledConnection(ModbusSlaveConnection object) { + public PooledConnection(@Nullable ModbusSlaveConnection object) { super(object); } @@ -97,6 +97,9 @@ public boolean maybeResetConnection(String activityName) { long localLastConnected = lastConnected; ModbusSlaveConnection connection = getObject(); + if (connection == null) { + return false; + } EndpointPoolConfiguration configuration = getEndpointPoolConfiguration(localEndpoint); long reconnectAfterMillis = configuration.getReconnectAfterMillis(); @@ -147,8 +150,8 @@ public ModbusSlaveConnectionFactoryImpl( } @Override - public ModbusSlaveConnection create(ModbusSlaveEndpoint endpoint) throws Exception { - return endpoint.accept(new ModbusSlaveEndpointVisitor() { + public @Nullable ModbusSlaveConnection create(ModbusSlaveEndpoint endpoint) throws Exception { + return endpoint.accept(new ModbusSlaveEndpointVisitor<@Nullable ModbusSlaveConnection>() { @Override public @Nullable ModbusSlaveConnection visit(ModbusSerialSlaveEndpoint modbusSerialSlavePoolingKey) { SerialConnection connection = new SerialConnection(modbusSerialSlavePoolingKey.getSerialParameters()); @@ -183,27 +186,34 @@ public ModbusSlaveConnection create(ModbusSlaveEndpoint endpoint) throws Excepti } @Override - public PooledObject wrap(ModbusSlaveConnection connection) { + public PooledObject<@Nullable ModbusSlaveConnection> wrap(@Nullable ModbusSlaveConnection connection) { return new PooledConnection(connection); } @Override - public void destroyObject(ModbusSlaveEndpoint endpoint, @Nullable PooledObject obj) { + public void destroyObject(ModbusSlaveEndpoint endpoint, + @Nullable PooledObject<@Nullable ModbusSlaveConnection> obj) { if (obj == null) { return; } - logger.trace("destroyObject for connection {} and endpoint {} -> closing the connection", obj.getObject(), - endpoint); - obj.getObject().resetConnection(); + ModbusSlaveConnection connection = obj.getObject(); + if (connection == null) { + return; + } + logger.trace("destroyObject for connection {} and endpoint {} -> closing the connection", connection, endpoint); + connection.resetConnection(); } @Override - public void activateObject(ModbusSlaveEndpoint endpoint, @Nullable PooledObject obj) - throws Exception { + public void activateObject(ModbusSlaveEndpoint endpoint, + @Nullable PooledObject<@Nullable ModbusSlaveConnection> obj) throws Exception { if (obj == null) { return; } ModbusSlaveConnection connection = obj.getObject(); + if (connection == null) { + return; + } try { EndpointPoolConfiguration config = getEndpointPoolConfiguration(endpoint); if (!connection.isConnected()) { @@ -226,7 +236,8 @@ public void activateObject(ModbusSlaveEndpoint endpoint, @Nullable PooledObject< } @Override - public void passivateObject(ModbusSlaveEndpoint endpoint, @Nullable PooledObject obj) { + public void passivateObject(ModbusSlaveEndpoint endpoint, + @Nullable PooledObject<@Nullable ModbusSlaveConnection> obj) { if (obj == null) { return; } @@ -238,8 +249,9 @@ public void passivateObject(ModbusSlaveEndpoint endpoint, @Nullable PooledObject } @Override - public boolean validateObject(ModbusSlaveEndpoint key, @Nullable PooledObject p) { - boolean valid = p != null && p.getObject().isConnected(); + public boolean validateObject(ModbusSlaveEndpoint key, @Nullable PooledObject<@Nullable ModbusSlaveConnection> p) { + @SuppressWarnings("null") // p.getObject() cannot be null due to short-circuiting boolean condition + boolean valid = p != null && p.getObject() != null && p.getObject().isConnected(); ModbusSlaveConnection slaveConnection = p != null ? p.getObject() : null; logger.trace("Validating endpoint {} connection {} -> {}", key, slaveConnection, valid); return valid; @@ -272,7 +284,7 @@ public EndpointPoolConfiguration getEndpointPoolConfiguration(ModbusSlaveEndpoin .orElseGet(() -> defaultPoolConfigurationFactory.apply(endpoint)); } - private void tryConnect(ModbusSlaveEndpoint endpoint, PooledObject obj, + private void tryConnect(ModbusSlaveEndpoint endpoint, PooledObject<@Nullable ModbusSlaveConnection> obj, ModbusSlaveConnection connection, EndpointPoolConfiguration config) throws Exception { if (connection.isConnected()) { return; From 769aa562a10f9e007ff08723b31b60da240dc8c4 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Sun, 16 Apr 2023 21:43:59 +0200 Subject: [PATCH 045/126] Code cleanup: Use Java 17 features (#3522) Signed-off-by: Holger Friedrich --- .../marketplace/test/TestAddonService.java | 2 + .../openhab/core/addon/AddonEventFactory.java | 6 +- .../org/openhab/core/audio/AudioFormat.java | 3 +- .../core/audio/internal/AudioServlet.java | 4 +- .../internal/webaudio/WebAudioAudioSink.java | 7 +- .../core/audio/utils/ToneSynthesizer.java | 2 +- .../internal/JaasAuthenticationProvider.java | 12 +-- .../internal/OAuthStoreHandlerImpl.java | 20 ++--- .../internal/cipher/SymmetricKeyCipher.java | 8 +- .../internal/MediaActionTypeProvider.java | 12 +-- .../internal/MediaModuleHandlerFactory.java | 8 +- .../media/internal/PlayActionHandler.java | 2 +- .../media/internal/SayActionHandler.java | 2 +- .../internal/SynthesizeActionHandler.java | 2 +- .../AbstractScriptedModuleHandlerFactory.java | 26 +++--- .../internal/CacheScriptExtension.java | 12 +-- .../shared/ScriptedAutomationManager.java | 9 +- .../script/ScriptDependencyListener.java | 1 + .../internal/ScriptEngineManagerImpl.java | 9 +- .../defaultscope/ItemRegistryDelegate.java | 4 +- .../defaultscope/ScriptBusEventImpl.java | 3 +- .../factory/ScriptModuleHandlerFactory.java | 10 +-- .../handler/AbstractScriptModuleHandler.java | 4 +- .../handler/ScriptConditionHandler.java | 4 +- .../rest/internal/ModuleTypeResource.java | 24 +++--- .../automation/dto/ActionTypeDTOMapper.java | 4 +- .../dto/ConditionTypeDTOMapper.java | 4 +- .../automation/dto/TriggerTypeDTOMapper.java | 4 +- .../automation/internal/RuleEngineImpl.java | 15 ++-- .../internal/RuleExecutionSimulator.java | 8 +- .../automation/internal/RuleRegistryImpl.java | 8 +- .../commands/AutomationCommandList.java | 14 ++-- .../automation/internal/commands/Printer.java | 71 ++++++++-------- .../CompositeModuleHandlerFactory.java | 24 +++--- .../factory/CoreModuleHandlerFactory.java | 37 ++++----- .../EphemerisModuleHandlerFactory.java | 4 +- .../handler/AnnotationActionHandler.java | 7 +- .../handler/ChannelEventTriggerHandler.java | 3 +- .../handler/CompareConditionHandler.java | 15 ++-- .../handler/GroupCommandTriggerHandler.java | 14 ++-- .../handler/GroupStateTriggerHandler.java | 18 ++-- .../handler/ItemCommandActionHandler.java | 4 +- .../handler/ItemCommandTriggerHandler.java | 12 +-- .../handler/ItemStateConditionHandler.java | 62 +++++++------- .../handler/ItemStateTriggerHandler.java | 20 +++-- .../handler/ItemStateUpdateActionHandler.java | 4 +- .../module/handler/SystemTriggerHandler.java | 4 +- .../handler/ThingStatusTriggerHandler.java | 11 +-- .../handler/TimerModuleHandlerFactory.java | 22 ++--- .../AnnotatedActionModuleTypeProvider.java | 11 +-- .../parser/gson/ModuleTypeGSONParser.java | 24 +++--- .../AutomationResourceBundlesTracker.java | 8 +- .../automation/internal/provider/Vendor.java | 3 +- .../provider/i18n/ModuleI18nUtil.java | 12 +-- .../i18n/ModuleTypeI18nServiceImpl.java | 36 ++++---- .../internal/type/ModuleTypeRegistryImpl.java | 42 ++++------ .../AnnotationActionModuleTypeHelper.java | 8 +- ...nnotatedThingActionModuleTypeProvider.java | 7 +- .../util/ConfigurationNormalizer.java | 2 +- .../core/automation/util/ModuleBuilder.java | 12 +-- .../automation/util/ReferenceResolver.java | 27 +++--- .../core/ConfigDescriptionParameter.java | 2 +- .../core/config/core/ConfigParser.java | 10 +-- .../openhab/core/config/core/ConfigUtil.java | 4 +- .../core/config/core/Configuration.java | 2 +- .../config/core/ConfigurationSerializer.java | 16 ++-- .../config/core/OrderingMapSerializer.java | 4 +- .../i18n/I18nConfigOptionsProvider.java | 12 +-- .../normalization/ListNormalizer.java | 10 +-- .../core/xml/ConfigDescriptionConverter.java | 8 +- .../ConfigDescriptionParameterConverter.java | 6 +- .../core/xml/util/ConverterValueMap.java | 4 +- .../config/core/xml/util/NodeIterator.java | 18 ++-- .../core/config/core/xml/util/NodeValue.java | 4 +- .../discovery/AbstractDiscoveryService.java | 6 +- .../config/discovery/DiscoveryResultFlag.java | 2 +- .../discovery/internal/PersistentInbox.java | 6 +- .../AbstractDiscoveryServiceTest.java | 8 +- .../dispatch/internal/ConfigDispatcher.java | 2 +- .../internal/EphemerisManagerImpl.java | 4 +- .../openhab/core/io/bin2json/Bin2Json.java | 70 ++++++++-------- .../ItemConsoleCommandExtension.java | 6 +- .../MetadataConsoleCommandExtension.java | 2 +- .../auth/internal/AuthenticationHandler.java | 4 +- .../auth/internal/AuthorizePageServlet.java | 3 +- .../internal/CreateAPITokenPageServlet.java | 5 +- .../internal/CertificateGenerator.java | 2 +- .../openhab/core/io/net/http/HttpUtil.java | 2 +- .../net/http/internal/TrustManagerUtil.java | 4 +- .../http/internal/WebClientFactoryImpl.java | 4 +- .../core/io/net/exec/ExecUtilTest.java | 2 +- .../io/rest/auth/internal/AuthFilter.java | 1 - .../core/io/rest/auth/internal/JwtHelper.java | 3 +- .../io/rest/auth/internal/TokenResource.java | 4 +- .../core/internal/GsonMessageBodyWriter.java | 4 +- .../internal/channel/ChannelTypeResource.java | 4 +- .../config/ConfigDescriptionResource.java | 6 +- .../rest/core/internal/item/ItemResource.java | 10 +-- .../internal/profile/ProfileTypeResource.java | 6 +- .../service/ConfigurableServiceResource.java | 12 ++- .../rest/core/item/EnrichedItemDTOMapper.java | 3 +- .../sitemap/SitemapSubscriptionService.java | 10 +-- .../sitemap/internal/PageChangeListener.java | 31 ++++--- .../sitemap/internal/SitemapResource.java | 77 ++++++++--------- .../openhab/core/io/rest/sse/SseResource.java | 6 +- .../internal/SseItemStatesEventBuilder.java | 7 +- .../io/rest/sse/internal/util/SseUtil.java | 5 +- .../openhab/core/io/rest/JSONResponse.java | 4 +- .../io/rest/internal/filter/CorsFilter.java | 2 +- .../transport/modbus/ModbusBitUtilities.java | 26 +++--- .../modbus/internal/ModbusLibraryWrapper.java | 12 +-- .../modbus/internal/ModbusManagerImpl.java | 82 +++++++++---------- .../internal/ModbusSlaveIOExceptionImpl.java | 10 +-- .../BitUtilitiesCommandToRegistersTest.java | 14 ++-- ...UtilitiesExtractIndividualMethodsTest.java | 6 +- ...tilitiesExtractStateFromRegistersTest.java | 8 +- .../test/BitUtilitiesExtractStringTest.java | 12 ++- .../modbus/test/IntegrationTestSupport.java | 3 +- .../mqtt/MqttBrokerConnectionEx.java | 4 +- .../internal/SerialPortIdentifierImpl.java | 4 +- .../internal/SerialPortIdentifierImpl.java | 4 +- .../core/io/websocket/ItemEventUtility.java | 8 +- .../core/karaf/internal/FeatureInstaller.java | 8 +- .../jaas/ManagedUserBackingEngine.java | 6 +- .../core/internal/ModelRepositoryImpl.java | 10 +-- .../ValueTypeToStringConverter.java | 11 ++- .../item/internal/GenericItemProvider.java | 3 +- .../model/lazygen/LazyStandaloneSetup.java | 3 +- .../internal/PersistenceModelManager.java | 12 +-- .../persistence/scoping/GlobalStrategies.java | 6 +- .../PersistenceGlobalScopeProvider.java | 5 +- .../runtime/internal/DSLRuleProvider.java | 49 ++++------- .../runtime/internal/RuleContextHelper.java | 4 +- .../internal/engine/DSLScriptEngine.java | 17 ++-- .../runtime/internal/engine/ScriptImpl.java | 12 +-- .../core/model/script/actions/BusEvent.java | 20 ++--- .../core/model/script/actions/HTTP.java | 28 +++---- .../core/model/script/actions/Log.java | 8 +- .../engine/action/ThingActionService.java | 3 +- .../script/jvmmodel/ScriptTypeComputer.java | 4 +- .../script/scoping/ActionClassLoader.java | 5 +- .../sitemap/internal/SitemapProviderImpl.java | 4 +- .../core/persistence/dto/ItemHistoryDTO.java | 4 +- .../extensions/PersistenceExtensions.java | 7 +- .../internal/PersistenceManagerImpl.java | 37 +++------ .../extensions/TestPersistenceService.java | 2 +- .../internal/SemanticsMetadataProvider.java | 11 ++- .../internal/SemanticsServiceImpl.java | 6 +- .../json/internal/JsonStorageTest.java | 2 +- .../storage/json/internal/MigrationTest.java | 3 +- .../handler/MagicDelayedOnlineHandler.java | 5 +- .../modules/MagicThingActionsService.java | 4 +- .../firmware/MagicFirmwareProvider.java | 5 +- .../core/test/SyntheticBundleInstaller.java | 8 +- .../openhab/core/test/java/JavaOSGiTest.java | 4 +- .../org/openhab/core/test/java/JavaTest.java | 2 +- .../org/openhab/core/thing/ThingStatus.java | 2 +- .../binding/BaseThingHandlerFactory.java | 7 +- .../thing/binding/firmware/ProgressStep.java | 2 +- .../core/thing/firmware/FirmwareStatus.java | 2 +- .../thing/firmware/FirmwareUpdateResult.java | 2 +- .../thing/internal/AutoUpdateManager.java | 3 +- .../core/thing/internal/BridgeImpl.java | 4 +- .../thing/internal/CommunicationManager.java | 80 +++++++++--------- .../ThingConfigDescriptionAliasProvider.java | 8 +- .../thing/internal/ThingFactoryHelper.java | 8 +- .../core/thing/internal/ThingManagerImpl.java | 2 +- .../thing/internal/ThingRegistryImpl.java | 16 ++-- .../console/LinkConsoleCommandExtension.java | 2 +- .../firmware/FirmwareUpdateServiceImpl.java | 8 +- .../firmware/ProgressCallbackImpl.java | 3 +- ...mChannelLinkConfigDescriptionProvider.java | 4 +- .../SystemHysteresisStateProfile.java | 23 +++--- .../profiles/SystemOffsetProfile.java | 13 ++- .../profiles/SystemRangeStateProfile.java | 23 +++--- .../profiles/TimestampOffsetProfile.java | 10 +-- .../openhab/core/thing/link/AbstractLink.java | 3 +- .../core/thing/link/ItemChannelLink.java | 3 +- .../ProfileTypeI18nLocalizationService.java | 10 +-- .../openhab/core/thing/util/ThingHelper.java | 4 +- .../AbstractDescriptionTypeConverter.java | 4 +- .../xml/internal/ChannelTypeConverter.java | 12 +-- .../xml/internal/ThingTypeXmlProvider.java | 9 +- .../SystemHysteresisStateProfileTest.java | 2 +- .../profiles/SystemRangeStateProfileTest.java | 2 +- .../internal/profiles/ToggleProfileTest.java | 2 +- .../internal/ThingDescriptionReaderTest.java | 12 +-- .../core/ui/icon/internal/IconServlet.java | 4 +- .../openhab/core/ui/chart/ChartProvider.java | 2 +- .../DefaultChartProvider.java | 14 ++-- .../UIComponentSitemapProvider.java | 5 +- .../ui/internal/items/ItemUIRegistryImpl.java | 37 ++++----- .../internal/proxy/ProxyServletService.java | 8 +- .../core/voice/internal/DialogProcessor.java | 9 +- .../org/openhab/core/voice/text/ASTNode.java | 2 +- .../text/AbstractRuleBasedInterpreter.java | 46 +++++------ .../voice/text/ExpressionAlternatives.java | 4 +- .../voice/text/ExpressionCardinality.java | 4 +- .../core/voice/text/ExpressionSequence.java | 4 +- .../internal/cache/TTSLRUCacheImplTest.java | 3 +- .../core/cache/ByteArrayFileCacheTest.java | 2 +- 201 files changed, 959 insertions(+), 1185 deletions(-) diff --git a/bundles/org.openhab.core.addon.marketplace/src/test/java/org/openhab/core/addon/marketplace/test/TestAddonService.java b/bundles/org.openhab.core.addon.marketplace/src/test/java/org/openhab/core/addon/marketplace/test/TestAddonService.java index 8b09cf4bff7..73ffa5cead0 100644 --- a/bundles/org.openhab.core.addon.marketplace/src/test/java/org/openhab/core/addon/marketplace/test/TestAddonService.java +++ b/bundles/org.openhab.core.addon.marketplace/src/test/java/org/openhab/core/addon/marketplace/test/TestAddonService.java @@ -60,10 +60,12 @@ protected BundleVersion getCoreVersion() { return new BundleVersion("3.2.0"); } + @Override public void addAddonHandler(MarketplaceAddonHandler handler) { this.addonHandlers.add(handler); } + @Override public void removeAddonHandler(MarketplaceAddonHandler handler) { this.addonHandlers.remove(handler); } diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonEventFactory.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonEventFactory.java index 7fd9fe64d62..9465d7fa17c 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonEventFactory.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonEventFactory.java @@ -55,12 +55,10 @@ protected Event createEventByType(String eventType, String topic, String payload throws Exception { if (topic.endsWith(ADDON_FAILURE_EVENT_TOPIC_POSTFIX)) { String[] properties = deserializePayload(payload, String[].class); - Event event = new AddonEvent(topic, payload, properties[0], properties[1]); - return event; + return new AddonEvent(topic, payload, properties[0], properties[1]); } else { String id = deserializePayload(payload, String.class); - Event event = new AddonEvent(topic, payload, id); - return event; + return new AddonEvent(topic, payload, id); } } diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioFormat.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioFormat.java index 2d1769eaa88..2a6942b6b97 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioFormat.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioFormat.java @@ -435,8 +435,7 @@ public boolean isCompatible(@Nullable AudioFormat audioFormat) { @Override public boolean equals(@Nullable Object obj) { - if (obj instanceof AudioFormat) { - AudioFormat format = (AudioFormat) obj; + if (obj instanceof AudioFormat format) { if (!(null == getCodec() ? null == format.getCodec() : getCodec().equals(format.getCodec()))) { return false; } diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/AudioServlet.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/AudioServlet.java index 8a704810bbd..9bcee8cef5b 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/AudioServlet.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/AudioServlet.java @@ -120,8 +120,8 @@ private void tryClose(@Nullable AudioStream stream) { } // try to set the content-length, if possible - if (stream instanceof FixedLengthAudioStream) { - final long size = ((FixedLengthAudioStream) stream).length(); + if (stream instanceof FixedLengthAudioStream audioStream) { + final long size = audioStream.length(); resp.setContentLength((int) size); } diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/webaudio/WebAudioAudioSink.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/webaudio/WebAudioAudioSink.java index 8227f10de77..7c9299b5847 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/webaudio/WebAudioAudioSink.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/webaudio/WebAudioAudioSink.java @@ -72,14 +72,13 @@ public void process(@Nullable AudioStream audioStream) } try (AudioStream stream = audioStream) { logger.debug("Received audio stream of format {}", audioStream.getFormat()); - if (audioStream instanceof URLAudioStream) { + if (audioStream instanceof URLAudioStream urlAudioStream) { // it is an external URL, so we can directly pass this on. - URLAudioStream urlAudioStream = (URLAudioStream) audioStream; sendEvent(urlAudioStream.getURL()); - } else if (audioStream instanceof FixedLengthAudioStream) { + } else if (audioStream instanceof FixedLengthAudioStream lengthAudioStream) { // we need to serve it for a while and make it available to multiple clients, hence only // FixedLengthAudioStreams are supported. - sendEvent(audioHTTPServer.serve((FixedLengthAudioStream) audioStream, 10).toString()); + sendEvent(audioHTTPServer.serve(lengthAudioStream, 10)); } else { throw new UnsupportedAudioStreamException( "Web audio sink can only handle FixedLengthAudioStreams and URLAudioStreams.", diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/utils/ToneSynthesizer.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/utils/ToneSynthesizer.java index ac47ca828df..c6c7f613289 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/utils/ToneSynthesizer.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/utils/ToneSynthesizer.java @@ -82,7 +82,7 @@ public static List parseMelody(String melody) throws ParseException { case 1: var note = noteTextParts[0]; int octaves = (int) note.chars().filter(ch -> ch == '\'').count(); - note = note.replaceAll("'", ""); + note = note.replace("'", ""); var noteObj = Note.fromString(note); if (noteObj.isPresent()) { melodySounds.add(noteTone(noteObj.get(), soundMillis, octaves)); diff --git a/bundles/org.openhab.core.auth.jaas/src/main/java/org/openhab/core/auth/jaas/internal/JaasAuthenticationProvider.java b/bundles/org.openhab.core.auth.jaas/src/main/java/org/openhab/core/auth/jaas/internal/JaasAuthenticationProvider.java index 42735ddce03..2249031139b 100644 --- a/bundles/org.openhab.core.auth.jaas/src/main/java/org/openhab/core/auth/jaas/internal/JaasAuthenticationProvider.java +++ b/bundles/org.openhab.core.auth.jaas/src/main/java/org/openhab/core/auth/jaas/internal/JaasAuthenticationProvider.java @@ -81,10 +81,10 @@ public Authentication authenticate(final Credentials credentials) throws Authent public void handle(@NonNullByDefault({}) Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback callback : callbacks) { - if (callback instanceof PasswordCallback) { - ((PasswordCallback) callback).setPassword(password); - } else if (callback instanceof NameCallback) { - ((NameCallback) callback).setName(name); + if (callback instanceof PasswordCallback passwordCallback) { + passwordCallback.setPassword(password); + } else if (callback instanceof NameCallback nameCallback) { + nameCallback.setName(name); } else { throw new UnsupportedCallbackException(callback); } @@ -133,8 +133,8 @@ protected void modified(Map properties) { Object propertyValue = properties.get("realmName"); if (propertyValue != null) { - if (propertyValue instanceof String) { - realmName = (String) propertyValue; + if (propertyValue instanceof String string) { + realmName = string; } else { realmName = propertyValue.toString(); } diff --git a/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/OAuthStoreHandlerImpl.java b/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/OAuthStoreHandlerImpl.java index 565cf21702c..9fb9a9eae25 100644 --- a/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/OAuthStoreHandlerImpl.java +++ b/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/OAuthStoreHandlerImpl.java @@ -127,9 +127,7 @@ public void deactivate() { // token does not exist return null; } - - AccessTokenResponse decryptedAccessToken = decryptToken(accessTokenResponseFromStore); - return decryptedAccessToken; + return decryptToken(accessTokenResponseFromStore); } @Override @@ -167,8 +165,7 @@ public void savePersistedParams(String handle, @Nullable PersistedParams persist @Override public @Nullable PersistedParams loadPersistedParams(String handle) { - PersistedParams persistedParams = (PersistedParams) storageFacade.get(handle, SERVICE_CONFIGURATION); - return persistedParams; + return (PersistedParams) storageFacade.get(handle, SERVICE_CONFIGURATION); } private AccessTokenResponse encryptToken(AccessTokenResponse accessTokenResponse) throws GeneralSecurityException { @@ -185,7 +182,7 @@ private AccessTokenResponse encryptToken(AccessTokenResponse accessTokenResponse private AccessTokenResponse decryptToken(AccessTokenResponse accessTokenResponse) throws GeneralSecurityException { AccessTokenResponse decryptedToken = (AccessTokenResponse) accessTokenResponse.clone(); - if (!storageCipher.isPresent()) { + if (storageCipher.isEmpty()) { return decryptedToken; // do nothing if no cipher } logger.debug("Decrypting token: {}", accessTokenResponse); @@ -195,7 +192,7 @@ private AccessTokenResponse decryptToken(AccessTokenResponse accessTokenResponse } private @Nullable String encrypt(String token) throws GeneralSecurityException { - if (!storageCipher.isPresent()) { + if (storageCipher.isEmpty()) { return token; // do nothing if no cipher } else { StorageCipher cipher = storageCipher.get(); @@ -288,8 +285,7 @@ public Set getAllHandlesFromIndex() { // update last used when it is an access token if (ACCESS_TOKEN_RESPONSE.equals(recordType)) { try { - AccessTokenResponse accessTokenResponse = gson.fromJson(value, AccessTokenResponse.class); - return accessTokenResponse; + return gson.fromJson(value, AccessTokenResponse.class); } catch (Exception e) { logger.error( "Unable to deserialize json, discarding AccessTokenResponse. " @@ -299,16 +295,14 @@ public Set getAllHandlesFromIndex() { } } else if (SERVICE_CONFIGURATION.equals(recordType)) { try { - PersistedParams params = gson.fromJson(value, PersistedParams.class); - return params; + return gson.fromJson(value, PersistedParams.class); } catch (Exception e) { logger.error("Unable to deserialize json, discarding PersistedParams. json:\n{}", value, e); return null; } } else if (LAST_USED.equals(recordType)) { try { - Instant lastUsedDate = gson.fromJson(value, Instant.class); - return lastUsedDate; + return gson.fromJson(value, Instant.class); } catch (Exception e) { logger.info("Unable to deserialize json, reset LAST_USED to now. json:\n{}", value); return Instant.now(); diff --git a/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/cipher/SymmetricKeyCipher.java b/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/cipher/SymmetricKeyCipher.java index 8192857cb2d..060bb8b7b2a 100644 --- a/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/cipher/SymmetricKeyCipher.java +++ b/bundles/org.openhab.core.auth.oauth2client/src/main/java/org/openhab/core/auth/oauth2client/internal/cipher/SymmetricKeyCipher.java @@ -89,7 +89,7 @@ public String getUniqueCipherId() { } // Generate IV - byte iv[] = new byte[IV_BYTE_SIZE]; + byte[] iv = new byte[IV_BYTE_SIZE]; random.nextBytes(iv); Cipher cipherEnc = Cipher.getInstance(ENCRYPTION_ALGO_MODE_WITH_PADDING); cipherEnc.init(Cipher.ENCRYPT_MODE, encryptionKey, new IvParameterSpec(iv)); @@ -100,9 +100,8 @@ public String getUniqueCipherId() { System.arraycopy(iv, 0, encryptedBytesWithIV, 0, IV_BYTE_SIZE); // append encrypted text to tail System.arraycopy(encryptedBytes, 0, encryptedBytesWithIV, IV_BYTE_SIZE, encryptedBytes.length); - String encryptedBase64String = Base64.getEncoder().encodeToString(encryptedBytesWithIV); - return encryptedBase64String; + return Base64.getEncoder().encodeToString(encryptedBytesWithIV); } @Override @@ -128,8 +127,7 @@ public String getUniqueCipherId() { private static SecretKey generateEncryptionKey() throws NoSuchAlgorithmException { KeyGenerator keygen = KeyGenerator.getInstance(ENCRYPTION_ALGO); keygen.init(ENCRYPTION_KEY_SIZE_BITS); - SecretKey secretKey = keygen.generateKey(); - return secretKey; + return keygen.generateKey(); } private SecretKey getOrGenerateEncryptionKey() throws NoSuchAlgorithmException, IOException { diff --git a/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/MediaActionTypeProvider.java b/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/MediaActionTypeProvider.java index df13630417c..7a52a6602c6 100644 --- a/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/MediaActionTypeProvider.java +++ b/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/MediaActionTypeProvider.java @@ -120,19 +120,15 @@ private List getConfigSynthesizeDesc(@Nullable Local } private ConfigDescriptionParameter getAudioSinkConfigDescParam(@Nullable Locale locale) { - ConfigDescriptionParameter param2 = ConfigDescriptionParameterBuilder - .create(SayActionHandler.PARAM_SINK, Type.TEXT).withRequired(false).withLabel("Sink") - .withDescription("the audio sink id").withOptions(getSinkOptions(locale)).withLimitToOptions(true) - .build(); - return param2; + return ConfigDescriptionParameterBuilder.create(SayActionHandler.PARAM_SINK, Type.TEXT).withRequired(false) + .withLabel("Sink").withDescription("the audio sink id").withOptions(getSinkOptions(locale)) + .withLimitToOptions(true).build(); } private ConfigDescriptionParameter getVolumeConfigDescParam(@Nullable Locale locale) { - ConfigDescriptionParameter param3 = ConfigDescriptionParameterBuilder - .create(SayActionHandler.PARAM_VOLUME, Type.INTEGER).withLabel("Volume") + return ConfigDescriptionParameterBuilder.create(SayActionHandler.PARAM_VOLUME, Type.INTEGER).withLabel("Volume") .withDescription("the volume to use").withMinimum(BigDecimal.ZERO).withMaximum(BigDecimal.valueOf(100)) .withStepSize(BigDecimal.ONE).build(); - return param3; } /** diff --git a/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/MediaModuleHandlerFactory.java b/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/MediaModuleHandlerFactory.java index 065735f159b..954abcfb6ec 100644 --- a/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/MediaModuleHandlerFactory.java +++ b/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/MediaModuleHandlerFactory.java @@ -62,14 +62,14 @@ public Collection getTypes() { @Override protected @Nullable ModuleHandler internalCreate(Module module, String ruleUID) { - if (module instanceof Action) { + if (module instanceof Action action) { switch (module.getTypeUID()) { case SayActionHandler.TYPE_ID: - return new SayActionHandler((Action) module, voiceManager); + return new SayActionHandler(action, voiceManager); case PlayActionHandler.TYPE_ID: - return new PlayActionHandler((Action) module, audioManager); + return new PlayActionHandler(action, audioManager); case SynthesizeActionHandler.TYPE_ID: - return new SynthesizeActionHandler((Action) module, audioManager); + return new SynthesizeActionHandler(action, audioManager); default: break; } diff --git a/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/PlayActionHandler.java b/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/PlayActionHandler.java index fadceea8b6f..027404b6d32 100644 --- a/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/PlayActionHandler.java +++ b/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/PlayActionHandler.java @@ -57,7 +57,7 @@ public PlayActionHandler(Action module, AudioManager audioManager) { this.sink = sinkParam != null ? sinkParam.toString() : null; Object volumeParam = module.getConfiguration().get(PARAM_VOLUME); - this.volume = volumeParam instanceof BigDecimal ? new PercentType((BigDecimal) volumeParam) : null; + this.volume = volumeParam instanceof BigDecimal bd ? new PercentType(bd) : null; } @Override diff --git a/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/SayActionHandler.java b/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/SayActionHandler.java index 856271f12cd..66757d48fb0 100644 --- a/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/SayActionHandler.java +++ b/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/SayActionHandler.java @@ -52,7 +52,7 @@ public SayActionHandler(Action module, VoiceManager voiceManager) { this.sink = sinkParam != null ? sinkParam.toString() : null; Object volumeParam = module.getConfiguration().get(PARAM_VOLUME); - this.volume = volumeParam instanceof BigDecimal ? new PercentType((BigDecimal) volumeParam) : null; + this.volume = volumeParam instanceof BigDecimal bd ? new PercentType(bd) : null; } @Override diff --git a/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/SynthesizeActionHandler.java b/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/SynthesizeActionHandler.java index ae0e1fa674c..2dc89d27496 100644 --- a/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/SynthesizeActionHandler.java +++ b/bundles/org.openhab.core.automation.module.media/src/main/java/org/openhab/core/automation/module/media/internal/SynthesizeActionHandler.java @@ -50,7 +50,7 @@ public SynthesizeActionHandler(Action module, AudioManager audioManager) { this.sink = sinkParam != null ? sinkParam.toString() : null; Object volumeParam = module.getConfiguration().get(PARAM_VOLUME); - this.volume = volumeParam instanceof BigDecimal ? new PercentType((BigDecimal) volumeParam) : null; + this.volume = volumeParam instanceof BigDecimal bd ? new PercentType(bd) : null; } @Override diff --git a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/internal/AbstractScriptedModuleHandlerFactory.java b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/internal/AbstractScriptedModuleHandlerFactory.java index 7723ded11f5..714ff83b7fa 100644 --- a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/internal/AbstractScriptedModuleHandlerFactory.java +++ b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/internal/AbstractScriptedModuleHandlerFactory.java @@ -46,20 +46,18 @@ public abstract class AbstractScriptedModuleHandlerFactory extends BaseModuleHan ModuleHandler moduleHandler = null; if (scriptedHandler != null) { - if (scriptedHandler instanceof SimpleActionHandler) { - moduleHandler = new SimpleActionHandlerDelegate((Action) module, (SimpleActionHandler) scriptedHandler); - } else if (scriptedHandler instanceof SimpleConditionHandler) { - moduleHandler = new SimpleConditionHandlerDelegate((Condition) module, - (SimpleConditionHandler) scriptedHandler); - } else if (scriptedHandler instanceof SimpleTriggerHandler) { - moduleHandler = new SimpleTriggerHandlerDelegate((Trigger) module, - (SimpleTriggerHandler) scriptedHandler); - } else if (scriptedHandler instanceof ScriptedActionHandlerFactory) { - moduleHandler = ((ScriptedActionHandlerFactory) scriptedHandler).get((Action) module); - } else if (scriptedHandler instanceof ScriptedTriggerHandlerFactory) { - moduleHandler = ((ScriptedTriggerHandlerFactory) scriptedHandler).get((Trigger) module); - } else if (scriptedHandler instanceof ScriptedConditionHandlerFactory) { - moduleHandler = ((ScriptedConditionHandlerFactory) scriptedHandler).get((Condition) module); + if (scriptedHandler instanceof SimpleActionHandler handler) { + moduleHandler = new SimpleActionHandlerDelegate((Action) module, handler); + } else if (scriptedHandler instanceof SimpleConditionHandler handler) { + moduleHandler = new SimpleConditionHandlerDelegate((Condition) module, handler); + } else if (scriptedHandler instanceof SimpleTriggerHandler handler) { + moduleHandler = new SimpleTriggerHandlerDelegate((Trigger) module, handler); + } else if (scriptedHandler instanceof ScriptedActionHandlerFactory factory) { + moduleHandler = factory.get((Action) module); + } else if (scriptedHandler instanceof ScriptedTriggerHandlerFactory factory) { + moduleHandler = factory.get((Trigger) module); + } else if (scriptedHandler instanceof ScriptedConditionHandlerFactory factory) { + moduleHandler = factory.get((Condition) module); } else { logger.error("Not supported moduleHandler: {}", module.getTypeUID()); } diff --git a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/internal/CacheScriptExtension.java b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/internal/CacheScriptExtension.java index 9abaa9d390c..def3ae78056 100644 --- a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/internal/CacheScriptExtension.java +++ b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/internal/CacheScriptExtension.java @@ -137,12 +137,12 @@ public void unload(String scriptIdentifier) { */ private void asyncCancelJob(@Nullable Object o) { Runnable cancelJob = null; - if (o instanceof ScheduledFuture) { - cancelJob = () -> ((ScheduledFuture) o).cancel(true); - } else if (o instanceof java.util.Timer) { - cancelJob = () -> ((java.util.Timer) o).cancel(); - } else if (o instanceof org.openhab.core.automation.module.script.action.Timer) { - cancelJob = () -> ((org.openhab.core.automation.module.script.action.Timer) o).cancel(); + if (o instanceof ScheduledFuture future) { + cancelJob = () -> future.cancel(true); + } else if (o instanceof java.util.Timer timer) { + cancelJob = () -> timer.cancel(); + } else if (o instanceof org.openhab.core.automation.module.script.action.Timer timer) { + cancelJob = () -> timer.cancel(); } if (cancelJob != null) { // not using execute so ensure this operates in another thread and we don't block here diff --git a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/ScriptedAutomationManager.java b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/ScriptedAutomationManager.java index fb4ee3ef5d6..0c51034f125 100644 --- a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/ScriptedAutomationManager.java +++ b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/ScriptedAutomationManager.java @@ -167,9 +167,8 @@ public Rule addUnmanagedRule(Rule element) { List actions = new ArrayList<>(); actions.addAll(element.getActions()); - if (element instanceof SimpleRuleActionHandler) { - String privId = addPrivateActionHandler( - new SimpleRuleActionHandlerDelegate((SimpleRuleActionHandler) element)); + if (element instanceof SimpleRuleActionHandler handler) { + String privId = addPrivateActionHandler(new SimpleRuleActionHandlerDelegate(handler)); Action scriptedAction = ActionBuilder.create().withId(Integer.toString(moduleIndex++)) .withTypeUID("jsr223.ScriptedAction").withConfiguration(new Configuration()).build(); @@ -180,9 +179,7 @@ public Rule addUnmanagedRule(Rule element) { builder.withConfiguration(element.getConfiguration()); builder.withActions(actions); - Rule rule = builder.build(); - - return rule; + return builder.build(); } public void addConditionType(ConditionType conditionType) { diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptDependencyListener.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptDependencyListener.java index 0ac610a4024..dedebcbc6ad 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptDependencyListener.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptDependencyListener.java @@ -24,5 +24,6 @@ @NonNullByDefault @FunctionalInterface public interface ScriptDependencyListener extends Consumer { + @Override void accept(String dependency); } diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineManagerImpl.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineManagerImpl.java index 9d8d7e80140..3467aa7698e 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineManagerImpl.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineManagerImpl.java @@ -182,8 +182,7 @@ public boolean loadScript(String engineIdentifier, InputStreamReader scriptData) ScriptEngine engine = container.getScriptEngine(); try { engine.eval(scriptData); - if (engine instanceof Invocable) { - Invocable inv = (Invocable) engine; + if (engine instanceof Invocable inv) { try { inv.invokeFunction("scriptLoaded", engineIdentifier); } catch (NoSuchMethodException e) { @@ -210,8 +209,7 @@ public void removeEngine(String engineIdentifier) { tracker.removeTracking(engineIdentifier); } ScriptEngine scriptEngine = container.getScriptEngine(); - if (scriptEngine instanceof Invocable) { - Invocable inv = (Invocable) scriptEngine; + if (scriptEngine instanceof Invocable inv) { try { inv.invokeFunction("scriptUnloaded"); } catch (NoSuchMethodException e) { @@ -223,12 +221,11 @@ public void removeEngine(String engineIdentifier) { logger.trace("ScriptEngine does not support Invocable interface"); } - if (scriptEngine instanceof AutoCloseable) { + if (scriptEngine instanceof AutoCloseable closeable) { // we cannot not use ScheduledExecutorService.execute here as it might execute the task in the calling // thread (calling ScriptEngine.close in the same thread may result in a deadlock if the ScriptEngine // tries to Thread.join) scheduler.schedule(() -> { - AutoCloseable closeable = (AutoCloseable) scriptEngine; try { closeable.close(); } catch (Exception e) { diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/defaultscope/ItemRegistryDelegate.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/defaultscope/ItemRegistryDelegate.java index 1acc2e5fe59..f49d0d3c0bb 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/defaultscope/ItemRegistryDelegate.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/defaultscope/ItemRegistryDelegate.java @@ -50,9 +50,9 @@ public boolean isEmpty() { @Override public boolean containsKey(@Nullable Object key) { - if (key instanceof String) { + if (key instanceof String string) { try { - itemRegistry.getItem((String) key); + itemRegistry.getItem(string); return true; } catch (ItemNotFoundException e) { return false; diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/defaultscope/ScriptBusEventImpl.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/defaultscope/ScriptBusEventImpl.java index d344e99c487..72490aff17b 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/defaultscope/ScriptBusEventImpl.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/defaultscope/ScriptBusEventImpl.java @@ -158,8 +158,7 @@ public Map storeStates(Item @Nullable... items) { Map statesMap = new HashMap<>(); if (items != null) { for (Item item : items) { - if (item instanceof GroupItem) { - GroupItem groupItem = (GroupItem) item; + if (item instanceof GroupItem groupItem) { for (Item member : groupItem.getAllMembers()) { statesMap.put(member, member.getState()); } diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/factory/ScriptModuleHandlerFactory.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/factory/ScriptModuleHandlerFactory.java index 2762be49dcd..b1524466349 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/factory/ScriptModuleHandlerFactory.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/factory/ScriptModuleHandlerFactory.java @@ -68,12 +68,10 @@ public Collection getTypes() { protected @Nullable ModuleHandler internalCreate(Module module, String ruleUID) { logger.trace("create {} -> {}", module.getId(), module.getTypeUID()); String moduleTypeUID = module.getTypeUID(); - if (ScriptConditionHandler.TYPE_ID.equals(moduleTypeUID) && module instanceof Condition) { - ScriptConditionHandler handler = new ScriptConditionHandler((Condition) module, ruleUID, - scriptEngineManager); - return handler; - } else if (ScriptActionHandler.TYPE_ID.equals(moduleTypeUID) && module instanceof Action) { - ScriptActionHandler handler = new ScriptActionHandler((Action) module, ruleUID, scriptEngineManager, + if (ScriptConditionHandler.TYPE_ID.equals(moduleTypeUID) && module instanceof Condition condition) { + return new ScriptConditionHandler(condition, ruleUID, scriptEngineManager); + } else if (ScriptActionHandler.TYPE_ID.equals(moduleTypeUID) && module instanceof Action action) { + ScriptActionHandler handler = new ScriptActionHandler(action, ruleUID, scriptEngineManager, this::onHandlerRemoval); trackedHandlers.put(handler.getEngineIdentifier(), handler); return handler; diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/AbstractScriptModuleHandler.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/AbstractScriptModuleHandler.java index f087f5c2942..71f6ae2b76e 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/AbstractScriptModuleHandler.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/AbstractScriptModuleHandler.java @@ -71,8 +71,8 @@ public AbstractScriptModuleHandler(T module, String ruleUID, ScriptEngineManager private static String getValidConfigParameter(String parameter, Configuration config, String moduleId) { Object value = config.get(parameter); - if (value != null && value instanceof String && !((String) value).trim().isEmpty()) { - return (String) value; + if (value != null && value instanceof String string && !string.trim().isEmpty()) { + return string; } else { throw new IllegalStateException(String.format( "Config parameter '%s' is missing in the configuration of module '%s'.", parameter, moduleId)); diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptConditionHandler.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptConditionHandler.java index bea05798b28..4721be85910 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptConditionHandler.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptConditionHandler.java @@ -52,8 +52,8 @@ public boolean isSatisfied(final Map context) { setExecutionContext(scriptEngine, context); try { Object returnVal = scriptEngine.eval(script); - if (returnVal instanceof Boolean) { - result = (boolean) returnVal; + if (returnVal instanceof Boolean boolean1) { + result = boolean1; } else { logger.error("Script did not return a boolean value, but '{}'", returnVal); } diff --git a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/ModuleTypeResource.java b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/ModuleTypeResource.java index 412f1428b72..7c1a5c7c043 100644 --- a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/ModuleTypeResource.java +++ b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/ModuleTypeResource.java @@ -136,21 +136,21 @@ public Response getByUID( } private ModuleTypeDTO getModuleTypeDTO(final ModuleType moduleType) { - if (moduleType instanceof ActionType) { - if (moduleType instanceof CompositeActionType) { - return ActionTypeDTOMapper.map((CompositeActionType) moduleType); + if (moduleType instanceof ActionType actionType) { + if (moduleType instanceof CompositeActionType compositeActionType) { + return ActionTypeDTOMapper.map(compositeActionType); } - return ActionTypeDTOMapper.map((ActionType) moduleType); - } else if (moduleType instanceof ConditionType) { - if (moduleType instanceof CompositeConditionType) { - return ConditionTypeDTOMapper.map((CompositeConditionType) moduleType); + return ActionTypeDTOMapper.map(actionType); + } else if (moduleType instanceof ConditionType conditionType) { + if (moduleType instanceof CompositeConditionType compositeConditionType) { + return ConditionTypeDTOMapper.map(compositeConditionType); } - return ConditionTypeDTOMapper.map((ConditionType) moduleType); - } else if (moduleType instanceof TriggerType) { - if (moduleType instanceof CompositeTriggerType) { - return TriggerTypeDTOMapper.map((CompositeTriggerType) moduleType); + return ConditionTypeDTOMapper.map(conditionType); + } else if (moduleType instanceof TriggerType triggerType) { + if (moduleType instanceof CompositeTriggerType compositeTriggerType) { + return TriggerTypeDTOMapper.map(compositeTriggerType); } - return TriggerTypeDTOMapper.map((TriggerType) moduleType); + return TriggerTypeDTOMapper.map(triggerType); } else { throw new IllegalArgumentException( String.format("Cannot handle given module type class (%s)", moduleType.getClass())); diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/dto/ActionTypeDTOMapper.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/dto/ActionTypeDTOMapper.java index 45705704c74..c9f8c714e06 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/dto/ActionTypeDTOMapper.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/dto/ActionTypeDTOMapper.java @@ -60,8 +60,8 @@ public static List map(final @Nullable Collection typ } final List dtos = new ArrayList<>(types.size()); for (final ActionType type : types) { - if (type instanceof CompositeActionType) { - dtos.add(map((CompositeActionType) type)); + if (type instanceof CompositeActionType actionType) { + dtos.add(map(actionType)); } else { dtos.add(map(type)); } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/dto/ConditionTypeDTOMapper.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/dto/ConditionTypeDTOMapper.java index f91c3112c02..df4a84e8a36 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/dto/ConditionTypeDTOMapper.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/dto/ConditionTypeDTOMapper.java @@ -61,8 +61,8 @@ public static List map(final @Nullable Collection dtos = new ArrayList<>(types.size()); for (final ConditionType type : types) { - if (type instanceof CompositeConditionType) { - dtos.add(map((CompositeConditionType) type)); + if (type instanceof CompositeConditionType conditionType) { + dtos.add(map(conditionType)); } else { dtos.add(map(type)); } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/dto/TriggerTypeDTOMapper.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/dto/TriggerTypeDTOMapper.java index 17095391c55..ea520d745ba 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/dto/TriggerTypeDTOMapper.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/dto/TriggerTypeDTOMapper.java @@ -60,8 +60,8 @@ public static List map(final @Nullable Collection t } final List dtos = new ArrayList<>(types.size()); for (final TriggerType type : types) { - if (type instanceof CompositeTriggerType) { - dtos.add(map((CompositeTriggerType) type)); + if (type instanceof CompositeTriggerType triggerType) { + dtos.add(map(triggerType)); } else { dtos.add(map(type)); } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java index 269ec3588c0..2fa454ae5a6 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java @@ -555,12 +555,12 @@ protected void postRuleStatusInfoEvent(String ruleUID, RuleStatusInfo statusInfo try { ModuleHandler moduleHandler = getModuleHandler(m, rUID); if (moduleHandler != null) { - if (mm instanceof WrappedAction) { - ((WrappedAction) mm).setModuleHandler((ActionHandler) moduleHandler); - } else if (mm instanceof WrappedCondition) { - ((WrappedCondition) mm).setModuleHandler((ConditionHandler) moduleHandler); - } else if (mm instanceof WrappedTrigger) { - ((WrappedTrigger) mm).setModuleHandler((TriggerHandler) moduleHandler); + if (mm instanceof WrappedAction action) { + action.setModuleHandler((ActionHandler) moduleHandler); + } else if (mm instanceof WrappedCondition condition) { + condition.setModuleHandler((ConditionHandler) moduleHandler); + } else if (mm instanceof WrappedTrigger trigger) { + trigger.setModuleHandler((TriggerHandler) moduleHandler); } } else { if (sb == null) { @@ -1185,8 +1185,7 @@ private void executeActions(WrappedRule rule, boolean stopOnFirstFail) { } catch (Throwable t) { String errMessage = "Fail to execute action: " + action.getId(); if (stopOnFirstFail) { - RuntimeException re = new RuntimeException(errMessage, t); - throw re; + throw new RuntimeException(errMessage, t); } else { logger.warn(errMessage, t); } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleExecutionSimulator.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleExecutionSimulator.java index 4875664bced..b3847fd470f 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleExecutionSimulator.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleExecutionSimulator.java @@ -93,9 +93,8 @@ private List simulateExecutionsForRule(Rule rule, ZonedDateTime f TriggerHandler triggerHandler = (TriggerHandler) this.ruleEngine.getModuleHandler(trigger, rule.getUID()); // Only triggers that are time-based will be considered within the simulation - if (triggerHandler instanceof TimeBasedTriggerHandler) { - SchedulerTemporalAdjuster temporalAdjuster = ((TimeBasedTriggerHandler) triggerHandler) - .getTemporalAdjuster(); + if (triggerHandler instanceof TimeBasedTriggerHandler handler) { + SchedulerTemporalAdjuster temporalAdjuster = handler.getTemporalAdjuster(); if (temporalAdjuster != null) { executions.addAll(simulateExecutionsForCronBasedRule(rule, from, until, temporalAdjuster)); } @@ -143,8 +142,7 @@ private boolean checkConditions(Rule rule, ZonedDateTime current) { rule.getUID()); // Only conditions that are time based are checked - if (conditionHandler instanceof TimeBasedConditionHandler - && !((TimeBasedConditionHandler) conditionHandler).isSatisfiedAt(current)) { + if (conditionHandler instanceof TimeBasedConditionHandler handler && !handler.isSatisfiedAt(current)) { return false; } } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleRegistryImpl.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleRegistryImpl.java index 584c4da295b..c7c5971823a 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleRegistryImpl.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleRegistryImpl.java @@ -201,8 +201,8 @@ protected void unsetModuleTypeRegistry(ModuleTypeRegistry moduleTypeRegistry) { */ @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.STATIC) protected void setTemplateRegistry(TemplateRegistry templateRegistry) { - if (templateRegistry instanceof RuleTemplateRegistry) { - this.templateRegistry = (RuleTemplateRegistry) templateRegistry; + if (templateRegistry instanceof RuleTemplateRegistry registry) { + this.templateRegistry = registry; templateRegistry.addRegistryChangeListener(this); } } @@ -557,9 +557,7 @@ private void processValue(@Nullable Object configValue, ConfigDescriptionParamet if (configValue != null) { Type type = configParameter.getType(); if (configParameter.isMultiple()) { - if (configValue instanceof List) { - @SuppressWarnings("rawtypes") - List lConfigValues = (List) configValue; + if (configValue instanceof List lConfigValues) { for (Object value : lConfigValues) { if (!checkType(type, value)) { throw new IllegalArgumentException("Unexpected value for configuration property \"" diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/commands/AutomationCommandList.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/commands/AutomationCommandList.java index 02c694d6edf..fbe9e956ebc 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/commands/AutomationCommandList.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/commands/AutomationCommandList.java @@ -293,7 +293,7 @@ private Collection getRuleByFilter(Map list) { return rules; } else { for (String ruleUID : list.values()) { - if (ruleUID.indexOf(id) > -1) { + if (ruleUID.contains(id)) { rules.add(autoCommands.getRule(ruleUID)); } } @@ -388,14 +388,14 @@ private void addCollection(Collection collection, Map list) { Iterator i = collection.iterator(); while (i.hasNext()) { Object element = i.next(); - if (element instanceof ModuleType) { - list.put(((ModuleType) element).getUID(), element); + if (element instanceof ModuleType type) { + list.put(type.getUID(), element); } - if (element instanceof RuleTemplate) { - list.put(((RuleTemplate) element).getUID(), element); + if (element instanceof RuleTemplate template) { + list.put(template.getUID(), element); } - if (element instanceof Rule) { - list.put(((Rule) element).getUID(), element); + if (element instanceof Rule rule) { + list.put(rule.getUID(), element); } } } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/commands/Printer.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/commands/Printer.java index aafc3da3272..ceb9de0c07f 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/commands/Printer.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/commands/Printer.java @@ -231,12 +231,12 @@ static String printTemplate(Template template) { templateProperty.set(0, TAGS); templateProperty.set(1, getTagsRecord(template.getTags())); templateContent.add(Utils.getRow(columnWidths, templateProperty)); - if (template instanceof RuleTemplate) { + if (template instanceof RuleTemplate ruleTemplate) { templateContent.addAll(collectRecords(columnWidths, CONFIGURATION_DESCRIPTIONS, - getConfigurationDescriptionRecords(((RuleTemplate) template).getConfigurationDescriptions()))); - templateContent.addAll(collectRecords(columnWidths, TRIGGERS, ((RuleTemplate) template).getTriggers())); - templateContent.addAll(collectRecords(columnWidths, CONDITIONS, ((RuleTemplate) template).getConditions())); - templateContent.addAll(collectRecords(columnWidths, ACTIONS, ((RuleTemplate) template).getActions())); + getConfigurationDescriptionRecords(ruleTemplate.getConfigurationDescriptions()))); + templateContent.addAll(collectRecords(columnWidths, TRIGGERS, ruleTemplate.getTriggers())); + templateContent.addAll(collectRecords(columnWidths, CONDITIONS, ruleTemplate.getConditions())); + templateContent.addAll(collectRecords(columnWidths, ACTIONS, ruleTemplate.getActions())); } return Utils.getTableContent(TABLE_WIDTH, columnWidths, templateContent, titleRow); } @@ -278,27 +278,24 @@ static String printModuleType(ModuleType moduleType) { moduleTypeContent.addAll(collectRecords(columnWidths, CONFIGURATION_DESCRIPTIONS, getConfigurationDescriptionRecords(moduleType.getConfigurationDescriptions()))); - if (moduleType instanceof TriggerType) { - moduleTypeContent.addAll(collectRecords(columnWidths, OUTPUTS, ((TriggerType) moduleType).getOutputs())); + if (moduleType instanceof TriggerType type) { + moduleTypeContent.addAll(collectRecords(columnWidths, OUTPUTS, type.getOutputs())); } - if (moduleType instanceof ConditionType) { - moduleTypeContent.addAll(collectRecords(columnWidths, INPUTS, ((ConditionType) moduleType).getInputs())); + if (moduleType instanceof ConditionType type) { + moduleTypeContent.addAll(collectRecords(columnWidths, INPUTS, type.getInputs())); } - if (moduleType instanceof ActionType) { - moduleTypeContent.addAll(collectRecords(columnWidths, INPUTS, ((ActionType) moduleType).getInputs())); - moduleTypeContent.addAll(collectRecords(columnWidths, OUTPUTS, ((ActionType) moduleType).getOutputs())); + if (moduleType instanceof ActionType type) { + moduleTypeContent.addAll(collectRecords(columnWidths, INPUTS, type.getInputs())); + moduleTypeContent.addAll(collectRecords(columnWidths, OUTPUTS, type.getOutputs())); } - if (moduleType instanceof CompositeTriggerType) { - moduleTypeContent - .addAll(collectRecords(columnWidths, CHILDREN, ((CompositeTriggerType) moduleType).getChildren())); + if (moduleType instanceof CompositeTriggerType type) { + moduleTypeContent.addAll(collectRecords(columnWidths, CHILDREN, type.getChildren())); } - if (moduleType instanceof CompositeConditionType) { - moduleTypeContent.addAll( - collectRecords(columnWidths, CHILDREN, ((CompositeConditionType) moduleType).getChildren())); + if (moduleType instanceof CompositeConditionType type) { + moduleTypeContent.addAll(collectRecords(columnWidths, CHILDREN, type.getChildren())); } - if (moduleType instanceof CompositeActionType) { - moduleTypeContent - .addAll(collectRecords(columnWidths, CHILDREN, ((CompositeActionType) moduleType).getChildren())); + if (moduleType instanceof CompositeActionType type) { + moduleTypeContent.addAll(collectRecords(columnWidths, CHILDREN, type.getChildren())); } return Utils.getTableContent(TABLE_WIDTH, columnWidths, moduleTypeContent, titleRow); } @@ -342,14 +339,14 @@ private static List collectRecords(int[] columnWidths, String prop, Coll values.add(""); if (list != null && !list.isEmpty()) { for (Object element : list) { - if (element instanceof String) { - res.add(Utils.getColumn(columnWidths[0], values.get(0)) + (String) element); + if (element instanceof String string) { + res.add(Utils.getColumn(columnWidths[0], values.get(0)) + string); if (isFirst) { isFirst = false; values.set(0, ""); } - } else if (element instanceof Module) { - List moduleRecords = getModuleRecords((Module) element); + } else if (element instanceof Module module) { + List moduleRecords = getModuleRecords(module); for (String elementRecord : moduleRecords) { res.add(Utils.getColumn(columnWidths[0], values.get(0)) + elementRecord); if (isFirst) { @@ -365,14 +362,14 @@ private static List collectRecords(int[] columnWidths, String prop, Coll isFirst = false; } values.set(0, ""); - if (element instanceof FilterCriteria) { - values.set(1, getFilterCriteriaRecord((FilterCriteria) element)); - } else if (element instanceof ParameterOption) { - values.set(1, getParameterOptionRecord((ParameterOption) element)); - } else if (element instanceof Input) { - values.set(1, getInputRecord((Input) element)); - } else if (element instanceof Output) { - values.set(1, getOutputRecord((Output) element)); + if (element instanceof FilterCriteria criteria) { + values.set(1, getFilterCriteriaRecord(criteria)); + } else if (element instanceof ParameterOption option) { + values.set(1, getParameterOptionRecord(option)); + } else if (element instanceof Input input) { + values.set(1, getInputRecord(input)); + } else if (element instanceof Output output) { + values.set(1, getOutputRecord(output)); } else if (element instanceof Entry) { values.set(1, " " + ((Entry) element).getKey() + " = \"" + ((Entry) element).getValue().toString() + "\""); @@ -425,11 +422,11 @@ private static List getModuleRecords(Module module) { moduleContent.addAll( collectRecords(columnWidths, CONFIGURATION, module.getConfiguration().getProperties().entrySet())); Map inputs = null; - if (module instanceof Condition) { - inputs = ((Condition) module).getInputs(); + if (module instanceof Condition condition) { + inputs = condition.getInputs(); } - if (module instanceof Action) { - inputs = ((Action) module).getInputs(); + if (module instanceof Action action) { + inputs = action.getInputs(); } if (inputs != null && !inputs.isEmpty()) { moduleContent.addAll(collectRecords(columnWidths, INPUTS, new ArrayList<>(inputs.entrySet()))); diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/composite/CompositeModuleHandlerFactory.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/composite/CompositeModuleHandlerFactory.java index 0c5d730f5ad..00c52653b67 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/composite/CompositeModuleHandlerFactory.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/composite/CompositeModuleHandlerFactory.java @@ -114,8 +114,7 @@ public void ungetHandler(Module module, String childModulePrefix, ModuleHandler private String getRuleId(String childModulePrefix) { int i = childModulePrefix.indexOf(':'); - String ruleId = i != -1 ? childModulePrefix.substring(0, i) : childModulePrefix; - return ruleId; + return i != -1 ? childModulePrefix.substring(0, i) : childModulePrefix; } @Override @@ -123,29 +122,26 @@ private String getRuleId(String childModulePrefix) { ModuleHandler handler = null; String moduleType = module.getTypeUID(); ModuleType mt = mtRegistry.get(moduleType); - if (mt instanceof CompositeTriggerType) { - List childModules = ((CompositeTriggerType) mt).getChildren(); + if (mt instanceof CompositeTriggerType type) { + List childModules = type.getChildren(); LinkedHashMap mapModuleToHandler = getChildHandlers(module.getId(), module.getConfiguration(), childModules, ruleUID); if (mapModuleToHandler != null) { - handler = new CompositeTriggerHandler((Trigger) module, (CompositeTriggerType) mt, mapModuleToHandler, - ruleUID); + handler = new CompositeTriggerHandler((Trigger) module, type, mapModuleToHandler, ruleUID); } - } else if (mt instanceof CompositeConditionType) { - List childModules = ((CompositeConditionType) mt).getChildren(); + } else if (mt instanceof CompositeConditionType type) { + List childModules = type.getChildren(); LinkedHashMap mapModuleToHandler = getChildHandlers(module.getId(), module.getConfiguration(), childModules, ruleUID); if (mapModuleToHandler != null) { - handler = new CompositeConditionHandler((Condition) module, (CompositeConditionType) mt, - mapModuleToHandler, ruleUID); + handler = new CompositeConditionHandler((Condition) module, type, mapModuleToHandler, ruleUID); } - } else if (mt instanceof CompositeActionType) { - List childModules = ((CompositeActionType) mt).getChildren(); + } else if (mt instanceof CompositeActionType type) { + List childModules = type.getChildren(); LinkedHashMap mapModuleToHandler = getChildHandlers(module.getId(), module.getConfiguration(), childModules, ruleUID); if (mapModuleToHandler != null) { - handler = new CompositeActionHandler((Action) module, (CompositeActionType) mt, mapModuleToHandler, - ruleUID); + handler = new CompositeActionHandler((Action) module, type, mapModuleToHandler, ruleUID); } } if (handler != null) { diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/factory/CoreModuleHandlerFactory.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/factory/CoreModuleHandlerFactory.java index 84f01b9bab7..9862255a7c8 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/factory/CoreModuleHandlerFactory.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/factory/CoreModuleHandlerFactory.java @@ -103,48 +103,47 @@ public Collection getTypes() { protected synchronized @Nullable ModuleHandler internalCreate(final Module module, final String ruleUID) { logger.trace("create {} -> {} : {}", module.getId(), module.getTypeUID(), ruleUID); final String moduleTypeUID = module.getTypeUID(); - if (module instanceof Trigger) { + if (module instanceof Trigger trigger) { // Handle triggers if (GenericEventTriggerHandler.MODULE_TYPE_ID.equals(moduleTypeUID)) { - return new GenericEventTriggerHandler((Trigger) module, bundleContext); + return new GenericEventTriggerHandler(trigger, bundleContext); } else if (ChannelEventTriggerHandler.MODULE_TYPE_ID.equals(moduleTypeUID)) { - return new ChannelEventTriggerHandler((Trigger) module, bundleContext); + return new ChannelEventTriggerHandler(trigger, bundleContext); } else if (ItemCommandTriggerHandler.MODULE_TYPE_ID.equals(moduleTypeUID)) { - return new ItemCommandTriggerHandler((Trigger) module, ruleUID, bundleContext, itemRegistry); + return new ItemCommandTriggerHandler(trigger, ruleUID, bundleContext, itemRegistry); } else if (SystemTriggerHandler.STARTLEVEL_MODULE_TYPE_ID.equals(moduleTypeUID)) { - return new SystemTriggerHandler((Trigger) module, bundleContext); + return new SystemTriggerHandler(trigger, bundleContext); } else if (ThingStatusTriggerHandler.CHANGE_MODULE_TYPE_ID.equals(moduleTypeUID) || ThingStatusTriggerHandler.UPDATE_MODULE_TYPE_ID.equals(moduleTypeUID)) { - return new ThingStatusTriggerHandler((Trigger) module, bundleContext); + return new ThingStatusTriggerHandler(trigger, bundleContext); } else if (ItemStateTriggerHandler.CHANGE_MODULE_TYPE_ID.equals(moduleTypeUID) || ItemStateTriggerHandler.UPDATE_MODULE_TYPE_ID.equals(moduleTypeUID)) { - return new ItemStateTriggerHandler((Trigger) module, ruleUID, bundleContext, itemRegistry); + return new ItemStateTriggerHandler(trigger, ruleUID, bundleContext, itemRegistry); } else if (GroupCommandTriggerHandler.MODULE_TYPE_ID.equals(moduleTypeUID)) { - return new GroupCommandTriggerHandler((Trigger) module, ruleUID, bundleContext, itemRegistry); + return new GroupCommandTriggerHandler(trigger, ruleUID, bundleContext, itemRegistry); } else if (GroupStateTriggerHandler.CHANGE_MODULE_TYPE_ID.equals(moduleTypeUID) || GroupStateTriggerHandler.UPDATE_MODULE_TYPE_ID.equals(moduleTypeUID)) { - return new GroupStateTriggerHandler((Trigger) module, ruleUID, bundleContext, itemRegistry); + return new GroupStateTriggerHandler(trigger, ruleUID, bundleContext, itemRegistry); } - } else if (module instanceof Condition) { + } else if (module instanceof Condition condition) { // Handle conditions if (ItemStateConditionHandler.ITEM_STATE_CONDITION.equals(moduleTypeUID)) { - return new ItemStateConditionHandler((Condition) module, ruleUID, bundleContext, itemRegistry, - timeZoneProvider); + return new ItemStateConditionHandler(condition, ruleUID, bundleContext, itemRegistry, timeZoneProvider); } else if (GenericEventConditionHandler.MODULETYPE_ID.equals(moduleTypeUID)) { - return new GenericEventConditionHandler((Condition) module); + return new GenericEventConditionHandler(condition); } else if (CompareConditionHandler.MODULE_TYPE.equals(moduleTypeUID)) { - return new CompareConditionHandler((Condition) module); + return new CompareConditionHandler(condition); } - } else if (module instanceof Action) { + } else if (module instanceof Action action) { // Handle actions if (ItemCommandActionHandler.ITEM_COMMAND_ACTION.equals(moduleTypeUID)) { - return new ItemCommandActionHandler((Action) module, eventPublisher, itemRegistry); + return new ItemCommandActionHandler(action, eventPublisher, itemRegistry); } else if (ItemStateUpdateActionHandler.ITEM_STATE_UPDATE_ACTION.equals(moduleTypeUID)) { - return new ItemStateUpdateActionHandler((Action) module, eventPublisher, itemRegistry); + return new ItemStateUpdateActionHandler(action, eventPublisher, itemRegistry); } else if (RuleEnablementActionHandler.UID.equals(moduleTypeUID)) { - return new RuleEnablementActionHandler((Action) module); + return new RuleEnablementActionHandler(action); } else if (RunRuleActionHandler.UID.equals(moduleTypeUID)) { - return new RunRuleActionHandler((Action) module); + return new RunRuleActionHandler(action); } } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/factory/EphemerisModuleHandlerFactory.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/factory/EphemerisModuleHandlerFactory.java index 4ec5c9491aa..b2f0335fa1d 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/factory/EphemerisModuleHandlerFactory.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/factory/EphemerisModuleHandlerFactory.java @@ -70,14 +70,14 @@ public Collection getTypes() { final String moduleTypeUID = module.getTypeUID(); logger.trace("create {} -> {} : {}", module.getId(), moduleTypeUID, ruleUID); - if (module instanceof Condition) { + if (module instanceof Condition condition) { switch (moduleTypeUID) { case EphemerisConditionHandler.HOLIDAY_MODULE_TYPE_ID: case EphemerisConditionHandler.NOT_HOLIDAY_MODULE_TYPE_ID: case EphemerisConditionHandler.WEEKEND_MODULE_TYPE_ID: case EphemerisConditionHandler.WEEKDAY_MODULE_TYPE_ID: case EphemerisConditionHandler.DAYSET_MODULE_TYPE_ID: - return new EphemerisConditionHandler((Condition) module, ephemerisManager); + return new EphemerisConditionHandler(condition, ephemerisManager); } } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/AnnotationActionHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/AnnotationActionHandler.java index 5e2c7b7c59d..0d5fb51db2f 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/AnnotationActionHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/AnnotationActionHandler.java @@ -66,8 +66,7 @@ public AnnotationActionHandler(Action module, ActionType mt, Method method, Obje for (int i = 0; i < annotations.length; i++) { Annotation[] annotationsOnParam = annotations[i]; if (annotationsOnParam != null && annotationsOnParam.length == 1) { - if (annotationsOnParam[0] instanceof ActionInput) { - ActionInput inputAnnotation = (ActionInput) annotationsOnParam[0]; + if (annotationsOnParam[0] instanceof ActionInput inputAnnotation) { // check if the moduleType has a configdescription with this input if (hasInput(moduleType, inputAnnotation.name())) { args.add(i, context.get(inputAnnotation.name())); @@ -106,8 +105,8 @@ public AnnotationActionHandler(Action module, ActionType mt, Method method, Obje method, moduleType.getUID(), ex.getMessage()); } // we allow simple data types as return values and put them under the context key "result". - } else if (result instanceof Boolean) { - output.put(MODULE_RESULT, (boolean) result); + } else if (result instanceof Boolean boolean1) { + output.put(MODULE_RESULT, boolean1); } else if (result instanceof String) { output.put(MODULE_RESULT, result); } else if (result instanceof Integer) { diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ChannelEventTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ChannelEventTriggerHandler.java index b45d75f8e68..1d727083b50 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ChannelEventTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ChannelEventTriggerHandler.java @@ -77,8 +77,7 @@ public void receive(Event event) { @Override public boolean apply(Event event) { boolean eventMatches = false; - if (event instanceof ChannelTriggeredEvent) { - ChannelTriggeredEvent cte = (ChannelTriggeredEvent) event; + if (event instanceof ChannelTriggeredEvent cte) { if (channelUID.equals(cte.getChannel())) { String eventOnChannel = this.eventOnChannel; logger.trace("->FILTER: {}:{}", cte.getEvent(), eventOnChannel); diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/CompareConditionHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/CompareConditionHandler.java index d1a1f403831..bfcc52c47ed 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/CompareConditionHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/CompareConditionHandler.java @@ -51,12 +51,11 @@ public CompareConditionHandler(Condition module) { @Override public boolean isSatisfied(Map context) { Object operatorObj = this.module.getConfiguration().get(OPERATOR); - String operator = (operatorObj != null && operatorObj instanceof String) ? (String) operatorObj : null; + String operator = (operatorObj != null && operatorObj instanceof String s) ? s : null; Object rightObj = this.module.getConfiguration().get(RIGHT_OP); - String rightOperandString = (rightObj != null && rightObj instanceof String) ? (String) rightObj : null; + String rightOperandString = (rightObj != null && rightObj instanceof String s) ? s : null; Object leftObjFieldNameObj = this.module.getConfiguration().get(INPUT_LEFT_FIELD); - String leftObjectFieldName = (leftObjFieldNameObj != null && leftObjFieldNameObj instanceof String) - ? (String) leftObjFieldNameObj + String leftObjectFieldName = (leftObjFieldNameObj != null && leftObjFieldNameObj instanceof String s) ? s : null; if (rightOperandString == null || operator == null) { return false; @@ -125,8 +124,8 @@ public boolean isSatisfied(Map context) { } case "matches": // Matcher... - if (toCompare instanceof String && rightValue instanceof String) { - return ((String) toCompare).matches((String) rightValue); + if (toCompare instanceof String string1 && rightValue instanceof String string2) { + return string1.matches(string2); } default: break; @@ -157,8 +156,8 @@ private int compare(Object a, Object b) throws UncomparableException { if ("null".equals(rightOperandString2)) { return rightOperandString2; } - if (toCompare instanceof State) { - return TypeParser.parseState(List.of(((State) toCompare).getClass()), rightOperandString2); + if (toCompare instanceof State state) { + return TypeParser.parseState(List.of(state.getClass()), rightOperandString2); } else if (toCompare instanceof Integer) { return Integer.parseInt(rightOperandString2); } else if (toCompare instanceof String) { diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupCommandTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupCommandTriggerHandler.java index efced669185..69a12a8156e 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupCommandTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupCommandTriggerHandler.java @@ -85,27 +85,25 @@ public Set getSubscribedEventTypes() { @Override public void receive(Event event) { - if (event instanceof ItemAddedEvent) { - if (groupName.equals(((ItemAddedEvent) event).getItem().name)) { + if (event instanceof ItemAddedEvent addedEvent) { + if (groupName.equals(addedEvent.getItem().name)) { logger.info("Group '{}' needed for rule '{}' added. Trigger '{}' will now work.", groupName, ruleUID, module.getId()); return; } - } else if (event instanceof ItemRemovedEvent) { - if (groupName.equals(((ItemRemovedEvent) event).getItem().name)) { + } else if (event instanceof ItemRemovedEvent removedEvent) { + if (groupName.equals(removedEvent.getItem().name)) { logger.warn("Group '{}' needed for rule '{}' removed. Trigger '{}' will no longer work.", groupName, ruleUID, module.getId()); return; } } - if (callback instanceof TriggerHandlerCallback) { - TriggerHandlerCallback cb = (TriggerHandlerCallback) callback; + if (callback instanceof TriggerHandlerCallback cb) { logger.trace("Received Event: Source: {} Topic: {} Type: {} Payload: {}", event.getSource(), event.getTopic(), event.getType(), event.getPayload()); Map values = new HashMap<>(); - if (event instanceof ItemCommandEvent) { - ItemCommandEvent icEvent = (ItemCommandEvent) event; + if (event instanceof ItemCommandEvent icEvent) { String itemName = icEvent.getItemName(); Item item = itemRegistry.get(itemName); if (item != null && item.getGroupNames().contains(groupName)) { diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupStateTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupStateTriggerHandler.java index 75a2f0aca06..bafe1a0d604 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupStateTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupStateTriggerHandler.java @@ -95,26 +95,24 @@ public Set getSubscribedEventTypes() { @Override public void receive(Event event) { - if (event instanceof ItemAddedEvent) { - if (groupName.equals(((ItemAddedEvent) event).getItem().name)) { + if (event instanceof ItemAddedEvent addedEvent) { + if (groupName.equals(addedEvent.getItem().name)) { logger.info("Group '{}' needed for rule '{}' added. Trigger '{}' will now work.", groupName, ruleUID, module.getId()); return; } - } else if (event instanceof ItemRemovedEvent) { - if (groupName.equals(((ItemRemovedEvent) event).getItem().name)) { + } else if (event instanceof ItemRemovedEvent removedEvent) { + if (groupName.equals(removedEvent.getItem().name)) { logger.warn("Group '{}' needed for rule '{}' removed. Trigger '{}' will no longer work.", groupName, ruleUID, module.getId()); return; } } - if (callback instanceof TriggerHandlerCallback) { - TriggerHandlerCallback cb = (TriggerHandlerCallback) callback; + if (callback instanceof TriggerHandlerCallback cb) { logger.trace("Received Event: Source: {} Topic: {} Type: {} Payload: {}", event.getSource(), event.getTopic(), event.getType(), event.getPayload()); - if (event instanceof ItemStateUpdatedEvent && UPDATE_MODULE_TYPE_ID.equals(module.getTypeUID())) { - ItemStateUpdatedEvent isEvent = (ItemStateUpdatedEvent) event; + if (event instanceof ItemStateUpdatedEvent isEvent && UPDATE_MODULE_TYPE_ID.equals(module.getTypeUID())) { String itemName = isEvent.getItemName(); Item item = itemRegistry.get(itemName); if (item != null && item.getGroupNames().contains(groupName)) { @@ -127,8 +125,8 @@ public void receive(Event event) { cb.triggered(this.module, values); } } - } else if (event instanceof ItemStateChangedEvent && CHANGE_MODULE_TYPE_ID.equals(module.getTypeUID())) { - ItemStateChangedEvent iscEvent = (ItemStateChangedEvent) event; + } else if (event instanceof ItemStateChangedEvent iscEvent + && CHANGE_MODULE_TYPE_ID.equals(module.getTypeUID())) { String itemName = iscEvent.getItemName(); Item item = itemRegistry.get(itemName); if (item != null && item.getGroupNames().contains(groupName)) { diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemCommandActionHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemCommandActionHandler.java index 0c51d2f93f2..e3224ea921e 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemCommandActionHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemCommandActionHandler.java @@ -75,9 +75,9 @@ public ItemCommandActionHandler(Action module, EventPublisher eventPublisher, It } else { Object cmd = inputs.get(COMMAND); - if (cmd instanceof Command) { + if (cmd instanceof Command command1) { if (item.getAcceptedCommandTypes().contains(cmd.getClass())) { - commandObj = (Command) cmd; + commandObj = command1; } } } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemCommandTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemCommandTriggerHandler.java index 258bd5b503b..ac9cb07ca63 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemCommandTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemCommandTriggerHandler.java @@ -91,14 +91,14 @@ public Set getSubscribedEventTypes() { @Override public void receive(Event event) { - if (event instanceof ItemAddedEvent) { - if (itemName.equals(((ItemAddedEvent) event).getItem().name)) { + if (event instanceof ItemAddedEvent addedEvent) { + if (itemName.equals(addedEvent.getItem().name)) { logger.info("Item '{}' needed for rule '{}' added. Trigger '{}' will now work.", itemName, ruleUID, module.getId()); return; } - } else if (event instanceof ItemRemovedEvent) { - if (itemName.equals(((ItemRemovedEvent) event).getItem().name)) { + } else if (event instanceof ItemRemovedEvent removedEvent) { + if (itemName.equals(removedEvent.getItem().name)) { logger.warn("Item '{}' needed for rule '{}' removed. Trigger '{}' will no longer work.", itemName, ruleUID, module.getId()); return; @@ -110,9 +110,9 @@ public void receive(Event event) { logger.trace("Received Event: Source: {} Topic: {} Type: {} Payload: {}", event.getSource(), event.getTopic(), event.getType(), event.getPayload()); Map values = new HashMap<>(); - if (event instanceof ItemCommandEvent) { + if (event instanceof ItemCommandEvent commandEvent) { String command = this.command; - Command itemCommand = ((ItemCommandEvent) event).getItemCommand(); + Command itemCommand = commandEvent.getItemCommand(); if (command == null || command.equals(itemCommand.toFullString())) { values.put("command", itemCommand); values.put("event", event); diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateConditionHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateConditionHandler.java index aca6f670ef2..e55a91e6118 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateConditionHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateConditionHandler.java @@ -106,14 +106,14 @@ public Set getSubscribedEventTypes() { @Override public void receive(Event event) { - if (event instanceof ItemAddedEvent) { - if (itemName.equals(((ItemAddedEvent) event).getItem().name)) { + if (event instanceof ItemAddedEvent addedEvent) { + if (itemName.equals(addedEvent.getItem().name)) { logger.info("Item '{}' needed for rule '{}' added. Condition '{}' will now work.", itemName, ruleUID, module.getId()); return; } - } else if (event instanceof ItemRemovedEvent) { - if (itemName.equals(((ItemRemovedEvent) event).getItem().name)) { + } else if (event instanceof ItemRemovedEvent removedEvent) { + if (itemName.equals(removedEvent.getItem().name)) { logger.warn("Item '{}' needed for rule '{}' removed. Condition '{}' will no longer work.", itemName, ruleUID, module.getId()); return; @@ -160,13 +160,12 @@ private boolean lessThanOrEqualsToItemState(String itemName, String state) throw Item item = itemRegistry.getItem(itemName); State compareState = TypeParser.parseState(item.getAcceptedDataTypes(), state); State itemState = item.getState(); - if (itemState instanceof DateTimeType) { - ZonedDateTime itemTime = ((DateTimeType) itemState).getZonedDateTime(); + if (itemState instanceof DateTimeType type) { + ZonedDateTime itemTime = type.getZonedDateTime(); ZonedDateTime compareTime = getCompareTime(state); return itemTime.compareTo(compareTime) <= 0; - } else if (itemState instanceof QuantityType) { - QuantityType qtState = (QuantityType) itemState; - if (compareState instanceof DecimalType) { + } else if (itemState instanceof QuantityType qtState) { + if (compareState instanceof DecimalType type) { // allow compareState without unit -> implicitly assume its the same as the one from the // state, but warn the user if (!Units.ONE.equals(qtState.getUnit())) { @@ -174,21 +173,20 @@ private boolean lessThanOrEqualsToItemState(String itemName, String state) throw "Received a QuantityType state '{}' with unit for item {}, but the condition is defined as a plain number without unit ({}), please consider adding a unit to the condition for rule {}.", qtState, itemName, state, ruleUID); } - return qtState.compareTo( - new QuantityType<>(((DecimalType) compareState).toBigDecimal(), qtState.getUnit())) <= 0; - } else if (compareState instanceof QuantityType) { - return qtState.compareTo((QuantityType) compareState) <= 0; + return qtState.compareTo(new QuantityType<>(type.toBigDecimal(), qtState.getUnit())) <= 0; + } else if (compareState instanceof QuantityType type) { + return qtState.compareTo(type) <= 0; } - } else if (itemState instanceof PercentType && null != compareState) { + } else if (itemState instanceof PercentType type && null != compareState) { // we need to handle PercentType first, otherwise the comparison will fail PercentType percentState = compareState.as(PercentType.class); if (null != percentState) { - return ((PercentType) itemState).compareTo(percentState) <= 0; + return type.compareTo(percentState) <= 0; } - } else if (itemState instanceof DecimalType && null != compareState) { + } else if (itemState instanceof DecimalType type && null != compareState) { DecimalType decimalState = compareState.as(DecimalType.class); if (null != decimalState) { - return ((DecimalType) itemState).compareTo(decimalState) <= 0; + return type.compareTo(decimalState) <= 0; } } return false; @@ -199,13 +197,12 @@ private boolean greaterThanOrEqualsToItemState(String itemName, String state) th Item item = itemRegistry.getItem(itemName); State compareState = TypeParser.parseState(item.getAcceptedDataTypes(), state); State itemState = item.getState(); - if (itemState instanceof DateTimeType) { - ZonedDateTime itemTime = ((DateTimeType) itemState).getZonedDateTime(); + if (itemState instanceof DateTimeType type) { + ZonedDateTime itemTime = type.getZonedDateTime(); ZonedDateTime compareTime = getCompareTime(state); return itemTime.compareTo(compareTime) >= 0; - } else if (itemState instanceof QuantityType) { - QuantityType qtState = (QuantityType) itemState; - if (compareState instanceof DecimalType) { + } else if (itemState instanceof QuantityType qtState) { + if (compareState instanceof DecimalType type) { // allow compareState without unit -> implicitly assume its the same as the one from the // state, but warn the user if (!Units.ONE.equals(qtState.getUnit())) { @@ -213,21 +210,20 @@ private boolean greaterThanOrEqualsToItemState(String itemName, String state) th "Received a QuantityType state '{}' with unit for item {}, but the condition is defined as a plain number without unit ({}), please consider adding a unit to the condition for rule {}.", qtState, itemName, state, ruleUID); } - return qtState.compareTo( - new QuantityType<>(((DecimalType) compareState).toBigDecimal(), qtState.getUnit())) >= 0; - } else if (compareState instanceof QuantityType) { - return qtState.compareTo((QuantityType) compareState) >= 0; + return qtState.compareTo(new QuantityType<>(type.toBigDecimal(), qtState.getUnit())) >= 0; + } else if (compareState instanceof QuantityType type) { + return qtState.compareTo(type) >= 0; } - } else if (itemState instanceof PercentType && null != compareState) { + } else if (itemState instanceof PercentType type && null != compareState) { // we need to handle PercentType first, otherwise the comparison will fail PercentType percentState = compareState.as(PercentType.class); if (null != percentState) { - return ((PercentType) itemState).compareTo(percentState) >= 0; + return type.compareTo(percentState) >= 0; } - } else if (itemState instanceof DecimalType && null != compareState) { + } else if (itemState instanceof DecimalType type && null != compareState) { DecimalType decimalState = compareState.as(DecimalType.class); if (null != decimalState) { - return ((DecimalType) itemState).compareTo(decimalState) >= 0; + return type.compareTo(decimalState) >= 0; } } return false; @@ -237,12 +233,10 @@ private boolean equalsToItemState(String itemName, String state) throws ItemNotF Item item = itemRegistry.getItem(itemName); State compareState = TypeParser.parseState(item.getAcceptedDataTypes(), state); State itemState = item.getState(); - if (itemState instanceof QuantityType && compareState instanceof DecimalType) { - QuantityType qtState = (QuantityType) itemState; + if (itemState instanceof QuantityType qtState && compareState instanceof DecimalType type) { if (Units.ONE.equals(qtState.getUnit())) { // allow compareStates without unit if the unit of the state equals to ONE - return itemState - .equals(new QuantityType<>(((DecimalType) compareState).toBigDecimal(), qtState.getUnit())); + return itemState.equals(new QuantityType<>(type.toBigDecimal(), qtState.getUnit())); } else { // log a warning if the unit of the state differs from ONE logger.warn( diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateTriggerHandler.java index 15b4d0a572f..820c641c262 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateTriggerHandler.java @@ -105,14 +105,14 @@ public Set getSubscribedEventTypes() { @Override public void receive(Event event) { - if (event instanceof ItemAddedEvent) { - if (itemName.equals(((ItemAddedEvent) event).getItem().name)) { + if (event instanceof ItemAddedEvent addedEvent) { + if (itemName.equals(addedEvent.getItem().name)) { logger.info("Item '{}' needed for rule '{}' added. Trigger '{}' will now work.", itemName, ruleUID, module.getId()); return; } - } else if (event instanceof ItemRemovedEvent) { - if (itemName.equals(((ItemRemovedEvent) event).getItem().name)) { + } else if (event instanceof ItemRemovedEvent removedEvent) { + if (itemName.equals(removedEvent.getItem().name)) { logger.warn("Item '{}' needed for rule '{}' removed. Trigger '{}' will no longer work.", itemName, ruleUID, module.getId()); return; @@ -124,15 +124,17 @@ public void receive(Event event) { logger.trace("Received Event: Source: {} Topic: {} Type: {} Payload: {}", event.getSource(), event.getTopic(), event.getType(), event.getPayload()); Map values = new HashMap<>(); - if (event instanceof ItemStateUpdatedEvent && UPDATE_MODULE_TYPE_ID.equals(module.getTypeUID())) { + if (event instanceof ItemStateUpdatedEvent updatedEvent + && UPDATE_MODULE_TYPE_ID.equals(module.getTypeUID())) { String state = this.state; - State itemState = ((ItemStateUpdatedEvent) event).getItemState(); + State itemState = updatedEvent.getItemState(); if ((state == null || state.equals(itemState.toFullString()))) { values.put("state", itemState); } - } else if (event instanceof ItemStateChangedEvent && CHANGE_MODULE_TYPE_ID.equals(module.getTypeUID())) { - State itemState = ((ItemStateChangedEvent) event).getItemState(); - State oldItemState = ((ItemStateChangedEvent) event).getOldItemState(); + } else if (event instanceof ItemStateChangedEvent changedEvent + && CHANGE_MODULE_TYPE_ID.equals(module.getTypeUID())) { + State itemState = changedEvent.getItemState(); + State oldItemState = changedEvent.getOldItemState(); if (stateMatches(this.state, itemState) && stateMatches(this.previousState, oldItemState)) { values.put("oldState", oldItemState); diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateUpdateActionHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateUpdateActionHandler.java index 2bff826e79f..4a515b0d57c 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateUpdateActionHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateUpdateActionHandler.java @@ -71,9 +71,9 @@ public ItemStateUpdateActionHandler(Action module, EventPublisher eventPublisher } else { final Object st = inputs.get(STATE); - if (st instanceof State) { + if (st instanceof State state1) { if (item.getAcceptedDataTypes().contains(st.getClass())) { - stateObj = (State) st; + stateObj = state1; } } } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/SystemTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/SystemTriggerHandler.java index 58c7b5ca961..acf3b1f9384 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/SystemTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/SystemTriggerHandler.java @@ -79,8 +79,8 @@ public void receive(Event event) { } logger.trace("Received Event: Source: {} Topic: {} Type: {} Payload: {}", event.getSource(), event.getTopic(), event.getType(), event.getPayload()); - if (event instanceof StartlevelEvent && STARTLEVEL_MODULE_TYPE_ID.equals(module.getTypeUID())) { - Integer sl = ((StartlevelEvent) event).getStartlevel(); + if (event instanceof StartlevelEvent startlevelEvent && STARTLEVEL_MODULE_TYPE_ID.equals(module.getTypeUID())) { + Integer sl = startlevelEvent.getStartlevel(); if (startlevel <= sl && startlevel > StartLevelService.STARTLEVEL_RULEENGINE) { // only execute rules if their start level is higher than the rule engine activation level, since // otherwise the rule engine takes care of the execution already diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ThingStatusTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ThingStatusTriggerHandler.java index 87996ec1826..c185cb4b82b 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ThingStatusTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ThingStatusTriggerHandler.java @@ -104,14 +104,15 @@ public void receive(Event event) { logger.trace("Received Event: Source: {} Topic: {} Type: {} Payload: {}", event.getSource(), event.getTopic(), event.getType(), event.getPayload()); Map values = new HashMap<>(); - if (event instanceof ThingStatusInfoEvent && UPDATE_MODULE_TYPE_ID.equals(module.getTypeUID())) { - ThingStatus status = ((ThingStatusInfoEvent) event).getStatusInfo().getStatus(); + if (event instanceof ThingStatusInfoEvent infoEvent && UPDATE_MODULE_TYPE_ID.equals(module.getTypeUID())) { + ThingStatus status = infoEvent.getStatusInfo().getStatus(); if (statusMatches(this.status, status)) { values.put(OUT_STATUS, status); } - } else if (event instanceof ThingStatusInfoChangedEvent && CHANGE_MODULE_TYPE_ID.equals(module.getTypeUID())) { - ThingStatus newStatus = ((ThingStatusInfoChangedEvent) event).getStatusInfo().getStatus(); - ThingStatus oldStatus = ((ThingStatusInfoChangedEvent) event).getOldStatusInfo().getStatus(); + } else if (event instanceof ThingStatusInfoChangedEvent changedEvent + && CHANGE_MODULE_TYPE_ID.equals(module.getTypeUID())) { + ThingStatus newStatus = changedEvent.getStatusInfo().getStatus(); + ThingStatus oldStatus = changedEvent.getOldStatusInfo().getStatus(); if (statusMatches(this.status, newStatus) && statusMatches(this.previousStatus, oldStatus)) { values.put(OUT_NEW_STATUS, newStatus); values.put(OUT_OLD_STATUS, oldStatus); diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/TimerModuleHandlerFactory.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/TimerModuleHandlerFactory.java index 829db351cc3..2c87ce5ea21 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/TimerModuleHandlerFactory.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/TimerModuleHandlerFactory.java @@ -78,16 +78,18 @@ public Collection getTypes() { protected @Nullable ModuleHandler internalCreate(Module module, String ruleUID) { logger.trace("create {} -> {}", module.getId(), module.getTypeUID()); String moduleTypeUID = module.getTypeUID(); - if (GenericCronTriggerHandler.MODULE_TYPE_ID.equals(moduleTypeUID) && module instanceof Trigger) { - return new GenericCronTriggerHandler((Trigger) module, scheduler); - } else if (TimeOfDayTriggerHandler.MODULE_TYPE_ID.equals(moduleTypeUID) && module instanceof Trigger) { - return new TimeOfDayTriggerHandler((Trigger) module, scheduler); - } else if (DateTimeTriggerHandler.MODULE_TYPE_ID.equals(moduleTypeUID) && module instanceof Trigger) { - return new DateTimeTriggerHandler((Trigger) module, scheduler, itemRegistry, bundleContext); - } else if (TimeOfDayConditionHandler.MODULE_TYPE_ID.equals(moduleTypeUID) && module instanceof Condition) { - return new TimeOfDayConditionHandler((Condition) module); - } else if (DayOfWeekConditionHandler.MODULE_TYPE_ID.equals(moduleTypeUID) && module instanceof Condition) { - return new DayOfWeekConditionHandler((Condition) module); + if (GenericCronTriggerHandler.MODULE_TYPE_ID.equals(moduleTypeUID) && module instanceof Trigger trigger) { + return new GenericCronTriggerHandler(trigger, scheduler); + } else if (TimeOfDayTriggerHandler.MODULE_TYPE_ID.equals(moduleTypeUID) && module instanceof Trigger trigger) { + return new TimeOfDayTriggerHandler(trigger, scheduler); + } else if (DateTimeTriggerHandler.MODULE_TYPE_ID.equals(moduleTypeUID) && module instanceof Trigger trigger) { + return new DateTimeTriggerHandler(trigger, scheduler, itemRegistry, bundleContext); + } else if (TimeOfDayConditionHandler.MODULE_TYPE_ID.equals(moduleTypeUID) + && module instanceof Condition condition) { + return new TimeOfDayConditionHandler(condition); + } else if (DayOfWeekConditionHandler.MODULE_TYPE_ID.equals(moduleTypeUID) + && module instanceof Condition condition) { + return new DayOfWeekConditionHandler(condition); } else { logger.error("The module handler type '{}' is not supported.", moduleTypeUID); } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/provider/AnnotatedActionModuleTypeProvider.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/provider/AnnotatedActionModuleTypeProvider.java index d6aaac127d5..e2e8555824c 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/provider/AnnotatedActionModuleTypeProvider.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/provider/AnnotatedActionModuleTypeProvider.java @@ -122,9 +122,7 @@ public Collection getModuleTypes(@Nullable Locale loca Bundle bundle = FrameworkUtil.getBundle(mi.getActionProvider().getClass()); ModuleType mt = helper.buildModuleType(uid, moduleInformation); - - ModuleType localizedModuleType = moduleTypeI18nService.getModuleTypePerLocale(mt, locale, bundle); - return localizedModuleType; + return moduleTypeI18nService.getModuleTypePerLocale(mt, locale, bundle); } return null; } @@ -198,8 +196,8 @@ public void removeActionProvider(AnnotatedActions actionProvider, Map properties) { Object o = properties.get(OpenHAB.SERVICE_CONTEXT); String configName = null; - if (o instanceof String) { - configName = (String) o; + if (o instanceof String string) { + configName = string; } return configName; } @@ -211,8 +209,7 @@ public Collection getTypes() { @Override protected @Nullable ModuleHandler internalCreate(Module module, String ruleUID) { - if (module instanceof Action) { - Action actionModule = (Action) module; + if (module instanceof Action actionModule) { if (moduleInformation.containsKey(actionModule.getTypeUID())) { ModuleInformation finalMI = helper.getModuleInformationForIdentifier(actionModule, moduleInformation, false); diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/parser/gson/ModuleTypeGSONParser.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/parser/gson/ModuleTypeGSONParser.java index 8abeec89c16..1a995ec9ede 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/parser/gson/ModuleTypeGSONParser.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/parser/gson/ModuleTypeGSONParser.java @@ -74,12 +74,12 @@ public void serialize(Set dataObjects, OutputStreamWriter writer) th private void addAll(Set result, @Nullable List moduleTypes) { if (moduleTypes != null) { for (ModuleTypeDTO mt : moduleTypes) { - if (mt instanceof CompositeTriggerTypeDTO) { - result.add(TriggerTypeDTOMapper.map((CompositeTriggerTypeDTO) mt)); - } else if (mt instanceof CompositeConditionTypeDTO) { - result.add(ConditionTypeDTOMapper.map((CompositeConditionTypeDTO) mt)); - } else if (mt instanceof CompositeActionTypeDTO) { - result.add(ActionTypeDTOMapper.map((CompositeActionTypeDTO) mt)); + if (mt instanceof CompositeTriggerTypeDTO tO) { + result.add(TriggerTypeDTOMapper.map(tO)); + } else if (mt instanceof CompositeConditionTypeDTO tO) { + result.add(ConditionTypeDTOMapper.map(tO)); + } else if (mt instanceof CompositeActionTypeDTO tO) { + result.add(ActionTypeDTOMapper.map(tO)); } } } @@ -92,12 +92,12 @@ private Map> createMapByType(Set List conditions = new ArrayList<>(); List actions = new ArrayList<>(); for (ModuleType moduleType : dataObjects) { - if (moduleType instanceof TriggerType) { - triggers.add((TriggerType) moduleType); - } else if (moduleType instanceof ConditionType) { - conditions.add((ConditionType) moduleType); - } else if (moduleType instanceof ActionType) { - actions.add((ActionType) moduleType); + if (moduleType instanceof TriggerType type) { + triggers.add(type); + } else if (moduleType instanceof ConditionType type) { + conditions.add(type); + } else if (moduleType instanceof ActionType type) { + actions.add(type); } } map.put("triggers", triggers); diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/provider/AutomationResourceBundlesTracker.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/provider/AutomationResourceBundlesTracker.java index 546bd418901..b60262c6bb3 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/provider/AutomationResourceBundlesTracker.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/provider/AutomationResourceBundlesTracker.java @@ -94,8 +94,8 @@ protected void deactivate(BundleContext bc) { @SuppressWarnings({ "rawtypes" }) @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, target = "(provider.type=bundle)") protected void addProvider(Provider provider) { - if (provider instanceof AbstractResourceBundleProvider) { - addAbstractResourceBundleProvider((AbstractResourceBundleProvider) provider); + if (provider instanceof AbstractResourceBundleProvider bundleProvider) { + addAbstractResourceBundleProvider(bundleProvider); } } @@ -110,8 +110,8 @@ protected void addAbstractResourceBundleProvider(AbstractResourceBundleProvider @SuppressWarnings({ "rawtypes" }) protected void removeProvider(Provider provider) { - if (provider instanceof AbstractResourceBundleProvider) { - removeAbstractResourceBundleProvider((AbstractResourceBundleProvider) provider); + if (provider instanceof AbstractResourceBundleProvider bundleProvider) { + removeAbstractResourceBundleProvider(bundleProvider); } } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/provider/Vendor.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/provider/Vendor.java index 42d1b7e9e54..e3136d74594 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/provider/Vendor.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/provider/Vendor.java @@ -120,8 +120,7 @@ public int count() { */ @Override public boolean equals(@Nullable Object obj) { - if (obj instanceof Vendor) { - Vendor other = (Vendor) obj; + if (obj instanceof Vendor other) { return vendorSymbolicName.equals(other.vendorSymbolicName) && vendorVersion.equals(other.vendorVersion); } return false; diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/provider/i18n/ModuleI18nUtil.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/provider/i18n/ModuleI18nUtil.java index 763c5367c6d..1ba63685015 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/provider/i18n/ModuleI18nUtil.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/provider/i18n/ModuleI18nUtil.java @@ -59,14 +59,14 @@ public List getLocalizedModules(List modules, Bundle bu @SuppressWarnings("unchecked") private @Nullable T createLocalizedModule(T module, @Nullable String label, @Nullable String description) { - if (module instanceof Action) { - return (T) createLocalizedAction((Action) module, label, description); + if (module instanceof Action action) { + return (T) createLocalizedAction(action, label, description); } - if (module instanceof Condition) { - return (T) createLocalizedCondition((Condition) module, label, description); + if (module instanceof Condition condition) { + return (T) createLocalizedCondition(condition, label, description); } - if (module instanceof Trigger) { - return (T) createLocalizedTrigger((Trigger) module, label, description); + if (module instanceof Trigger trigger) { + return (T) createLocalizedTrigger(trigger, label, description); } return null; } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/provider/i18n/ModuleTypeI18nServiceImpl.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/provider/i18n/ModuleTypeI18nServiceImpl.java index 36647875bd6..346685f7f86 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/provider/i18n/ModuleTypeI18nServiceImpl.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/provider/i18n/ModuleTypeI18nServiceImpl.java @@ -86,19 +86,19 @@ public ModuleTypeI18nServiceImpl(final @Reference ConfigI18nLocalizationService List lconfigDescriptionParameters = getLocalizedConfigDescriptionParameters( defModuleType.getConfigurationDescriptions(), ModuleTypeI18nUtil.MODULE_TYPE, uid, bundle, locale); - if (defModuleType instanceof ActionType) { - return createLocalizedActionType((ActionType) defModuleType, bundle, uid, locale, - lconfigDescriptionParameters, llabel == null ? defModuleType.getLabel() : llabel, + if (defModuleType instanceof ActionType type) { + return createLocalizedActionType(type, bundle, uid, locale, lconfigDescriptionParameters, + llabel == null ? defModuleType.getLabel() : llabel, ldescription == null ? defModuleType.getDescription() : ldescription); } - if (defModuleType instanceof ConditionType) { - return createLocalizedConditionType((ConditionType) defModuleType, bundle, uid, locale, - lconfigDescriptionParameters, llabel == null ? defModuleType.getLabel() : llabel, + if (defModuleType instanceof ConditionType type) { + return createLocalizedConditionType(type, bundle, uid, locale, lconfigDescriptionParameters, + llabel == null ? defModuleType.getLabel() : llabel, ldescription == null ? defModuleType.getDescription() : ldescription); } - if (defModuleType instanceof TriggerType) { - return createLocalizedTriggerType((TriggerType) defModuleType, bundle, uid, locale, - lconfigDescriptionParameters, llabel != null ? llabel : defModuleType.getLabel(), + if (defModuleType instanceof TriggerType type) { + return createLocalizedTriggerType(type, bundle, uid, locale, lconfigDescriptionParameters, + llabel != null ? llabel : defModuleType.getLabel(), ldescription == null ? defModuleType.getDescription() : ldescription); } return null; @@ -136,9 +136,9 @@ public ModuleTypeI18nServiceImpl(final @Reference ConfigI18nLocalizationService List inputs = moduleTypeI18nUtil.getLocalizedInputs(at.getInputs(), bundle, moduleTypeUID, locale); List outputs = moduleTypeI18nUtil.getLocalizedOutputs(at.getOutputs(), bundle, moduleTypeUID, locale); ActionType lat = null; - if (at instanceof CompositeActionType) { - List modules = moduleI18nUtil.getLocalizedModules(((CompositeActionType) at).getChildren(), bundle, - moduleTypeUID, ModuleTypeI18nUtil.MODULE_TYPE, locale); + if (at instanceof CompositeActionType type) { + List modules = moduleI18nUtil.getLocalizedModules(type.getChildren(), bundle, moduleTypeUID, + ModuleTypeI18nUtil.MODULE_TYPE, locale); lat = new CompositeActionType(moduleTypeUID, lconfigDescriptions, llabel, ldescription, at.getTags(), at.getVisibility(), inputs, outputs, modules); } else { @@ -165,9 +165,9 @@ public ModuleTypeI18nServiceImpl(final @Reference ConfigI18nLocalizationService @Nullable String llabel, @Nullable String ldescription) { List inputs = moduleTypeI18nUtil.getLocalizedInputs(ct.getInputs(), bundle, moduleTypeUID, locale); ConditionType lct = null; - if (ct instanceof CompositeConditionType) { - List modules = moduleI18nUtil.getLocalizedModules(((CompositeConditionType) ct).getChildren(), - bundle, moduleTypeUID, ModuleTypeI18nUtil.MODULE_TYPE, locale); + if (ct instanceof CompositeConditionType type) { + List modules = moduleI18nUtil.getLocalizedModules(type.getChildren(), bundle, moduleTypeUID, + ModuleTypeI18nUtil.MODULE_TYPE, locale); lct = new CompositeConditionType(moduleTypeUID, lconfigDescriptions, llabel, ldescription, ct.getTags(), ct.getVisibility(), inputs, modules); } else { @@ -194,9 +194,9 @@ public ModuleTypeI18nServiceImpl(final @Reference ConfigI18nLocalizationService @Nullable String llabel, @Nullable String ldescription) { List outputs = moduleTypeI18nUtil.getLocalizedOutputs(tt.getOutputs(), bundle, moduleTypeUID, locale); TriggerType ltt = null; - if (tt instanceof CompositeTriggerType) { - List modules = moduleI18nUtil.getLocalizedModules(((CompositeTriggerType) tt).getChildren(), - bundle, moduleTypeUID, ModuleTypeI18nUtil.MODULE_TYPE, locale); + if (tt instanceof CompositeTriggerType type) { + List modules = moduleI18nUtil.getLocalizedModules(type.getChildren(), bundle, moduleTypeUID, + ModuleTypeI18nUtil.MODULE_TYPE, locale); ltt = new CompositeTriggerType(moduleTypeUID, lconfigDescriptions, llabel, ldescription, tt.getTags(), tt.getVisibility(), outputs, modules); } else { diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/type/ModuleTypeRegistryImpl.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/type/ModuleTypeRegistryImpl.java index 16ecad9ded3..4444d18d756 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/type/ModuleTypeRegistryImpl.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/type/ModuleTypeRegistryImpl.java @@ -127,8 +127,8 @@ public Collection getTriggers(@Nullable Locale locale, String... ta Collection moduleTypes = getByTags(locale, tags); Collection triggerTypes = new ArrayList<>(); for (ModuleType mt : moduleTypes) { - if (mt instanceof TriggerType) { - triggerTypes.add((TriggerType) mt); + if (mt instanceof TriggerType type) { + triggerTypes.add(type); } } return triggerTypes; @@ -139,8 +139,8 @@ public Collection getTriggers(String... tags) { Collection moduleTypes = getByTags(tags); Collection triggerTypes = new ArrayList<>(); for (ModuleType mt : moduleTypes) { - if (mt instanceof TriggerType) { - triggerTypes.add((TriggerType) mt); + if (mt instanceof TriggerType type) { + triggerTypes.add(type); } } return triggerTypes; @@ -151,8 +151,8 @@ public Collection getConditions(String... tags) { Collection moduleTypes = getByTags(tags); Collection conditionTypes = new ArrayList<>(); for (ModuleType mt : moduleTypes) { - if (mt instanceof ConditionType) { - conditionTypes.add((ConditionType) mt); + if (mt instanceof ConditionType type) { + conditionTypes.add(type); } } return conditionTypes; @@ -163,8 +163,8 @@ public Collection getConditions(@Nullable Locale locale, String.. Collection moduleTypes = getByTags(locale, tags); Collection conditionTypes = new ArrayList<>(); for (ModuleType mt : moduleTypes) { - if (mt instanceof ConditionType) { - conditionTypes.add((ConditionType) mt); + if (mt instanceof ConditionType type) { + conditionTypes.add(type); } } return conditionTypes; @@ -175,8 +175,8 @@ public Collection getActions(String... tags) { Collection moduleTypes = getByTags(tags); Collection actionTypes = new ArrayList<>(); for (ModuleType mt : moduleTypes) { - if (mt instanceof ActionType) { - actionTypes.add((ActionType) mt); + if (mt instanceof ActionType type) { + actionTypes.add(type); } } return actionTypes; @@ -187,8 +187,8 @@ public Collection getActions(@Nullable Locale locale, String... tags Collection moduleTypes = getByTags(locale, tags); Collection actionTypes = new ArrayList<>(); for (ModuleType mt : moduleTypes) { - if (mt instanceof ActionType) { - actionTypes.add((ActionType) mt); + if (mt instanceof ActionType type) { + actionTypes.add(type); } } return actionTypes; @@ -199,36 +199,30 @@ public Collection getActions(@Nullable Locale locale, String... tags return null; } ModuleType result; - if (mType instanceof CompositeTriggerType) { - CompositeTriggerType m = (CompositeTriggerType) mType; + if (mType instanceof CompositeTriggerType m) { result = new CompositeTriggerType(mType.getUID(), mType.getConfigurationDescriptions(), mType.getLabel(), mType.getDescription(), mType.getTags(), mType.getVisibility(), m.getOutputs(), new ArrayList<>(m.getChildren())); - } else if (mType instanceof TriggerType) { - TriggerType m = (TriggerType) mType; + } else if (mType instanceof TriggerType m) { result = new TriggerType(mType.getUID(), mType.getConfigurationDescriptions(), mType.getLabel(), mType.getDescription(), mType.getTags(), mType.getVisibility(), m.getOutputs()); - } else if (mType instanceof CompositeConditionType) { - CompositeConditionType m = (CompositeConditionType) mType; + } else if (mType instanceof CompositeConditionType m) { result = new CompositeConditionType(mType.getUID(), mType.getConfigurationDescriptions(), mType.getLabel(), mType.getDescription(), mType.getTags(), mType.getVisibility(), m.getInputs(), new ArrayList<>(m.getChildren())); - } else if (mType instanceof ConditionType) { - ConditionType m = (ConditionType) mType; + } else if (mType instanceof ConditionType m) { result = new ConditionType(mType.getUID(), mType.getConfigurationDescriptions(), mType.getLabel(), mType.getDescription(), mType.getTags(), mType.getVisibility(), m.getInputs()); - } else if (mType instanceof CompositeActionType) { - CompositeActionType m = (CompositeActionType) mType; + } else if (mType instanceof CompositeActionType m) { result = new CompositeActionType(mType.getUID(), mType.getConfigurationDescriptions(), mType.getLabel(), mType.getDescription(), mType.getTags(), mType.getVisibility(), m.getInputs(), m.getOutputs(), new ArrayList<>(m.getChildren())); - } else if (mType instanceof ActionType) { - ActionType m = (ActionType) mType; + } else if (mType instanceof ActionType m) { result = new ActionType(mType.getUID(), mType.getConfigurationDescriptions(), mType.getLabel(), mType.getDescription(), mType.getTags(), mType.getVisibility(), m.getInputs(), m.getOutputs()); } else { diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/module/provider/AnnotationActionModuleTypeHelper.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/module/provider/AnnotationActionModuleTypeHelper.java index 26f3758725f..75a651f318d 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/module/provider/AnnotationActionModuleTypeHelper.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/module/provider/AnnotationActionModuleTypeHelper.java @@ -116,9 +116,7 @@ private List getInputsFromAction(Method method) { "", "")); } else if (paramAnnotations.length == 1) { Annotation a = paramAnnotations[0]; - if (a instanceof ActionInput) { - ActionInput inp = (ActionInput) a; - + if (a instanceof ActionInput inp) { // check if a type is given, otherwise use the java type specified on the parameter String type; if (!"".equals(inp.type())) { @@ -174,10 +172,8 @@ private List getOutputsFromMethod(Method method) { if (configParam != null) { configDescriptions.add(configParam); } - - ActionType at = new ActionType(UID, configDescriptions, mi.getLabel(), mi.getDescription(), mi.getTags(), + return new ActionType(UID, configDescriptions, mi.getLabel(), mi.getDescription(), mi.getTags(), mi.getVisibility(), mi.getInputs(), mi.getOutputs()); - return at; } return null; } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/thingsupport/AnnotatedThingActionModuleTypeProvider.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/thingsupport/AnnotatedThingActionModuleTypeProvider.java index ce8fded8214..8151450db4d 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/thingsupport/AnnotatedThingActionModuleTypeProvider.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/thingsupport/AnnotatedThingActionModuleTypeProvider.java @@ -126,9 +126,7 @@ public Collection getModuleTypes(@Nullable Locale loca Bundle bundle = FrameworkUtil.getBundle(mi.getActionProvider().getClass()); ModuleType mt = helper.buildModuleType(uid, moduleInformation); - - ModuleType localizedModuleType = moduleTypeI18nService.getModuleTypePerLocale(mt, locale, bundle); - return localizedModuleType; + return moduleTypeI18nService.getModuleTypePerLocale(mt, locale, bundle); } return null; } @@ -228,8 +226,7 @@ public Collection getTypes() { @Override protected @Nullable ModuleHandler internalCreate(Module module, String ruleUID) { - if (module instanceof Action) { - Action actionModule = (Action) module; + if (module instanceof Action actionModule) { if (moduleInformation.containsKey(actionModule.getTypeUID())) { ModuleInformation finalMI = helper.getModuleInformationForIdentifier(actionModule, moduleInformation, true); diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/util/ConfigurationNormalizer.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/util/ConfigurationNormalizer.java index 8067a043967..b548573b86e 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/util/ConfigurationNormalizer.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/util/ConfigurationNormalizer.java @@ -83,7 +83,7 @@ public static void normalizeConfiguration(Configuration configuration, if (parameter != null) { String parameterName = entry.getKey(); final Object value = configuration.get(parameterName); - if (value instanceof String && ((String) value).contains("${")) { + if (value instanceof String string && string.contains("${")) { continue; // It is a reference } if (value == null) { diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/util/ModuleBuilder.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/util/ModuleBuilder.java index 6e5a1ccae60..41b264aedd3 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/util/ModuleBuilder.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/util/ModuleBuilder.java @@ -55,12 +55,12 @@ public static TriggerBuilder createTrigger(final Trigger trigger) { @SuppressWarnings("unchecked") public static , T extends Module> ModuleBuilder create(Module module) { - if (module instanceof Action) { - return (ModuleBuilder) createAction((Action) module); - } else if (module instanceof Condition) { - return (ModuleBuilder) createCondition((Condition) module); - } else if (module instanceof Trigger) { - return (ModuleBuilder) createTrigger((Trigger) module); + if (module instanceof Action action) { + return (ModuleBuilder) createAction(action); + } else if (module instanceof Condition condition) { + return (ModuleBuilder) createCondition(condition); + } else if (module instanceof Trigger trigger) { + return (ModuleBuilder) createTrigger(trigger); } else { throw new IllegalArgumentException("Parameter must be an instance of Action, Condition or Trigger."); } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/util/ReferenceResolver.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/util/ReferenceResolver.java index e0456892c1c..7c4c365ec5b 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/util/ReferenceResolver.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/util/ReferenceResolver.java @@ -81,15 +81,14 @@ public class ReferenceResolver { public static void updateConfiguration(Configuration config, Map context, Logger logger) { for (String configKey : config.keySet()) { Object o = config.get(configKey); - if (o instanceof String) { - Object result = resolveProperty(config, context, logger, configKey, (String) o); + if (o instanceof String string) { + Object result = resolveProperty(config, context, logger, configKey, string); config.put(configKey, result); - } else if (o instanceof List) { + } else if (o instanceof List list) { List resultList = new ArrayList<>(); - List list = (List) o; for (Object obj : list) { - if (obj instanceof String) { - resultList.add(resolveProperty(config, context, logger, configKey, (String) obj)); + if (obj instanceof String string) { + resultList.add(resolveProperty(config, context, logger, configKey, string)); } } config.put(configKey, resultList); @@ -124,10 +123,10 @@ public static Map getCompositeChildContext(Module module, Map resultContext = new HashMap<>(); Map inputs = null; - if (module instanceof Condition) { - inputs = ((Condition) module).getInputs(); - } else if (module instanceof Action) { - inputs = ((Action) module).getInputs(); + if (module instanceof Condition condition) { + inputs = condition.getInputs(); + } else if (module instanceof Action action) { + inputs = action.getInputs(); } if (inputs != null) { @@ -340,10 +339,10 @@ public static Object resolveComplexDataReference(Object object, String... tokens try { Object obj = object; for (String token : tokens) { - if (obj instanceof Map) { - obj = getValueFromMap(((Map) obj), token); - } else if (obj instanceof List) { - obj = getValueFromList(((List) obj), Integer.parseInt(token)); + if (obj instanceof Map map) { + obj = getValueFromMap(map, token); + } else if (obj instanceof List list) { + obj = getValueFromList(list, Integer.parseInt(token)); } else { final Class objClass = obj.getClass(); obj = getValueFromBean(objClass, obj, token); diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigDescriptionParameter.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigDescriptionParameter.java index 338a5944f49..98212328d12 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigDescriptionParameter.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigDescriptionParameter.java @@ -69,7 +69,7 @@ public enum Type { /** * The data type for a boolean ({@code true} or {@code false}). */ - BOOLEAN; + BOOLEAN } diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigParser.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigParser.java index faf94df0268..b59980bf193 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigParser.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigParser.java @@ -102,7 +102,7 @@ private ConfigParser() { } // Allows to have List, List, List etc (and the corresponding Set) - if (value instanceof Collection) { + if (value instanceof Collection collection1) { Class innerClass = (Class) ((ParameterizedType) field.getGenericType()) .getActualTypeArguments()[0]; Collection collection; @@ -114,7 +114,7 @@ private ConfigParser() { LOGGER.warn("Skipping field '{}', only List and Set is supported as target Collection", fieldName); continue; } - for (final Object it : (Collection) value) { + for (final Object it : collection1) { final Object normalized = valueAs(it, innerClass); if (normalized == null) { continue; @@ -185,8 +185,7 @@ public static T valueAsOrElse(@Nullable Object value, Class type, T defau Object result = value; // Handle the conversion case of Number to Float,Double,Long,Integer,Short,Byte,BigDecimal - if (value instanceof Number) { - Number number = (Number) value; + if (value instanceof Number number) { if (Float.class.equals(typeClass)) { result = number.floatValue(); } else if (Double.class.equals(typeClass)) { @@ -202,9 +201,8 @@ public static T valueAsOrElse(@Nullable Object value, Class type, T defau } else if (BigDecimal.class.equals(typeClass)) { result = new BigDecimal(number.toString()); } - } else if (value instanceof String && !String.class.equals(typeClass)) { + } else if (value instanceof String strValue && !String.class.equals(typeClass)) { // Handle the conversion case of String to Float,Double,Long,Integer,BigDecimal,Boolean - String strValue = (String) value; if (Float.class.equals(typeClass)) { result = Float.valueOf(strValue); } else if (Double.class.equals(typeClass)) { diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigUtil.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigUtil.java index f662f6d3167..88e92affcca 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigUtil.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigUtil.java @@ -184,8 +184,8 @@ public static Object normalizeType(Object value, @Nullable ConfigDescriptionPara return value; } else if (value instanceof Number) { return new BigDecimal(value.toString()); - } else if (value instanceof Collection) { - return normalizeCollection((Collection) value); + } else if (value instanceof Collection collection) { + return normalizeCollection(collection); } throw new IllegalArgumentException( "Invalid type '{" + value.getClass().getCanonicalName() + "}' of configuration value!"); diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/Configuration.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/Configuration.java index 9691601cec1..50d15021043 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/Configuration.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/Configuration.java @@ -135,7 +135,7 @@ public int hashCode() { @Override public boolean equals(@Nullable Object obj) { - return (obj instanceof Configuration) && this.properties.equals(((Configuration) obj).properties); + return (obj instanceof Configuration c) && this.properties.equals(c.properties); } @Override diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigurationSerializer.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigurationSerializer.java index 2c4658ddb46..53069f26eca 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigurationSerializer.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigurationSerializer.java @@ -38,9 +38,9 @@ public JsonElement serialize(Configuration src, Type typeOfSrc, JsonSerializatio JsonObject result = new JsonObject(); src.keySet().stream().sorted().forEachOrdered((String propName) -> { Object value = src.get(propName); - if (value instanceof List) { + if (value instanceof List list) { JsonArray array = new JsonArray(); - for (Object element : (List) value) { + for (Object element : list) { array.add(serializePrimitive(element)); } result.add(propName, array); @@ -52,12 +52,12 @@ public JsonElement serialize(Configuration src, Type typeOfSrc, JsonSerializatio } private JsonPrimitive serializePrimitive(Object primitive) { - if (primitive instanceof String) { - return new JsonPrimitive((String) primitive); - } else if (primitive instanceof Number) { - return new JsonPrimitive((Number) primitive); - } else if (primitive instanceof Boolean) { - return new JsonPrimitive((Boolean) primitive); + if (primitive instanceof String string) { + return new JsonPrimitive(string); + } else if (primitive instanceof Number number) { + return new JsonPrimitive(number); + } else if (primitive instanceof Boolean boolean1) { + return new JsonPrimitive(boolean1); } return null; } diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/OrderingMapSerializer.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/OrderingMapSerializer.java index 0b51e6e09ce..c6a74a4d5dd 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/OrderingMapSerializer.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/OrderingMapSerializer.java @@ -48,8 +48,8 @@ public JsonElement serialize(Map<@Nullable Object, @Nullable Object> src, Type t } possiblySortedStream.forEachOrdered(entry -> { Object key = entry.getKey(); - if (key instanceof String) { - ordered.add((String) key, context.serialize(entry.getValue())); + if (key instanceof String string) { + ordered.add(string, context.serialize(entry.getValue())); } else { JsonElement serialized = context.serialize(key); ordered.add(serialized.isJsonPrimitive() ? serialized.getAsString() : serialized.toString(), diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/internal/i18n/I18nConfigOptionsProvider.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/internal/i18n/I18nConfigOptionsProvider.java index 97a8845101d..c5613ca8cc0 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/internal/i18n/I18nConfigOptionsProvider.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/internal/i18n/I18nConfigOptionsProvider.java @@ -79,16 +79,10 @@ public class I18nConfigOptionsProvider implements ConfigOptionProvider { } private Collection processTimeZoneParam() { - Comparator byOffset = (t1, t2) -> { - return t1.getRawOffset() - t2.getRawOffset(); - }; - Comparator byID = (t1, t2) -> { - return t1.getID().compareTo(t2.getID()); - }; + Comparator byOffset = (t1, t2) -> t1.getRawOffset() - t2.getRawOffset(); + Comparator byID = (t1, t2) -> t1.getID().compareTo(t2.getID()); return ZoneId.getAvailableZoneIds().stream().map(TimeZone::getTimeZone).sorted(byOffset.thenComparing(byID)) - .map(tz -> { - return new ParameterOption(tz.getID(), getTimeZoneRepresentation(tz)); - }).collect(Collectors.toList()); + .map(tz -> new ParameterOption(tz.getID(), getTimeZoneRepresentation(tz))).collect(Collectors.toList()); } private static String getTimeZoneRepresentation(TimeZone tz) { diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/internal/normalization/ListNormalizer.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/internal/normalization/ListNormalizer.java index 8569e9459fb..ce19336fb32 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/internal/normalization/ListNormalizer.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/internal/normalization/ListNormalizer.java @@ -45,16 +45,16 @@ public Object doNormalize(Object value) { } return ret; } - if (value instanceof List) { - List ret = new ArrayList(((List) value).size()); - for (Object object : (List) value) { + if (value instanceof List list) { + List ret = new ArrayList(list.size()); + for (Object object : list) { ret.add(delegate.normalize(object)); } return ret; } - if (value instanceof Iterable) { + if (value instanceof Iterable iterable) { List ret = new ArrayList(); - for (Object object : (Iterable) value) { + for (Object object : iterable) { ret.add(delegate.normalize(object)); } return ret; diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/ConfigDescriptionConverter.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/ConfigDescriptionConverter.java index 6d7d50a2fa8..a3a30be56f9 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/ConfigDescriptionConverter.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/ConfigDescriptionConverter.java @@ -89,11 +89,11 @@ public ConfigDescriptionConverter() { // respective arrays while (nodeIterator.hasNext()) { Object node = nodeIterator.next(); - if (node instanceof ConfigDescriptionParameter) { - configDescriptionParams.add((ConfigDescriptionParameter) node); + if (node instanceof ConfigDescriptionParameter parameter) { + configDescriptionParams.add(parameter); } - if (node instanceof ConfigDescriptionParameterGroup) { - configDescriptionGroups.add((ConfigDescriptionParameterGroup) node); + if (node instanceof ConfigDescriptionParameterGroup group) { + configDescriptionGroups.add(group); } } diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/ConfigDescriptionParameterConverter.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/ConfigDescriptionParameterConverter.java index 6dcf86bcd15..7574db2d7f2 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/ConfigDescriptionParameterConverter.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/ConfigDescriptionParameterConverter.java @@ -146,12 +146,10 @@ private Boolean falseIfNull(@Nullable Boolean b) { } private @Nullable List readParameterOptions(@Nullable Object rawNodeValueList) { - if (rawNodeValueList instanceof List) { - List list = (List) rawNodeValueList; + if (rawNodeValueList instanceof List list) { List result = new ArrayList<>(); for (Object object : list) { - if (object instanceof NodeValue) { - NodeValue nodeValue = (NodeValue) object; + if (object instanceof NodeValue nodeValue) { String value = nodeValue.getAttributes().get("value"); String label = nodeValue.getValue().toString(); result.add(new ParameterOption(value, label)); diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/ConverterValueMap.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/ConverterValueMap.java index dd8e12d1fa4..d67175c8bad 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/ConverterValueMap.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/ConverterValueMap.java @@ -154,9 +154,9 @@ public static Map readValueMap(HierarchicalStreamReader reader, public @Nullable String getString(String nodeName, @Nullable String defaultValue) { Object value = this.valueMap.get(nodeName); - if (value instanceof String) { + if (value instanceof String string) { // fixes a formatting problem with line breaks in text - return ((String) value).replaceAll("\\n\\s*", " ").trim(); + return string.replaceAll("\\n\\s*", " ").trim(); } return defaultValue; diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/NodeIterator.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/NodeIterator.java index 16a215207f4..fdee71def99 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/NodeIterator.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/NodeIterator.java @@ -109,8 +109,8 @@ public void assertEndOfType() throws ConversionException { if (hasNext()) { Object nextNode = next(); - if (nextNode instanceof NodeName) { - if (nodeName.equals(((NodeName) nextNode).getNodeName())) { + if (nextNode instanceof NodeName name) { + if (nodeName.equals(name.getNodeName())) { return nextNode; } } @@ -147,9 +147,9 @@ public void assertEndOfType() throws ConversionException { if (hasNext()) { Object nextNode = next(); - if (nextNode instanceof NodeAttributes) { - if (nodeName.equals(((NodeName) nextNode).getNodeName())) { - return ((NodeAttributes) nextNode).getAttribute(attributeName); + if (nextNode instanceof NodeAttributes attributes) { + if (nodeName.equals(attributes.getNodeName())) { + return attributes.getAttribute(attributeName); } } @@ -180,8 +180,8 @@ public void assertEndOfType() throws ConversionException { public @Nullable Object nextValue(String nodeName, boolean required) throws ConversionException { Object value = next(nodeName, required); - if (value instanceof NodeValue) { - return ((NodeValue) value).getValue(); + if (value instanceof NodeValue nodeValue) { + return nodeValue.getValue(); } return null; @@ -203,8 +203,8 @@ public void assertEndOfType() throws ConversionException { public @Nullable List<@NonNull ?> nextList(String nodeName, boolean required) throws ConversionException { Object list = next(nodeName, required); - if (list instanceof NodeList) { - return ((NodeList) list).getList(); + if (list instanceof NodeList nodeList) { + return nodeList.getList(); } return null; diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/NodeValue.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/NodeValue.java index 55710a7059b..042254d6f82 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/NodeValue.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/NodeValue.java @@ -55,8 +55,8 @@ public NodeValue(String nodeName, @Nullable Map attributes, @Nul private @Nullable Object formatText(@Nullable Object object) { // fixes a formatting problem with line breaks in text - if (object instanceof String) { - return ((String) object).replaceAll("\\n\\s*", " ").trim(); + if (object instanceof String string) { + return string.replaceAll("\\n\\s*", " ").trim(); } return object; diff --git a/bundles/org.openhab.core.config.discovery/src/main/java/org/openhab/core/config/discovery/AbstractDiscoveryService.java b/bundles/org.openhab.core.config.discovery/src/main/java/org/openhab/core/config/discovery/AbstractDiscoveryService.java index acfaf9192a4..094ab1f484b 100644 --- a/bundles/org.openhab.core.config.discovery/src/main/java/org/openhab/core/config/discovery/AbstractDiscoveryService.java +++ b/bundles/org.openhab.core.config.discovery/src/main/java/org/openhab/core/config/discovery/AbstractDiscoveryService.java @@ -429,15 +429,15 @@ protected long getTimestampOfLastScan() { } private boolean getAutoDiscoveryEnabled(Object autoDiscoveryEnabled) { - if (autoDiscoveryEnabled instanceof String) { - return Boolean.valueOf((String) autoDiscoveryEnabled); + if (autoDiscoveryEnabled instanceof String string) { + return Boolean.valueOf(string); } else { return Boolean.TRUE.equals(autoDiscoveryEnabled); } } private String inferKey(DiscoveryResult discoveryResult, String lastSegment) { - return "discovery." + discoveryResult.getThingUID().getAsString().replaceAll(":", ".") + "." + lastSegment; + return "discovery." + discoveryResult.getThingUID().getAsString().replace(":", ".") + "." + lastSegment; } protected DiscoveryResult getLocalizedDiscoveryResult(final DiscoveryResult discoveryResult, diff --git a/bundles/org.openhab.core.config.discovery/src/main/java/org/openhab/core/config/discovery/DiscoveryResultFlag.java b/bundles/org.openhab.core.config.discovery/src/main/java/org/openhab/core/config/discovery/DiscoveryResultFlag.java index b668a5fe18e..524c5ef400a 100644 --- a/bundles/org.openhab.core.config.discovery/src/main/java/org/openhab/core/config/discovery/DiscoveryResultFlag.java +++ b/bundles/org.openhab.core.config.discovery/src/main/java/org/openhab/core/config/discovery/DiscoveryResultFlag.java @@ -35,6 +35,6 @@ public enum DiscoveryResultFlag { * The flag {@code IGNORED} to signal that the result object should be regarded * as known by the system so that a further processing should be skipped. */ - IGNORED; + IGNORED } diff --git a/bundles/org.openhab.core.config.discovery/src/main/java/org/openhab/core/config/discovery/internal/PersistentInbox.java b/bundles/org.openhab.core.config.discovery/src/main/java/org/openhab/core/config/discovery/internal/PersistentInbox.java index d55ee3045fa..c56a661426d 100644 --- a/bundles/org.openhab.core.config.discovery/src/main/java/org/openhab/core/config/discovery/internal/PersistentInbox.java +++ b/bundles/org.openhab.core.config.discovery/src/main/java/org/openhab/core/config/discovery/internal/PersistentInbox.java @@ -284,8 +284,7 @@ private void internalAdd(DiscoveryResultWrapper discoveryResultWrapper) { logger.info("Added new thing '{}' to inbox.", thingUID); discoveryResultWrapper.future.complete(true); } else { - if (inboxResult instanceof DiscoveryResultImpl) { - DiscoveryResultImpl resultImpl = (DiscoveryResultImpl) inboxResult; + if (inboxResult instanceof DiscoveryResultImpl resultImpl) { resultImpl.synchronize(discoveryResult); discoveryResultStorage.put(discoveryResult.getThingUID().toString(), resultImpl); notifyListeners(resultImpl, EventType.UPDATED); @@ -478,8 +477,7 @@ public void updated(Thing oldThing, Thing thing) { @Override public void setFlag(ThingUID thingUID, @Nullable DiscoveryResultFlag flag) { DiscoveryResult result = get(thingUID); - if (result instanceof DiscoveryResultImpl) { - DiscoveryResultImpl resultImpl = (DiscoveryResultImpl) result; + if (result instanceof DiscoveryResultImpl resultImpl) { resultImpl.setFlag((flag == null) ? DiscoveryResultFlag.NEW : flag); discoveryResultStorage.put(resultImpl.getThingUID().toString(), resultImpl); notifyListeners(resultImpl, EventType.UPDATED); diff --git a/bundles/org.openhab.core.config.discovery/src/test/java/org/openhab/core/config/discovery/AbstractDiscoveryServiceTest.java b/bundles/org.openhab.core.config.discovery/src/test/java/org/openhab/core/config/discovery/AbstractDiscoveryServiceTest.java index 2842a08d851..f0e31c81b19 100644 --- a/bundles/org.openhab.core.config.discovery/src/test/java/org/openhab/core/config/discovery/AbstractDiscoveryServiceTest.java +++ b/bundles/org.openhab.core.config.discovery/src/test/java/org/openhab/core/config/discovery/AbstractDiscoveryServiceTest.java @@ -51,10 +51,10 @@ public class AbstractDiscoveryServiceTest implements DiscoveryListener { private static final String VALUE1 = "value1"; private static final String VALUE2 = "value2"; private final Map properties = Map.of(KEY1, VALUE1, KEY2, VALUE2); - private static final String DISCOVERY_THING2_INFERED_KEY = "discovery." - + THING_UID2.getAsString().replaceAll(":", ".") + ".label"; - private static final String DISCOVERY_THING4_INFERED_KEY = "discovery." - + THING_UID4.getAsString().replaceAll(":", ".") + ".label"; + private static final String DISCOVERY_THING2_INFERED_KEY = "discovery." + THING_UID2.getAsString().replace(":", ".") + + ".label"; + private static final String DISCOVERY_THING4_INFERED_KEY = "discovery." + THING_UID4.getAsString().replace(":", ".") + + ".label"; private static final String DISCOVERY_LABEL = "Result Test"; private static final String DISCOVERY_LABEL_KEY1 = "@text/test"; private static final String DISCOVERY_LABEL_KEY2 = "@text/test2 [ \"50\", \"number\" ]"; diff --git a/bundles/org.openhab.core.config.dispatch/src/main/java/org/openhab/core/config/dispatch/internal/ConfigDispatcher.java b/bundles/org.openhab.core.config.dispatch/src/main/java/org/openhab/core/config/dispatch/internal/ConfigDispatcher.java index 382b97ecdd0..3246d06dcb1 100644 --- a/bundles/org.openhab.core.config.dispatch/src/main/java/org/openhab/core/config/dispatch/internal/ConfigDispatcher.java +++ b/bundles/org.openhab.core.config.dispatch/src/main/java/org/openhab/core/config/dispatch/internal/ConfigDispatcher.java @@ -233,7 +233,7 @@ public void processConfigFile(File dir) { Arrays.sort(files, new Comparator() { @Override public int compare(File left, File right) { - return Long.valueOf(left.lastModified()).compareTo(right.lastModified()); + return Long.compare(left.lastModified(), right.lastModified()); } }); for (File file : files) { diff --git a/bundles/org.openhab.core.ephemeris/src/main/java/org/openhab/core/ephemeris/internal/EphemerisManagerImpl.java b/bundles/org.openhab.core.ephemeris/src/main/java/org/openhab/core/ephemeris/internal/EphemerisManagerImpl.java index 8b68ab80b59..ab27188f319 100644 --- a/bundles/org.openhab.core.ephemeris/src/main/java/org/openhab/core/ephemeris/internal/EphemerisManagerImpl.java +++ b/bundles/org.openhab.core.ephemeris/src/main/java/org/openhab/core/ephemeris/internal/EphemerisManagerImpl.java @@ -153,8 +153,8 @@ protected void modified(Map config) { } else { logger.warn("Erroneous day set definition {} : {}", e.getKey(), entry); } - } else if (entry instanceof Iterable) { - addDayset(setName, (Iterable) entry); + } else if (entry instanceof Iterable iterable) { + addDayset(setName, iterable); } } else { logger.warn("Erroneous day set definition {}", e.getKey()); diff --git a/bundles/org.openhab.core.io.bin2json/src/main/java/org/openhab/core/io/bin2json/Bin2Json.java b/bundles/org.openhab.core.io.bin2json/src/main/java/org/openhab/core/io/bin2json/Bin2Json.java index 203c3f644ec..f268a9b4316 100644 --- a/bundles/org.openhab.core.io.bin2json/src/main/java/org/openhab/core/io/bin2json/Bin2Json.java +++ b/bundles/org.openhab.core.io.bin2json/src/main/java/org/openhab/core/io/bin2json/Bin2Json.java @@ -166,41 +166,40 @@ private JsonObject convertToJSon(@Nullable final JsonObject json, final JBBPAbst final String fieldName = field.getFieldName() == null ? "nonamed" : field.getFieldName(); if (field instanceof JBBPAbstractArrayField) { final JsonArray jsonArray = new JsonArray(); - if (field instanceof JBBPFieldArrayBit) { - for (final byte b : ((JBBPFieldArrayBit) field).getArray()) { + if (field instanceof JBBPFieldArrayBit bit) { + for (final byte b : bit.getArray()) { jsonArray.add(new JsonPrimitive(b)); } - } else if (field instanceof JBBPFieldArrayBoolean) { - for (final boolean b : ((JBBPFieldArrayBoolean) field).getArray()) { + } else if (field instanceof JBBPFieldArrayBoolean boolean1) { + for (final boolean b : boolean1.getArray()) { jsonArray.add(new JsonPrimitive(b)); } - } else if (field instanceof JBBPFieldArrayByte) { - for (final byte b : ((JBBPFieldArrayByte) field).getArray()) { + } else if (field instanceof JBBPFieldArrayByte byte1) { + for (final byte b : byte1.getArray()) { jsonArray.add(new JsonPrimitive(b)); } - } else if (field instanceof JBBPFieldArrayInt) { - for (final int b : ((JBBPFieldArrayInt) field).getArray()) { + } else if (field instanceof JBBPFieldArrayInt int1) { + for (final int b : int1.getArray()) { jsonArray.add(new JsonPrimitive(b)); } - } else if (field instanceof JBBPFieldArrayLong) { - for (final long b : ((JBBPFieldArrayLong) field).getArray()) { + } else if (field instanceof JBBPFieldArrayLong long1) { + for (final long b : long1.getArray()) { jsonArray.add(new JsonPrimitive(b)); } - } else if (field instanceof JBBPFieldArrayShort) { - for (final short b : ((JBBPFieldArrayShort) field).getArray()) { + } else if (field instanceof JBBPFieldArrayShort short1) { + for (final short b : short1.getArray()) { jsonArray.add(new JsonPrimitive(b)); } - } else if (field instanceof JBBPFieldArrayStruct) { - final JBBPFieldArrayStruct array = (JBBPFieldArrayStruct) field; + } else if (field instanceof JBBPFieldArrayStruct array) { for (int i = 0; i < array.size(); i++) { jsonArray.add(convertToJSon(new JsonObject(), array.getElementAt(i))); } - } else if (field instanceof JBBPFieldArrayUByte) { - for (final byte b : ((JBBPFieldArrayUByte) field).getArray()) { + } else if (field instanceof JBBPFieldArrayUByte byte1) { + for (final byte b : byte1.getArray()) { jsonArray.add(new JsonPrimitive(b & 0xFF)); } - } else if (field instanceof JBBPFieldArrayUShort) { - for (final short b : ((JBBPFieldArrayUShort) field).getArray()) { + } else if (field instanceof JBBPFieldArrayUShort short1) { + for (final short b : short1.getArray()) { jsonArray.add(new JsonPrimitive(b & 0xFFFF)); } } else { @@ -208,20 +207,19 @@ private JsonObject convertToJSon(@Nullable final JsonObject json, final JBBPAbst } jsn.add(fieldName, jsonArray); } else { - if (field instanceof JBBPFieldBit) { - jsn.addProperty(fieldName, ((JBBPFieldBit) field).getAsInt()); - } else if (field instanceof JBBPFieldBoolean) { - jsn.addProperty(fieldName, ((JBBPFieldBoolean) field).getAsBool()); - } else if (field instanceof JBBPFieldByte) { - jsn.addProperty(fieldName, ((JBBPFieldByte) field).getAsInt()); - } else if (field instanceof JBBPFieldInt) { - jsn.addProperty(fieldName, ((JBBPFieldInt) field).getAsInt()); - } else if (field instanceof JBBPFieldLong) { - jsn.addProperty(fieldName, ((JBBPFieldLong) field).getAsLong()); - } else if (field instanceof JBBPFieldShort) { - jsn.addProperty(fieldName, ((JBBPFieldShort) field).getAsInt()); - } else if (field instanceof JBBPFieldStruct) { - final JBBPFieldStruct struct = (JBBPFieldStruct) field; + if (field instanceof JBBPFieldBit bit) { + jsn.addProperty(fieldName, bit.getAsInt()); + } else if (field instanceof JBBPFieldBoolean boolean1) { + jsn.addProperty(fieldName, boolean1.getAsBool()); + } else if (field instanceof JBBPFieldByte byte1) { + jsn.addProperty(fieldName, byte1.getAsInt()); + } else if (field instanceof JBBPFieldInt int1) { + jsn.addProperty(fieldName, int1.getAsInt()); + } else if (field instanceof JBBPFieldLong long1) { + jsn.addProperty(fieldName, long1.getAsLong()); + } else if (field instanceof JBBPFieldShort short1) { + jsn.addProperty(fieldName, short1.getAsInt()); + } else if (field instanceof JBBPFieldStruct struct) { final JsonObject obj = new JsonObject(); for (final JBBPAbstractField f : struct.getArray()) { convertToJSon(obj, f); @@ -231,10 +229,10 @@ private JsonObject convertToJSon(@Nullable final JsonObject json, final JBBPAbst } else { jsn.add(fieldName, obj); } - } else if (field instanceof JBBPFieldUByte) { - jsn.addProperty(fieldName, ((JBBPFieldUByte) field).getAsInt()); - } else if (field instanceof JBBPFieldUShort) { - jsn.addProperty(fieldName, ((JBBPFieldUShort) field).getAsInt()); + } else if (field instanceof JBBPFieldUByte byte1) { + jsn.addProperty(fieldName, byte1.getAsInt()); + } else if (field instanceof JBBPFieldUShort short1) { + jsn.addProperty(fieldName, short1.getAsInt()); } else { throw new ConversionException(String.format("Unexpected field '%s'", field)); } diff --git a/bundles/org.openhab.core.io.console/src/main/java/org/openhab/core/io/console/internal/extension/ItemConsoleCommandExtension.java b/bundles/org.openhab.core.io.console/src/main/java/org/openhab/core/io/console/internal/extension/ItemConsoleCommandExtension.java index 02d6a5aecd6..4cd4d45dc97 100644 --- a/bundles/org.openhab.core.io.console/src/main/java/org/openhab/core/io/console/internal/extension/ItemConsoleCommandExtension.java +++ b/bundles/org.openhab.core.io.console/src/main/java/org/openhab/core/io/console/internal/extension/ItemConsoleCommandExtension.java @@ -138,8 +138,7 @@ public void execute(String[] args, Console console) { case SUBCMD_ADDTAG: if (args.length > 2) { Item item = itemRegistry.get(args[1]); - if (item instanceof GenericItem) { - GenericItem gItem = (GenericItem) item; + if (item instanceof GenericItem gItem) { handleTags(gItem::addTag, args[2], gItem, console); } } else { @@ -150,8 +149,7 @@ public void execute(String[] args, Console console) { case SUBCMD_RMTAG: if (args.length > 2) { Item item = itemRegistry.get(args[1]); - if (item instanceof GenericItem) { - GenericItem gItem = (GenericItem) item; + if (item instanceof GenericItem gItem) { handleTags(gItem::removeTag, args[2], gItem, console); } } else { diff --git a/bundles/org.openhab.core.io.console/src/main/java/org/openhab/core/io/console/internal/extension/MetadataConsoleCommandExtension.java b/bundles/org.openhab.core.io.console/src/main/java/org/openhab/core/io/console/internal/extension/MetadataConsoleCommandExtension.java index 1096e29c4e9..b6914215f4b 100644 --- a/bundles/org.openhab.core.io.console/src/main/java/org/openhab/core/io/console/internal/extension/MetadataConsoleCommandExtension.java +++ b/bundles/org.openhab.core.io.console/src/main/java/org/openhab/core/io/console/internal/extension/MetadataConsoleCommandExtension.java @@ -97,7 +97,7 @@ public void execute(String[] args, Console console) { } break; case SUBCMD_ORPHAN: - if (args.length == 2 && (args[1].equals("list") || args[1].equals("purge"))) { + if (args.length == 2 && ("list".equals(args[1]) || "purge".equals(args[1]))) { orphan(console, args[1], metadataRegistry.getAll(), itemRegistry.getAll()); } else { console.println("Specify action 'list' or 'purge' to be executed: orphan "); diff --git a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthenticationHandler.java b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthenticationHandler.java index a6276171d69..0fd2fd60b60 100644 --- a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthenticationHandler.java +++ b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthenticationHandler.java @@ -153,8 +153,8 @@ void modified(Map properties) { } Object loginUri = properties.get(AUTHENTICATION_ENDPOINT); - if (loginUri instanceof String) { - this.loginUri = (String) loginUri; + if (loginUri instanceof String string) { + this.loginUri = string; } } diff --git a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthorizePageServlet.java b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthorizePageServlet.java index 64a27c7bbcc..b116d3051e5 100644 --- a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthorizePageServlet.java +++ b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthorizePageServlet.java @@ -166,12 +166,11 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S String authorizationCode = UUID.randomUUID().toString().replace("-", ""); - if (user instanceof ManagedUser) { + if (user instanceof ManagedUser managedUser) { String codeChallenge = params.containsKey("code_challenge") ? params.get("code_challenge")[0] : null; String codeChallengeMethod = params.containsKey("code_challenge_method") ? params.get("code_challenge_method")[0] : null; - ManagedUser managedUser = (ManagedUser) user; PendingToken pendingToken = new PendingToken(authorizationCode, clientId, baseRedirectUri, scope, codeChallenge, codeChallengeMethod); managedUser.setPendingToken(pendingToken); diff --git a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/CreateAPITokenPageServlet.java b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/CreateAPITokenPageServlet.java index 10a7fcc387d..f3c21a728aa 100644 --- a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/CreateAPITokenPageServlet.java +++ b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/CreateAPITokenPageServlet.java @@ -99,9 +99,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S User user = login(username, password); String newApiToken; - if (user instanceof ManagedUser) { - if (((ManagedUser) user).getApiTokens().stream() - .anyMatch(apiToken -> apiToken.getName().equals(tokenName))) { + if (user instanceof ManagedUser managedUser) { + if (managedUser.getApiTokens().stream().anyMatch(apiToken -> apiToken.getName().equals(tokenName))) { resp.setContentType("text/html;charset=UTF-8"); resp.getWriter().append( getPageBody(params, getLocalizedMessage("auth.createapitoken.name.unique.fail"), false)); diff --git a/bundles/org.openhab.core.io.jetty.certificate/src/main/java/org/openhab/core/io/jetty/certificate/internal/CertificateGenerator.java b/bundles/org.openhab.core.io.jetty.certificate/src/main/java/org/openhab/core/io/jetty/certificate/internal/CertificateGenerator.java index 640bb22057f..7ed9834565e 100644 --- a/bundles/org.openhab.core.io.jetty.certificate/src/main/java/org/openhab/core/io/jetty/certificate/internal/CertificateGenerator.java +++ b/bundles/org.openhab.core.io.jetty.certificate/src/main/java/org/openhab/core/io/jetty/certificate/internal/CertificateGenerator.java @@ -125,7 +125,7 @@ private KeyStore ensureKeystore() throws KeyStoreException { throw new KeyStoreException("Failed to create the keystore " + keystoreFile.getAbsolutePath(), e); } } else { - try (InputStream keystoreStream = new FileInputStream(keystoreFile);) { + try (InputStream keystoreStream = new FileInputStream(keystoreFile)) { logger.debug("Keystore found. Trying to load {}", keystoreFile.getAbsolutePath()); keyStore.load(keystoreStream, KEYSTORE_PASSWORD.toCharArray()); } catch (NoSuchAlgorithmException | CertificateException | IOException e) { diff --git a/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/HttpUtil.java b/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/HttpUtil.java index 9cfe66ca3ff..cb108e33737 100644 --- a/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/HttpUtil.java +++ b/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/HttpUtil.java @@ -154,7 +154,7 @@ public static String executeUrl(String httpMethod, String url, Properties httpHe String proxyPassword, String nonProxyHosts) throws IOException { ContentResponse response = executeUrlAndGetReponse(httpMethod, url, httpHeaders, content, contentType, timeout, proxyHost, proxyPort, proxyUser, proxyPassword, nonProxyHosts); - String encoding = response.getEncoding() != null ? response.getEncoding().replaceAll("\"", "").trim() + String encoding = response.getEncoding() != null ? response.getEncoding().replace("\"", "").trim() : StandardCharsets.UTF_8.name(); String responseBody; try { diff --git a/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/internal/TrustManagerUtil.java b/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/internal/TrustManagerUtil.java index 19fb96a90db..cb9dbb900bc 100644 --- a/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/internal/TrustManagerUtil.java +++ b/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/internal/TrustManagerUtil.java @@ -36,8 +36,8 @@ static X509ExtendedTrustManager keyStoreToTrustManager(@Nullable KeyStore keySto tmf.init(keyStore); // Get hold of the X509ExtendedTrustManager for (TrustManager tm : tmf.getTrustManagers()) { - if (tm instanceof X509ExtendedTrustManager) { - return (X509ExtendedTrustManager) tm; + if (tm instanceof X509ExtendedTrustManager manager) { + return manager; } } } catch (NoSuchAlgorithmException e) { diff --git a/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/internal/WebClientFactoryImpl.java b/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/internal/WebClientFactoryImpl.java index 01fea18467d..21b8ab051fd 100644 --- a/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/internal/WebClientFactoryImpl.java +++ b/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/internal/WebClientFactoryImpl.java @@ -189,9 +189,9 @@ private int getConfigParameter(Map parameters, String parameter, if (value instanceof Integer) { return (Integer) value; } - if (value instanceof String) { + if (value instanceof String string) { try { - return Integer.parseInt((String) value); + return Integer.parseInt(string); } catch (NumberFormatException e) { logger.warn("ignoring invalid value {} for parameter {}", value, parameter); return defaultValue; diff --git a/bundles/org.openhab.core.io.net/src/test/java/org/openhab/core/io/net/exec/ExecUtilTest.java b/bundles/org.openhab.core.io.net/src/test/java/org/openhab/core/io/net/exec/ExecUtilTest.java index 678c1787612..cfcf9ac25bc 100644 --- a/bundles/org.openhab.core.io.net/src/test/java/org/openhab/core/io/net/exec/ExecUtilTest.java +++ b/bundles/org.openhab.core.io.net/src/test/java/org/openhab/core/io/net/exec/ExecUtilTest.java @@ -64,7 +64,7 @@ public void testExecuteCommandLineAndWaitResponseWithArguments() { private boolean isWindowsSystem() { String osName = System.getProperty("os.name").toLowerCase(); - return osName.indexOf("windows") >= 0; + return osName.contains("windows"); } @Test diff --git a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthFilter.java b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthFilter.java index 2b159e8ddfe..7faa3b41051 100644 --- a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthFilter.java +++ b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthFilter.java @@ -115,7 +115,6 @@ public class AuthFilter implements ContainerRequestFilter { @Override public void added(User element) { - return; } @Override diff --git a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JwtHelper.java b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JwtHelper.java index 0b35b839c9d..69ad5f14cb7 100644 --- a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JwtHelper.java +++ b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JwtHelper.java @@ -124,9 +124,8 @@ public String getJwtAccessToken(User user, String clientId, String scope, int to jws.setKey(jwtWebKey.getPrivateKey()); jws.setKeyIdHeaderValue(jwtWebKey.getKeyId()); jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); - String jwt = jws.getCompactSerialization(); - return jwt; + return jws.getCompactSerialization(); } catch (JoseException e) { throw new IllegalStateException("Error while writing JWT token", e); } diff --git a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResource.java b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResource.java index 105afb2f710..51f24f0672e 100644 --- a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResource.java +++ b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResource.java @@ -277,7 +277,7 @@ private Response processAuthorizationCodeGrant(String code, String redirectUri, return (pendingToken != null && pendingToken.getAuthorizationCode().equals(code)); }).findAny(); - if (!user.isPresent()) { + if (user.isEmpty()) { logger.warn("Couldn't find a user with the provided authentication code pending"); throw new TokenEndpointException(ErrorType.INVALID_GRANT); } @@ -382,7 +382,7 @@ private Response processRefreshTokenGrant(String clientId, @Nullable String refr u -> ((ManagedUser) u).getSessions().stream().anyMatch(s -> refreshToken.equals(s.getRefreshToken()))) .findAny(); - if (!refreshTokenUser.isPresent()) { + if (refreshTokenUser.isEmpty()) { logger.warn("Couldn't find a user with a session matching the provided refresh_token"); throw new TokenEndpointException(ErrorType.INVALID_GRANT); } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/GsonMessageBodyWriter.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/GsonMessageBodyWriter.java index a03f906cf4e..11da99be169 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/GsonMessageBodyWriter.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/GsonMessageBodyWriter.java @@ -79,8 +79,8 @@ public void writeTo(final T object, final Class type, final Type genericType, } try { - if (object instanceof InputStream && object instanceof JSONInputStream) { - ((InputStream) object).transferTo(entityStream); + if (object instanceof InputStream stream && object instanceof JSONInputStream) { + stream.transferTo(entityStream); } else { entityStream.write(gson.toJson(object).getBytes(StandardCharsets.UTF_8)); } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/channel/ChannelTypeResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/channel/ChannelTypeResource.java index b79db676d5e..46dd33c3ac9 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/channel/ChannelTypeResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/channel/ChannelTypeResource.java @@ -173,8 +173,8 @@ public Response getLinkableItemTypes( Set result = new HashSet<>(); for (ProfileType profileType : profileTypeRegistry.getProfileTypes()) { - if (profileType instanceof TriggerProfileType) { - if (((TriggerProfileType) profileType).getSupportedChannelTypeUIDs().contains(ctUID)) { + if (profileType instanceof TriggerProfileType type) { + if (type.getSupportedChannelTypeUIDs().contains(ctUID)) { for (String itemType : profileType.getSupportedItemTypes()) { result.add(itemType); } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/config/ConfigDescriptionResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/config/ConfigDescriptionResource.java index 47107fbaf66..3f0579f1495 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/config/ConfigDescriptionResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/config/ConfigDescriptionResource.java @@ -102,9 +102,9 @@ public Response getAll( @QueryParam("scheme") @Parameter(description = "scheme filter") @Nullable String scheme) { Locale locale = localeService.getLocale(language); Collection configDescriptions = configDescriptionRegistry.getConfigDescriptions(locale); - return Response.ok(new Stream2JSONInputStream(configDescriptions.stream().filter(configDescription -> { - return scheme == null || scheme.equals(configDescription.getUID().getScheme()); - }).map(EnrichedConfigDescriptionDTOMapper::map))).build(); + return Response.ok(new Stream2JSONInputStream(configDescriptions.stream() + .filter(configDescription -> scheme == null || scheme.equals(configDescription.getUID().getScheme())) + .map(EnrichedConfigDescriptionDTOMapper::map))).build(); } @GET diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java index 292376c4d21..46d734be7a2 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java @@ -333,9 +333,9 @@ public Response getBinaryItemState(@HeaderParam("Accept") @Nullable String media // if it exists if (item != null) { State state = item.getState(); - if (state instanceof RawType) { - String mimeType = ((RawType) state).getMimeType(); - byte[] data = ((RawType) state).getBytes(); + if (state instanceof RawType type) { + String mimeType = type.getMimeType(); + byte[] data = type.getBytes(); if ((acceptedMediaTypes.contains("image/*") && mimeType.startsWith("image/")) || acceptedMediaTypes.contains(mimeType)) { return Response.ok(data).type(mimeType).build(); @@ -921,8 +921,8 @@ private void addMetadata(EnrichedItemDTO dto, Set namespaces, @Nullable metadata.put(namespace, mdDto); } } - if (dto instanceof EnrichedGroupItemDTO) { - for (EnrichedItemDTO member : ((EnrichedGroupItemDTO) dto).members) { + if (dto instanceof EnrichedGroupItemDTO tO) { + for (EnrichedItemDTO member : tO.members) { addMetadata(member, namespaces, filter); } } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/profile/ProfileTypeResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/profile/ProfileTypeResource.java index f15688c83bf..408c600ae18 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/profile/ProfileTypeResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/profile/ProfileTypeResource.java @@ -152,8 +152,7 @@ private boolean profileTypeMatchesItemType(ProfileType pt, String itemType) { } private boolean triggerProfileMatchesProfileType(ProfileType profileType, ChannelType channelType) { - if (profileType instanceof TriggerProfileType) { - TriggerProfileType triggerProfileType = (TriggerProfileType) profileType; + if (profileType instanceof TriggerProfileType triggerProfileType) { if (triggerProfileType.getSupportedChannelTypeUIDs().isEmpty()) { return true; @@ -167,8 +166,7 @@ private boolean triggerProfileMatchesProfileType(ProfileType profileType, Channe } private boolean stateProfileMatchesProfileType(ProfileType profileType, ChannelType channelType) { - if (profileType instanceof StateProfileType) { - StateProfileType stateProfileType = (StateProfileType) profileType; + if (profileType instanceof StateProfileType stateProfileType) { if (stateProfileType.getSupportedItemTypesOfChannel().isEmpty()) { return true; diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/service/ConfigurableServiceResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/service/ConfigurableServiceResource.java index d404a5efed1..1b5f5490cf1 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/service/ConfigurableServiceResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/service/ConfigurableServiceResource.java @@ -364,13 +364,11 @@ private String getServiceId(ServiceReference serviceReference) { } final String serviceId; - if (pid instanceof String) { - serviceId = (String) pid; - } else if (pid instanceof String[]) { - final String[] pids = (String[]) pid; + if (pid instanceof String string) { + serviceId = string; + } else if (pid instanceof String[] pids) { serviceId = getServicePID(cn, Arrays.asList(pids)); - } else if (pid instanceof Collection) { - Collection pids = (Collection) pid; + } else if (pid instanceof Collection pids) { serviceId = getServicePID(cn, pids.stream().map(entry -> entry.toString()).collect(Collectors.toList())); } else { logger.warn("The component \"{}\" is using an unhandled service PID type ({}). Use component name.", cn, @@ -410,6 +408,6 @@ private String getServicePID(final String cn, final List pids) { } private String inferKey(String uri, String lastSegment) { - return "service." + uri.replaceAll(":", ".") + "." + lastSegment; + return "service." + uri.replace(":", ".") + "." + lastSegment; } } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/item/EnrichedItemDTOMapper.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/item/EnrichedItemDTOMapper.java index 9ce41efd977..2036e5b853f 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/item/EnrichedItemDTOMapper.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/item/EnrichedItemDTOMapper.java @@ -93,8 +93,7 @@ private static EnrichedItemDTO map(Item item, ItemDTO itemDTO, boolean drillDown EnrichedItemDTO enrichedItemDTO; - if (item instanceof GroupItem) { - GroupItem groupItem = (GroupItem) item; + if (item instanceof GroupItem groupItem) { EnrichedItemDTO[] memberDTOs; if (drillDown) { Collection members = new LinkedHashSet<>(); diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/SitemapSubscriptionService.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/SitemapSubscriptionService.java index 94ca20138b7..70bafa9e4bf 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/SitemapSubscriptionService.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/SitemapSubscriptionService.java @@ -267,8 +267,8 @@ private EList collectWidgets(String sitemapName, String pageId) { widgets = itemUIRegistry.getChildren(sitemap); } else { Widget pageWidget = itemUIRegistry.getWidget(sitemap, pageId); - if (pageWidget instanceof LinkableWidget) { - widgets = itemUIRegistry.getChildren((LinkableWidget) pageWidget); + if (pageWidget instanceof LinkableWidget widget) { + widgets = itemUIRegistry.getChildren(widget); // We add the page widget. It will help any UI to update the page title. widgets.add(pageWidget); } @@ -348,8 +348,7 @@ public Set getSubscribedEventTypes() { @Override public void receive(Event event) { - if (event instanceof ItemStatePredictedEvent) { - ItemStatePredictedEvent prediction = (ItemStatePredictedEvent) event; + if (event instanceof ItemStatePredictedEvent prediction) { Item item = itemUIRegistry.get(prediction.getItemName()); if (item instanceof GroupItem) { // don't send out auto-update events for group items as those will calculate their state based on their @@ -363,8 +362,7 @@ public void receive(Event event) { pageChangeListener.changeStateTo(item, prediction.getPredictedState()); } } - } else if (event instanceof ChannelDescriptionChangedEvent) { - ChannelDescriptionChangedEvent channelDescriptionChangedEvent = (ChannelDescriptionChangedEvent) event; + } else if (event instanceof ChannelDescriptionChangedEvent channelDescriptionChangedEvent) { channelDescriptionChangedEvent.getLinkedItemNames().forEach(itemName -> { for (PageChangeListener pageChangeListener : pageChangeListeners.values()) { pageChangeListener.descriptionChanged(itemName); diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/PageChangeListener.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/PageChangeListener.java index 263e6821e4d..3252af2f6bd 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/PageChangeListener.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/PageChangeListener.java @@ -79,8 +79,8 @@ private void updateItemsAndWidgets(EList widgets) { // cleanup statechange listeners in case widgets were removed items = getAllItems(this.widgets); for (Item item : items) { - if (item instanceof GenericItem) { - ((GenericItem) item).removeStateChangeListener(this); + if (item instanceof GenericItem genericItem) { + genericItem.removeStateChangeListener(this); } } } @@ -88,8 +88,8 @@ private void updateItemsAndWidgets(EList widgets) { this.widgets = widgets; items = getAllItems(widgets); for (Item item : items) { - if (item instanceof GenericItem) { - ((GenericItem) item).addStateChangeListener(this); + if (item instanceof GenericItem genericItem) { + genericItem.addStateChangeListener(this); } } } @@ -118,10 +118,10 @@ public void removeCallback(SitemapSubscriptionCallback callback) { */ public void dispose() { for (Item item : items) { - if (item instanceof GenericItem) { - ((GenericItem) item).removeStateChangeListener(this); - } else if (item instanceof GroupItem) { - ((GroupItem) item).removeStateChangeListener(this); + if (item instanceof GenericItem genericItem) { + genericItem.removeStateChangeListener(this); + } else if (item instanceof GroupItem groupItem) { + groupItem.removeStateChangeListener(this); } } } @@ -138,8 +138,8 @@ private Set getAllItems(EList widgets) { if (itemUIRegistry != null) { for (Widget widget : widgets) { addItemWithName(items, widget.getItem()); - if (widget instanceof Frame) { - items.addAll(getAllItems(((Frame) widget).getChildren())); + if (widget instanceof Frame frame) { + items.addAll(getAllItems(frame.getChildren())); } // now scan visibility rules for (VisibilityRule rule : widget.getVisibility()) { @@ -215,15 +215,14 @@ public void changeStateTo(Item item, State state) { private Set constructSitemapEvents(Item item, State state, List widgets) { Set events = new HashSet<>(); for (Widget w : widgets) { - if (w instanceof Frame) { - events.addAll(constructSitemapEvents(item, state, itemUIRegistry.getChildren((Frame) w))); + if (w instanceof Frame frame) { + events.addAll(constructSitemapEvents(item, state, itemUIRegistry.getChildren(frame))); } boolean itemBelongsToWidget = w.getItem() != null && w.getItem().equals(item.getName()); boolean skipWidget = !itemBelongsToWidget; // We skip the chart widgets having a refresh argument - if (!skipWidget && w instanceof Chart) { - Chart chartWidget = (Chart) w; + if (!skipWidget && w instanceof Chart chartWidget) { skipWidget = chartWidget.getRefresh() > 0; } if (!skipWidget || definesVisibilityOrColor(w, item.getName())) { @@ -326,8 +325,8 @@ public void descriptionChanged(String itemName) { private Set constructSitemapEventsForUpdatedDescr(Item item, List widgets) { Set events = new HashSet<>(); for (Widget w : widgets) { - if (w instanceof Frame) { - events.addAll(constructSitemapEventsForUpdatedDescr(item, itemUIRegistry.getChildren((Frame) w))); + if (w instanceof Frame frame) { + events.addAll(constructSitemapEventsForUpdatedDescr(item, itemUIRegistry.getChildren(frame))); } boolean itemBelongsToWidget = w.getItem() != null && w.getItem().equals(item.getName()); diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java index 2991d9b6ac2..478f244a141 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java @@ -368,8 +368,8 @@ private PageDTO getPageBean(String sitemapName, String pageId, URI uri, Locale l false, isLeaf(children), uri, locale, timeout, includeHidden); } else { Widget pageWidget = itemUIRegistry.getWidget(sitemap, pageId); - if (pageWidget instanceof LinkableWidget) { - EList children = itemUIRegistry.getChildren((LinkableWidget) pageWidget); + if (pageWidget instanceof LinkableWidget widget) { + EList children = itemUIRegistry.getChildren(widget); PageDTO pageBean = createPageBean(sitemapName, itemUIRegistry.getLabel(pageWidget), itemUIRegistry.getCategory(pageWidget), pageId, children, false, isLeaf(children), uri, locale, timeout, includeHidden); @@ -377,8 +377,8 @@ private PageDTO getPageBean(String sitemapName, String pageId, URI uri, Locale l while (parentPage instanceof Frame) { parentPage = parentPage.eContainer(); } - if (parentPage instanceof Widget) { - String parentId = itemUIRegistry.getWidgetId((Widget) parentPage); + if (parentPage instanceof Widget parentPageWidget) { + String parentId = itemUIRegistry.getWidgetId(parentPageWidget); pageBean.parent = getPageBean(sitemapName, parentId, uri, locale, timeout, includeHidden); pageBean.parent.widgets = null; pageBean.parent.parent = null; @@ -522,8 +522,7 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null bean.label = itemUIRegistry.getLabel(widget); bean.type = widget.eClass().getName(); bean.visibility = itemUIRegistry.getVisiblity(widget); - if (widget instanceof LinkableWidget) { - LinkableWidget linkableWidget = (LinkableWidget) widget; + if (widget instanceof LinkableWidget linkableWidget) { EList children = itemUIRegistry.getChildren(linkableWidget); if (widget instanceof Frame) { for (Widget child : children) { @@ -541,8 +540,7 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null isLeaf(children), uri, locale, false, evenIfHidden); } } - if (widget instanceof Switch) { - Switch switchWidget = (Switch) widget; + if (widget instanceof Switch switchWidget) { for (Mapping mapping : switchWidget.getMappings()) { MappingDTO mappingBean = new MappingDTO(); mappingBean.command = mapping.getCmd(); @@ -550,8 +548,7 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null bean.mappings.add(mappingBean); } } - if (widget instanceof Selection) { - Selection selectionWidget = (Selection) widget; + if (widget instanceof Selection selectionWidget) { for (Mapping mapping : selectionWidget.getMappings()) { MappingDTO mappingBean = new MappingDTO(); mappingBean.command = mapping.getCmd(); @@ -559,27 +556,23 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null bean.mappings.add(mappingBean); } } - if (widget instanceof Input) { - Input inputWidget = (Input) widget; + if (widget instanceof Input inputWidget) { bean.inputHint = inputWidget.getInputHint(); } - if (widget instanceof Slider) { - Slider sliderWidget = (Slider) widget; + if (widget instanceof Slider sliderWidget) { bean.sendFrequency = sliderWidget.getFrequency(); bean.switchSupport = sliderWidget.isSwitchEnabled(); bean.minValue = sliderWidget.getMinValue(); bean.maxValue = sliderWidget.getMaxValue(); bean.step = sliderWidget.getStep(); } - if (widget instanceof Image) { + if (widget instanceof Image imageWidget) { bean.url = buildProxyUrl(sitemapName, widget, uri); - Image imageWidget = (Image) widget; if (imageWidget.getRefresh() > 0) { bean.refresh = imageWidget.getRefresh(); } } - if (widget instanceof Video) { - Video videoWidget = (Video) widget; + if (widget instanceof Video videoWidget) { if (videoWidget.getEncoding() != null) { bean.encoding = videoWidget.getEncoding(); } @@ -589,17 +582,14 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null bean.url = buildProxyUrl(sitemapName, videoWidget, uri); } } - if (widget instanceof Webview) { - Webview webViewWidget = (Webview) widget; + if (widget instanceof Webview webViewWidget) { bean.url = webViewWidget.getUrl(); bean.height = webViewWidget.getHeight(); } - if (widget instanceof Mapview) { - Mapview mapViewWidget = (Mapview) widget; + if (widget instanceof Mapview mapViewWidget) { bean.height = mapViewWidget.getHeight(); } - if (widget instanceof Chart) { - Chart chartWidget = (Chart) widget; + if (widget instanceof Chart chartWidget) { bean.service = chartWidget.getService(); bean.period = chartWidget.getPeriod(); bean.legend = chartWidget.getLegend(); @@ -609,8 +599,7 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null bean.refresh = chartWidget.getRefresh(); } } - if (widget instanceof Setpoint) { - Setpoint setpointWidget = (Setpoint) widget; + if (widget instanceof Setpoint setpointWidget) { bean.minValue = setpointWidget.getMinValue(); bean.maxValue = setpointWidget.getMaxValue(); bean.step = setpointWidget.getStep(); @@ -641,12 +630,11 @@ private String buildProxyUrl(String sitemapName, Widget widget, URI uri) { private boolean isLeaf(EList children) { for (Widget w : children) { - if (w instanceof Frame) { - if (isLeaf(((Frame) w).getChildren())) { + if (w instanceof Frame frame) { + if (isLeaf(frame.getChildren())) { return false; } - } else if (w instanceof LinkableWidget) { - LinkableWidget linkableWidget = (LinkableWidget) w; + } else if (w instanceof LinkableWidget linkableWidget) { if (!itemUIRegistry.getChildren(linkableWidget).isEmpty()) { return false; } @@ -675,8 +663,8 @@ private boolean blockUnlessChangeOccurs(String sitemapname, String pageId) { timeout = waitForChanges(children); } else { Widget pageWidget = itemUIRegistry.getWidget(sitemap, pageId); - if (pageWidget instanceof LinkableWidget) { - EList children = itemUIRegistry.getChildren((LinkableWidget) pageWidget); + if (pageWidget instanceof LinkableWidget widget) { + EList children = itemUIRegistry.getChildren(widget); timeout = waitForChanges(children); } } @@ -728,24 +716,23 @@ private Set getAllItems(EList widgets) { for (Widget widget : widgets) { // We skip the chart widgets having a refresh argument boolean skipWidget = false; - if (widget instanceof Chart) { - Chart chartWidget = (Chart) widget; + if (widget instanceof Chart chartWidget) { skipWidget = chartWidget.getRefresh() > 0; } String itemName = widget.getItem(); if (!skipWidget && itemName != null) { try { Item item = itemUIRegistry.getItem(itemName); - if (item instanceof GenericItem) { - items.add((GenericItem) item); + if (item instanceof GenericItem genericItem) { + items.add(genericItem); } } catch (ItemNotFoundException e) { // ignore } } // Consider all items inside the frame - if (widget instanceof Frame) { - items.addAll(getAllItems(((Frame) widget).getChildren())); + if (widget instanceof Frame frame) { + items.addAll(getAllItems(frame.getChildren())); } // Consider items involved in any visibility, labelcolor, valuecolor and iconcolor condition items.addAll(getItemsInVisibilityCond(widget.getVisibility())); @@ -763,8 +750,8 @@ private Set getItemsInVisibilityCond(EList ruleList if (itemName != null) { try { Item item = itemUIRegistry.getItem(itemName); - if (item instanceof GenericItem) { - items.add((GenericItem) item); + if (item instanceof GenericItem genericItem) { + items.add(genericItem); } } catch (ItemNotFoundException e) { // ignore @@ -781,8 +768,8 @@ private Set getItemsInColorCond(EList colorList) { if (itemName != null) { try { Item item = itemUIRegistry.getItem(itemName); - if (item instanceof GenericItem) { - items.add((GenericItem) item); + if (item instanceof GenericItem genericItem) { + items.add(genericItem); } } catch (ItemNotFoundException e) { // ignore @@ -839,9 +826,9 @@ public void onEvent(SitemapEvent event) { if (sitemapName != null && sitemapName.equals(subscriptions.getSitemapName(info.subscriptionId)) && pageId != null && pageId.equals(subscriptions.getPageId(info.subscriptionId))) { if (logger.isDebugEnabled()) { - if (event instanceof SitemapWidgetEvent) { - logger.debug("Sent sitemap event for widget {} to subscription {}.", - ((SitemapWidgetEvent) event).widgetId, info.subscriptionId); + if (event instanceof SitemapWidgetEvent widgetEvent) { + logger.debug("Sent sitemap event for widget {} to subscription {}.", widgetEvent.widgetId, + info.subscriptionId); } else if (event instanceof ServerAliveEvent) { logger.debug("Sent alive event to subscription {}.", info.subscriptionId); } diff --git a/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/SseResource.java b/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/SseResource.java index 778192b6337..f5a9c562973 100644 --- a/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/SseResource.java +++ b/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/SseResource.java @@ -150,8 +150,8 @@ public void broadcast(Event event) { executorService.execute(() -> { handleEventBroadcastTopic(event); - if (event instanceof ItemStateChangedEvent) { - handleEventBroadcastItemState((ItemStateChangedEvent) event); + if (event instanceof ItemStateChangedEvent changedEvent) { + handleEventBroadcastItemState(changedEvent); } }); } @@ -236,7 +236,7 @@ public Object updateTrackedItems(@PathParam("connectionId") @Nullable String con } Optional itemStateInfo = itemStatesBroadcaster.getInfoIf(hasConnectionId(connectionId)) .findFirst(); - if (!itemStateInfo.isPresent()) { + if (itemStateInfo.isEmpty()) { return Response.status(Status.NOT_FOUND).build(); } diff --git a/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseItemStatesEventBuilder.java b/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseItemStatesEventBuilder.java index 6ec6ead98d6..28d7b1fc091 100644 --- a/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseItemStatesEventBuilder.java +++ b/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseItemStatesEventBuilder.java @@ -128,8 +128,7 @@ public SseItemStatesEventBuilder(final BundleContext bundleContext, final @Refer } else { // if it's not a transformation pattern, then it must be a format string - if (state instanceof QuantityType) { - QuantityType quantityState = (QuantityType) state; + if (state instanceof QuantityType quantityState) { // sanity convert current state to the item state description unit in case it was // updated in the meantime. The item state is still in the "original" unit while the // state description will display the new unit: @@ -141,10 +140,10 @@ public SseItemStatesEventBuilder(final BundleContext bundleContext, final @Refer if (quantityState != null) { state = quantityState; } - } else if (state instanceof DateTimeType) { + } else if (state instanceof DateTimeType type) { // Translate a DateTimeType state to the local time zone try { - state = ((DateTimeType) state).toLocaleZone(); + state = type.toLocaleZone(); } catch (DateTimeException e) { } } diff --git a/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/util/SseUtil.java b/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/util/SseUtil.java index f70f851814b..bbe05c7c59c 100644 --- a/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/util/SseUtil.java +++ b/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/util/SseUtil.java @@ -51,12 +51,11 @@ public static EventDTO buildDTO(final Event event) { * @return a new OutboundEvent */ public static OutboundSseEvent buildEvent(OutboundSseEvent.Builder eventBuilder, EventDTO event) { - final OutboundSseEvent sseEvent = eventBuilder.name("message") // + + return eventBuilder.name("message") // .mediaType(MediaType.APPLICATION_JSON_TYPE) // .data(event) // .build(); - - return sseEvent; } /** diff --git a/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/JSONResponse.java b/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/JSONResponse.java index b9df1cdd69b..d0123394085 100644 --- a/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/JSONResponse.java +++ b/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/JSONResponse.java @@ -210,8 +210,8 @@ public Response toResponse(Exception e) { Response.StatusType status = Response.Status.INTERNAL_SERVER_ERROR; // in case the Exception is a WebApplicationException, it already carries a Status - if (e instanceof WebApplicationException) { - status = ((WebApplicationException) e).getResponse().getStatusInfo(); + if (e instanceof WebApplicationException exception) { + status = exception.getResponse().getStatusInfo(); } JsonElement ret = INSTANCE.createErrorJson(e.getMessage(), status, null, e); diff --git a/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/filter/CorsFilter.java b/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/filter/CorsFilter.java index d5217d38b92..5d6bdf8d149 100644 --- a/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/filter/CorsFilter.java +++ b/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/filter/CorsFilter.java @@ -159,7 +159,7 @@ private boolean processPreflight(ContainerRequestContext requestContext, Contain if (values == null || values.isEmpty()) { return null; } - return values.get(0).toString(); + return values.get(0); } /** diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusBitUtilities.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusBitUtilities.java index 7972773aec8..be2ca28b505 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusBitUtilities.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusBitUtilities.java @@ -229,8 +229,7 @@ public static int extractBit(byte[] bytes, int registerIndex, int bitIndexWithin */ public static byte extractSInt8(byte[] bytes, int registerIndex, boolean hiByte) { int byteIndex = 2 * registerIndex + (hiByte ? 0 : 1); - byte signed = extractSInt8(bytes, byteIndex); - return signed; + return extractSInt8(bytes, byteIndex); } /** @@ -248,8 +247,7 @@ public static byte extractSInt8(byte[] bytes, int registerIndex, boolean hiByte) */ public static byte extractSInt8(byte[] bytes, int index) { assertIndexAndType(bytes, index, ValueType.INT8); - byte signed = bytes[index]; - return signed; + return bytes[index]; } /** @@ -263,8 +261,7 @@ public static byte extractSInt8(byte[] bytes, int index) { */ public static short extractUInt8(byte[] bytes, int registerIndex, boolean hiByte) { int byteIndex = 2 * registerIndex + (hiByte ? 0 : 1); - short unsigned = extractUInt8(bytes, byteIndex); - return unsigned; + return extractUInt8(bytes, byteIndex); } /** @@ -302,8 +299,7 @@ public static short extractSInt16(byte[] bytes, int index) { assertIndexAndType(bytes, index, ValueType.INT16); int hi = (bytes[index] & 0xff); int lo = (bytes[index + 1] & 0xff); - short signed = (short) ((hi << 8) | lo); - return signed; + return (short) ((hi << 8) | lo); } /** @@ -340,8 +336,7 @@ public static int extractSInt32(byte[] bytes, int index) { int lo1 = bytes[index + 1] & 0xff; int hi2 = bytes[index + 2] & 0xff; int lo2 = bytes[index + 3] & 0xff; - int signed = (hi1 << 24) | (lo1 << 16) | (hi2 << 8) | lo2; - return signed; + return (hi1 << 24) | (lo1 << 16) | (hi2 << 8) | lo2; } /** @@ -381,8 +376,7 @@ public static int extractSInt32Swap(byte[] bytes, int index) { int lo1 = bytes[index + 3] & 0xff; int hi2 = bytes[index + 0] & 0xff; int lo2 = bytes[index + 1] & 0xff; - int signed = (hi1 << 24) | (lo1 << 16) | (hi2 << 8) | lo2; - return signed; + return (hi1 << 24) | (lo1 << 16) | (hi2 << 8) | lo2; } /** @@ -633,10 +627,10 @@ public static ModbusRegisterArray commandToRegisters(Command command, ModbusCons if (command instanceof OnOffType || command instanceof OpenClosedType) { numericCommand = translateCommand2Boolean(command).get() ? new DecimalType(BigDecimal.ONE) : DecimalType.ZERO; - } else if (command instanceof DecimalType) { - numericCommand = (DecimalType) command; - } else if (command instanceof QuantityType) { - QuantityType qtCommand = ((QuantityType) command).toUnit(Units.ONE); + } else if (command instanceof DecimalType decimalType) { + numericCommand = decimalType; + } else if (command instanceof QuantityType quantityType) { + QuantityType qtCommand = quantityType.toUnit(Units.ONE); if (qtCommand == null) { throw new IllegalArgumentException( String.format("Command '%s' of class '%s' cannot be converted to bare number.", command, diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusLibraryWrapper.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusLibraryWrapper.java index 7c83e6cc986..b0a9a56cb92 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusLibraryWrapper.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusLibraryWrapper.java @@ -213,12 +213,12 @@ public static ModbusTransaction createTransactionForEndpoint(ModbusSlaveEndpoint // We disable modbus library retries and handle in the Manager implementation transaction.setRetries(0); transaction.setRetryDelayMillis(0); - if (transaction instanceof ModbusSerialTransaction) { - ((ModbusSerialTransaction) transaction).setSerialConnection((SerialConnection) connection); - } else if (transaction instanceof ModbusUDPTransaction) { - ((ModbusUDPTransaction) transaction).setTerminal(((UDPMasterConnection) connection).getTerminal()); - } else if (transaction instanceof ModbusTCPTransaction) { - ((ModbusTCPTransaction) transaction).setConnection((TCPMasterConnection) connection); + if (transaction instanceof ModbusSerialTransaction serialTransaction) { + serialTransaction.setSerialConnection((SerialConnection) connection); + } else if (transaction instanceof ModbusUDPTransaction pTransaction) { + pTransaction.setTerminal(((UDPMasterConnection) connection).getTerminal()); + } else if (transaction instanceof ModbusTCPTransaction pTransaction) { + pTransaction.setConnection((TCPMasterConnection) connection); } else { throw new IllegalStateException(); } diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusManagerImpl.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusManagerImpl.java index b0ef72b900f..8c75042aaee 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusManagerImpl.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusManagerImpl.java @@ -298,39 +298,38 @@ public void accept(AggregateStopWatch timer, WriteTask task, ModbusSlaveConnecti */ private static final long WARN_QUEUE_SIZE = 500; private static final long MONITOR_QUEUE_INTERVAL_MILLIS = 10000; - private static final Function DEFAULT_POOL_CONFIGURATION = endpoint -> { - return endpoint.accept(new ModbusSlaveEndpointVisitor() { - - @Override - public @NonNull EndpointPoolConfiguration visit(ModbusTCPSlaveEndpoint modbusIPSlavePoolingKey) { - EndpointPoolConfiguration endpointPoolConfig = new EndpointPoolConfiguration(); - endpointPoolConfig.setInterTransactionDelayMillis(DEFAULT_TCP_INTER_TRANSACTION_DELAY_MILLIS); - endpointPoolConfig.setConnectMaxTries(Modbus.DEFAULT_RETRIES); - endpointPoolConfig.setConnectTimeoutMillis(DEFAULT_CONNECT_TIMEOUT_MILLIS); - return endpointPoolConfig; - } + private static final Function DEFAULT_POOL_CONFIGURATION = endpoint -> endpoint + .accept(new ModbusSlaveEndpointVisitor() { + + @Override + public @NonNull EndpointPoolConfiguration visit(ModbusTCPSlaveEndpoint modbusIPSlavePoolingKey) { + EndpointPoolConfiguration endpointPoolConfig = new EndpointPoolConfiguration(); + endpointPoolConfig.setInterTransactionDelayMillis(DEFAULT_TCP_INTER_TRANSACTION_DELAY_MILLIS); + endpointPoolConfig.setConnectMaxTries(Modbus.DEFAULT_RETRIES); + endpointPoolConfig.setConnectTimeoutMillis(DEFAULT_CONNECT_TIMEOUT_MILLIS); + return endpointPoolConfig; + } - @Override - public @NonNull EndpointPoolConfiguration visit(ModbusSerialSlaveEndpoint modbusSerialSlavePoolingKey) { - EndpointPoolConfiguration endpointPoolConfig = new EndpointPoolConfiguration(); - // never "disconnect" (close/open serial port) serial connection between borrows - endpointPoolConfig.setReconnectAfterMillis(-1); - endpointPoolConfig.setInterTransactionDelayMillis(DEFAULT_SERIAL_INTER_TRANSACTION_DELAY_MILLIS); - endpointPoolConfig.setConnectMaxTries(Modbus.DEFAULT_RETRIES); - endpointPoolConfig.setConnectTimeoutMillis(DEFAULT_CONNECT_TIMEOUT_MILLIS); - return endpointPoolConfig; - } + @Override + public @NonNull EndpointPoolConfiguration visit(ModbusSerialSlaveEndpoint modbusSerialSlavePoolingKey) { + EndpointPoolConfiguration endpointPoolConfig = new EndpointPoolConfiguration(); + // never "disconnect" (close/open serial port) serial connection between borrows + endpointPoolConfig.setReconnectAfterMillis(-1); + endpointPoolConfig.setInterTransactionDelayMillis(DEFAULT_SERIAL_INTER_TRANSACTION_DELAY_MILLIS); + endpointPoolConfig.setConnectMaxTries(Modbus.DEFAULT_RETRIES); + endpointPoolConfig.setConnectTimeoutMillis(DEFAULT_CONNECT_TIMEOUT_MILLIS); + return endpointPoolConfig; + } - @Override - public @NonNull EndpointPoolConfiguration visit(ModbusUDPSlaveEndpoint modbusUDPSlavePoolingKey) { - EndpointPoolConfiguration endpointPoolConfig = new EndpointPoolConfiguration(); - endpointPoolConfig.setInterTransactionDelayMillis(DEFAULT_TCP_INTER_TRANSACTION_DELAY_MILLIS); - endpointPoolConfig.setConnectMaxTries(Modbus.DEFAULT_RETRIES); - endpointPoolConfig.setConnectTimeoutMillis(DEFAULT_CONNECT_TIMEOUT_MILLIS); - return endpointPoolConfig; - } - }); - }; + @Override + public @NonNull EndpointPoolConfiguration visit(ModbusUDPSlaveEndpoint modbusUDPSlavePoolingKey) { + EndpointPoolConfiguration endpointPoolConfig = new EndpointPoolConfiguration(); + endpointPoolConfig.setInterTransactionDelayMillis(DEFAULT_TCP_INTER_TRANSACTION_DELAY_MILLIS); + endpointPoolConfig.setConnectMaxTries(Modbus.DEFAULT_RETRIES); + endpointPoolConfig.setConnectTimeoutMillis(DEFAULT_CONNECT_TIMEOUT_MILLIS); + return endpointPoolConfig; + } + }); private final PollOperation pollOperation = new PollOperation(); private final WriteOperation writeOperation = new WriteOperation(); @@ -476,7 +475,7 @@ private , timer.connection.timeRunnable(() -> invalidate(endpoint, connection)); return Optional.empty(); } - if (!connection.isPresent()) { + if (connection.isEmpty()) { logger.warn("Could not connect to endpoint {} -- aborting request {} [operation ID {}]", endpoint, request, operationId); timer.callback.timeRunnable( @@ -562,7 +561,7 @@ private , connection = getConnection(timer, oneOffTask, task); logger.trace("Operation with task {}. Got a connection {} [operation ID {}]", task, connection.isPresent() ? "successfully" : "which was unconnected (connection issue)", operationId); - if (!connection.isPresent()) { + if (connection.isEmpty()) { // Could not acquire connection, time to abort // Error logged already, error callback called as well logger.trace("Initial connection was not successful, aborting. [operation ID {}]", operationId); @@ -585,7 +584,7 @@ private , Long lastTryMillis = null; while (tryIndex < maxTries) { logger.trace("Try {} out of {} [operation ID {}]", tryIndex + 1, maxTries, operationId); - if (!connection.isPresent()) { + if (connection.isEmpty()) { // Connection was likely reseted with previous try, and connection was not successfully // re-established. Error has been logged, time to abort. logger.trace("Try {} out of {}. Connection was not successful, aborting. [operation ID {}]", @@ -597,8 +596,8 @@ private , return; } // Check poll task is still registered (this is all asynchronous) - if (!oneOffTask && task instanceof PollTask) { - verifyTaskIsRegistered((PollTask) task); + if (!oneOffTask && task instanceof PollTask pollTask) { + verifyTaskIsRegistered(pollTask); } // Let's ensure that enough time is between the retries logger.trace( @@ -716,7 +715,7 @@ private , lastTryMillis = System.currentTimeMillis(); // Connection was reseted in error handling and needs to be reconnected. // Try to re-establish connection. - if (willRetry && !connection.isPresent()) { + if (willRetry && connection.isEmpty()) { connection = getConnection(timer, oneOffTask, task); } } @@ -778,13 +777,12 @@ public Future submitOneTimePoll(ModbusReadRequestBlueprint request, ModbusRea long scheduleTime = System.currentTimeMillis(); BasicPollTask task = new BasicPollTask(endpoint, request, resultCallback, failureCallback); logger.debug("Scheduling one-off poll task {}", task); - Future future = executor.submit(() -> { + return executor.submit(() -> { long millisInThreadPoolWaiting = System.currentTimeMillis() - scheduleTime; logger.debug("Will now execute one-off poll task {}, waited in thread pool for {}", task, millisInThreadPoolWaiting); executeOperation(task, true, pollOperation); }); - return future; } @Override @@ -871,13 +869,12 @@ public Future submitOneTimeWrite(ModbusWriteRequestBlueprint request, ModbusW WriteTask task = new BasicWriteTask(endpoint, request, resultCallback, failureCallback); long scheduleTime = System.currentTimeMillis(); logger.debug("Scheduling one-off write task {}", task); - Future future = localScheduledThreadPoolExecutor.submit(() -> { + return localScheduledThreadPoolExecutor.submit(() -> { long millisInThreadPoolWaiting = System.currentTimeMillis() - scheduleTime; logger.debug("Will now execute one-off write task {}, waited in thread pool for {}", task, millisInThreadPoolWaiting); executeOperation(task, true, writeOperation); }); - return future; } @Override @@ -1028,8 +1025,7 @@ private void logTaskQueueInfo() { task.getRequest().getDataLength(), future.isDone(), future.isCancelled(), future.getDelay(TimeUnit.MILLISECONDS), task); }); - if (scheduledThreadPoolExecutor instanceof ThreadPoolExecutor) { - ThreadPoolExecutor executor = ((ThreadPoolExecutor) scheduledThreadPoolExecutor); + if (scheduledThreadPoolExecutor instanceof ThreadPoolExecutor executor) { pollMonitorLogger.trace( "POLL MONITOR: scheduledThreadPoolExecutor queue size: {}, remaining space {}. Active threads {}", executor.getQueue().size(), executor.getQueue().remainingCapacity(), executor.getActiveCount()); diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusSlaveIOExceptionImpl.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusSlaveIOExceptionImpl.java index 1a81b421de0..b1e9a8c7e08 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusSlaveIOExceptionImpl.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/internal/ModbusSlaveIOExceptionImpl.java @@ -43,16 +43,14 @@ public ModbusSlaveIOExceptionImpl(IOException e) { @Override public @Nullable String getMessage() { return String.format("Modbus IO Error with cause=%s, EOF=%s, message='%s', cause2=%s", - error.getClass().getSimpleName(), - error instanceof ModbusIOException ? ((ModbusIOException) error).isEOF() : "?", error.getMessage(), - error.getCause()); + error.getClass().getSimpleName(), error instanceof ModbusIOException mioe ? mioe.isEOF() : "?", + error.getMessage(), error.getCause()); } @Override public String toString() { return String.format("ModbusSlaveIOException(cause=%s, EOF=%s, message='%s', cause2=%s)", - error.getClass().getSimpleName(), - error instanceof ModbusIOException ? ((ModbusIOException) error).isEOF() : "?", error.getMessage(), - error.getCause()); + error.getClass().getSimpleName(), error instanceof ModbusIOException mioe ? mioe.isEOF() : "?", + error.getMessage(), error.getCause()); } } diff --git a/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/BitUtilitiesCommandToRegistersTest.java b/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/BitUtilitiesCommandToRegistersTest.java index b9c131404c3..00d99baa6c0 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/BitUtilitiesCommandToRegistersTest.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/BitUtilitiesCommandToRegistersTest.java @@ -75,11 +75,11 @@ private static class Args implements Arguments { private Object expected; public Args(Object command, ValueType valueType, Object expected) { - this.command = command instanceof String ? new DecimalType((String) command) : (Command) command; + this.command = command instanceof String s ? new DecimalType(s) : (Command) command; this.valueType = valueType; // validating type with cast - if (expected instanceof Integer) { - this.expected = shorts((int) expected); + if (expected instanceof Integer integer) { + this.expected = shorts(integer); } else if (expected instanceof Class) { this.expected = expected; } else { @@ -118,8 +118,8 @@ private static Stream concatTestArgs(Object... objects) { for (Object obj : objects) { if (obj instanceof Args) { builder.add(obj); - } else if (obj instanceof Stream) { - ((Stream) obj).forEach(builder::add); + } else if (obj instanceof Stream stream) { + stream.forEach(builder::add); } else { throw new IllegalArgumentException("Illegal parameter " + obj.toString()); } @@ -326,8 +326,8 @@ public static Stream data() { @ParameterizedTest @MethodSource("data") public void testCommandToRegisters(Command command, ValueType type, Object expectedResult) { - if (expectedResult instanceof Class && Exception.class.isAssignableFrom((Class) expectedResult)) { - assertThrows((Class) expectedResult, () -> ModbusBitUtilities.commandToRegisters(command, type)); + if (expectedResult instanceof Class class1 && Exception.class.isAssignableFrom(class1)) { + assertThrows(class1, () -> ModbusBitUtilities.commandToRegisters(command, type)); return; } diff --git a/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/BitUtilitiesExtractIndividualMethodsTest.java b/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/BitUtilitiesExtractIndividualMethodsTest.java index 3f460711d33..710294134e0 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/BitUtilitiesExtractIndividualMethodsTest.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/BitUtilitiesExtractIndividualMethodsTest.java @@ -105,10 +105,10 @@ private void testIndividual(Object expectedResult, ValueType type, byte[] bytes, String testExplanation = String.format("bytes=%s, byteIndex=%d, type=%s", Arrays.toString(bytes), byteIndex, type); final Object expectedNumber; - if (expectedResult instanceof Class && Exception.class.isAssignableFrom((Class) expectedResult)) { + if (expectedResult instanceof Class class1 && Exception.class.isAssignableFrom(class1)) { assertThrows((Class) expectedResult, () -> methodUnderTest.get()); - } else if (expectedResult instanceof Optional) { - assertTrue(!((Optional) expectedResult).isPresent()); + } else if (expectedResult instanceof Optional optional) { + assertTrue(optional.isEmpty()); if (defaultWhenEmptyOptional == null) { fail("Should provide defaultWhenEmptyOptional"); } diff --git a/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/BitUtilitiesExtractStateFromRegistersTest.java b/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/BitUtilitiesExtractStateFromRegistersTest.java index 156e3f8c87c..ecfd9cdee1d 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/BitUtilitiesExtractStateFromRegistersTest.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/BitUtilitiesExtractStateFromRegistersTest.java @@ -359,16 +359,14 @@ ValueType.INT64_SWAP, shortArrayToRegisterArray(0x0, 0x0, 0x8, 0x0), 0 }, @MethodSource("data") public void testextractStateFromRegisters(Object expectedResult, ValueType type, ModbusRegisterArray registers, int index) { - if (expectedResult instanceof Class && Exception.class.isAssignableFrom((Class) expectedResult)) { - assertThrows((Class) expectedResult, - () -> ModbusBitUtilities.extractStateFromRegisters(registers, index, type)); + if (expectedResult instanceof Class class1 && Exception.class.isAssignableFrom(class1)) { + assertThrows(class1, () -> ModbusBitUtilities.extractStateFromRegisters(registers, index, type)); return; } Optional actualState = ModbusBitUtilities.extractStateFromRegisters(registers, index, type); // Wrap given expectedResult to Optional, if necessary - Optional expectedStateWrapped = expectedResult instanceof DecimalType - ? Optional.of((DecimalType) expectedResult) + Optional expectedStateWrapped = expectedResult instanceof DecimalType dt ? Optional.of(dt) : (Optional) expectedResult; assertThat(String.format("registers=%s, index=%d, type=%s", registers, index, type), actualState, is(equalTo(expectedStateWrapped))); diff --git a/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/BitUtilitiesExtractStringTest.java b/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/BitUtilitiesExtractStringTest.java index 3156083fd0e..e4e14729211 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/BitUtilitiesExtractStringTest.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/BitUtilitiesExtractStringTest.java @@ -99,8 +99,7 @@ public static Stream dataWithByteVariations() { streamBuilder.add( new Object[] { expected, offset, bytesOffsetted, origByteIndex + offset, length, charset }); } - Stream variations = streamBuilder.build(); - return variations; + return streamBuilder.build(); }); } @@ -109,8 +108,8 @@ public static Stream dataWithByteVariations() { @MethodSource("data") public void testExtractStringFromRegisters(Object expectedResult, ModbusRegisterArray registers, int index, int length, Charset charset) { - if (expectedResult instanceof Class && Exception.class.isAssignableFrom((Class) expectedResult)) { - assertThrows((Class) expectedResult, + if (expectedResult instanceof Class class1 && Exception.class.isAssignableFrom(class1)) { + assertThrows(class1, () -> ModbusBitUtilities.extractStringFromRegisters(registers, index, length, charset)); return; } else { @@ -125,9 +124,8 @@ public void testExtractStringFromRegisters(Object expectedResult, ModbusRegister @MethodSource("dataWithByteVariations") public void testExtractStringFromBytes(Object expectedResult, int byteOffset, byte[] bytes, int byteIndex, int length, Charset charset) { - if (expectedResult instanceof Class && Exception.class.isAssignableFrom((Class) expectedResult)) { - assertThrows((Class) expectedResult, - () -> ModbusBitUtilities.extractStringFromBytes(bytes, byteIndex, length, charset)); + if (expectedResult instanceof Class class1 && Exception.class.isAssignableFrom(class1)) { + assertThrows(class1, () -> ModbusBitUtilities.extractStringFromBytes(bytes, byteIndex, length, charset)); return; } else { String actualState = ModbusBitUtilities.extractStringFromBytes(bytes, byteIndex, length, charset); diff --git a/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/IntegrationTestSupport.java b/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/IntegrationTestSupport.java index dc3943a98df..0ec22cf398d 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/IntegrationTestSupport.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/test/java/org/openhab/core/io/transport/modbus/test/IntegrationTestSupport.java @@ -328,7 +328,7 @@ public UDPSlaveTerminal create(@NonNullByDefault({}) InetAddress interfac, int p public class SerialConnectionFactoryImpl implements SerialConnectionFactory { @Override public SerialConnection create(@NonNullByDefault({}) SerialParameters parameters) { - SerialConnection serialConnection = new SerialConnection(parameters) { + return new SerialConnection(parameters) { @Override public ModbusTransport getModbusTransport() { ModbusTransport transport = spy(super.getModbusTransport()); @@ -336,7 +336,6 @@ public ModbusTransport getModbusTransport() { return transport; } }; - return serialConnection; } } diff --git a/bundles/org.openhab.core.io.transport.mqtt/src/test/java/org/openhab/core/io/transport/mqtt/MqttBrokerConnectionEx.java b/bundles/org.openhab.core.io.transport.mqtt/src/test/java/org/openhab/core/io/transport/mqtt/MqttBrokerConnectionEx.java index 3cb0c461560..e5b612ca6be 100644 --- a/bundles/org.openhab.core.io.transport.mqtt/src/test/java/org/openhab/core/io/transport/mqtt/MqttBrokerConnectionEx.java +++ b/bundles/org.openhab.core.io.transport.mqtt/src/test/java/org/openhab/core/io/transport/mqtt/MqttBrokerConnectionEx.java @@ -99,9 +99,7 @@ protected MqttAsyncClientWrapper createClient() { } }).when(mockedClient).unsubscribe(any()); // state - doAnswer(i -> { - return MqttClientState.CONNECTED; - }).when(mockedClient).getState(); + doAnswer(i -> MqttClientState.CONNECTED).when(mockedClient).getState(); return mockedClient; } diff --git a/bundles/org.openhab.core.io.transport.serial.javacomm/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortIdentifierImpl.java b/bundles/org.openhab.core.io.transport.serial.javacomm/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortIdentifierImpl.java index fc0ef548548..fe49877dfd7 100644 --- a/bundles/org.openhab.core.io.transport.serial.javacomm/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortIdentifierImpl.java +++ b/bundles/org.openhab.core.io.transport.serial.javacomm/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortIdentifierImpl.java @@ -50,8 +50,8 @@ public String getName() { public SerialPort open(String owner, int timeout) throws PortInUseException { try { final CommPort cp = id.open(owner, timeout); - if (cp instanceof javax.comm.SerialPort) { - return new SerialPortImpl((javax.comm.SerialPort) cp); + if (cp instanceof javax.comm.SerialPort port) { + return new SerialPortImpl(port); } else { throw new IllegalStateException( String.format("We expect a serial port instead of '%s'", cp.getClass())); diff --git a/bundles/org.openhab.core.io.transport.serial.rxtx/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortIdentifierImpl.java b/bundles/org.openhab.core.io.transport.serial.rxtx/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortIdentifierImpl.java index b41d63e5903..f0d34267171 100644 --- a/bundles/org.openhab.core.io.transport.serial.rxtx/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortIdentifierImpl.java +++ b/bundles/org.openhab.core.io.transport.serial.rxtx/src/main/java/org/openhab/core/io/transport/serial/internal/SerialPortIdentifierImpl.java @@ -51,8 +51,8 @@ public String getName() { public SerialPort open(String owner, int timeout) throws PortInUseException { try { final CommPort cp = id.open(owner, timeout); - if (cp instanceof gnu.io.SerialPort) { - return new RxTxSerialPort((gnu.io.SerialPort) cp); + if (cp instanceof gnu.io.SerialPort port) { + return new RxTxSerialPort(port); } else { throw new IllegalStateException( String.format("We expect a serial port instead of '%s'", cp.getClass())); diff --git a/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/ItemEventUtility.java b/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/ItemEventUtility.java index 89ea5cd8464..e8f1d2b9ec9 100644 --- a/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/ItemEventUtility.java +++ b/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/ItemEventUtility.java @@ -56,10 +56,10 @@ public Event createCommandEvent(EventDTO eventDTO) throws EventProcessingExcepti Matcher matcher = getTopicMatcher(eventDTO.topic, "command"); Item item = getItem(matcher.group("entity")); Type command = parseType(eventDTO.payload); - if (command instanceof Command) { + if (command instanceof Command command1) { List> acceptedItemCommandTypes = item.getAcceptedCommandTypes(); if (acceptedItemCommandTypes.contains(command.getClass())) { - return ItemEventFactory.createCommandEvent(item.getName(), (Command) command, eventDTO.source); + return ItemEventFactory.createCommandEvent(item.getName(), command1, eventDTO.source); } } throw new EventProcessingException("Incompatible datatype, rejected."); @@ -69,10 +69,10 @@ public Event createStateEvent(EventDTO eventDTO) throws EventProcessingException Matcher matcher = getTopicMatcher(eventDTO.topic, "state"); Item item = getItem(matcher.group("entity")); Type state = parseType(eventDTO.payload); - if (state instanceof State) { + if (state instanceof State state1) { List> acceptedItemStateTypes = item.getAcceptedDataTypes(); if (acceptedItemStateTypes.contains(state.getClass())) { - return ItemEventFactory.createStateEvent(item.getName(), (State) state, eventDTO.source); + return ItemEventFactory.createStateEvent(item.getName(), state1, eventDTO.source); } } throw new EventProcessingException("Incompatible datatype, rejected."); diff --git a/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/FeatureInstaller.java b/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/FeatureInstaller.java index 00eaacfd788..d3dd50ba760 100644 --- a/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/FeatureInstaller.java +++ b/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/FeatureInstaller.java @@ -322,8 +322,8 @@ private boolean getOnlineRepositoryMode() { return false; } Object repos = properties.get(PROPERTY_MVN_REPOS); - if (repos instanceof String) { - return List.of(((String) repos).split(",")).contains(onlineRepoUrl); + if (repos instanceof String string) { + return List.of(string.split(",")).contains(onlineRepoUrl); } } catch (IOException e) { logger.error("Failed getting the add-on management online/offline mode: {}", e.getMessage(), @@ -350,8 +350,8 @@ private boolean setOnlineRepositoryMode(boolean enabled) { new Hashtable<>()); List repoCfg = new ArrayList<>(); Object repos = properties.get(PROPERTY_MVN_REPOS); - if (repos instanceof String) { - repoCfg.addAll(Arrays.asList(((String) repos).split(","))); + if (repos instanceof String string) { + repoCfg.addAll(Arrays.asList(string.split(","))); repoCfg.remove(""); } if (enabled && !repoCfg.contains(onlineRepoUrl)) { diff --git a/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/jaas/ManagedUserBackingEngine.java b/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/jaas/ManagedUserBackingEngine.java index 00224ebc3eb..7e774814db9 100644 --- a/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/jaas/ManagedUserBackingEngine.java +++ b/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/jaas/ManagedUserBackingEngine.java @@ -103,8 +103,7 @@ public List listRoles(Principal principal) { @Override public void addRole(String username, String role) { User user = userRegistry.get(username); - if (user instanceof ManagedUser) { - ManagedUser managedUser = (ManagedUser) user; + if (user instanceof ManagedUser managedUser) { managedUser.getRoles().add(role); userRegistry.update(managedUser); } @@ -113,8 +112,7 @@ public void addRole(String username, String role) { @Override public void deleteRole(String username, String role) { User user = userRegistry.get(username); - if (user instanceof ManagedUser) { - ManagedUser managedUser = (ManagedUser) user; + if (user instanceof ManagedUser managedUser) { managedUser.getRoles().remove(role); userRegistry.update(managedUser); } diff --git a/bundles/org.openhab.core.model.core/src/main/java/org/openhab/core/model/core/internal/ModelRepositoryImpl.java b/bundles/org.openhab.core.model.core/src/main/java/org/openhab/core/model/core/internal/ModelRepositoryImpl.java index 112d9ee405b..4b016f4d312 100644 --- a/bundles/org.openhab.core.model.core/src/main/java/org/openhab/core/model/core/internal/ModelRepositoryImpl.java +++ b/bundles/org.openhab.core.model.core/src/main/java/org/openhab/core/model/core/internal/ModelRepositoryImpl.java @@ -170,12 +170,10 @@ public Iterable getAllModelNamesOfType(final String modelType) { // Make a copy to avoid ConcurrentModificationException List resourceListCopy = new ArrayList<>(resourceSet.getResources()); - return resourceListCopy.stream().filter(input -> { - return input.getURI().lastSegment().contains(".") && input.isLoaded() - && modelType.equalsIgnoreCase(input.getURI().fileExtension()); - }).map(from -> { - return from.getURI().path(); - }).collect(Collectors.toList()); + return resourceListCopy.stream() + .filter(input -> input.getURI().lastSegment().contains(".") && input.isLoaded() + && modelType.equalsIgnoreCase(input.getURI().fileExtension())) + .map(from -> from.getURI().path()).collect(Collectors.toList()); } } diff --git a/bundles/org.openhab.core.model.core/src/main/java/org/openhab/core/model/core/valueconverter/ValueTypeToStringConverter.java b/bundles/org.openhab.core.model.core/src/main/java/org/openhab/core/model/core/valueconverter/ValueTypeToStringConverter.java index 6f7ce5b7ec6..ba2cb13c6ce 100644 --- a/bundles/org.openhab.core.model.core/src/main/java/org/openhab/core/model/core/valueconverter/ValueTypeToStringConverter.java +++ b/bundles/org.openhab.core.model.core/src/main/java/org/openhab/core/model/core/valueconverter/ValueTypeToStringConverter.java @@ -57,15 +57,14 @@ public String toString(@Nullable Object value) throws ValueConverterException { if (value == null) { throw new ValueConverterException("Value may not be null.", null, null); } - if (value instanceof String) { - return toEscapedString((String) value); + if (value instanceof String string) { + return toEscapedString(string); } - if (value instanceof BigDecimal) { - BigDecimal decimalValue = (BigDecimal) value; + if (value instanceof BigDecimal decimalValue) { return decimalValue.toPlainString(); } - if (value instanceof Boolean) { - return ((Boolean) value).toString(); + if (value instanceof Boolean boolean1) { + return boolean1.toString(); } throw new ValueConverterException("Unknown value type: " + value.getClass().getSimpleName(), null, null); } diff --git a/bundles/org.openhab.core.model.item/src/org/openhab/core/model/item/internal/GenericItemProvider.java b/bundles/org.openhab.core.model.item/src/org/openhab/core/model/item/internal/GenericItemProvider.java index 024ed4ead90..3e39efa4e15 100644 --- a/bundles/org.openhab.core.model.item/src/org/openhab/core/model/item/internal/GenericItemProvider.java +++ b/bundles/org.openhab.core.model.item/src/org/openhab/core/model/item/internal/GenericItemProvider.java @@ -223,8 +223,7 @@ private void processBindingConfigsFromModel(String modelName, EventType type) { private @Nullable Item createItemFromModelItem(ModelItem modelItem) { Item item; - if (modelItem instanceof ModelGroupItem) { - ModelGroupItem modelGroupItem = (ModelGroupItem) modelItem; + if (modelItem instanceof ModelGroupItem modelGroupItem) { Item baseItem; try { baseItem = createItemOfType(modelGroupItem.getType(), modelGroupItem.getName()); diff --git a/bundles/org.openhab.core.model.lazygen/src/main/java/org/openhab/core/model/lazygen/LazyStandaloneSetup.java b/bundles/org.openhab.core.model.lazygen/src/main/java/org/openhab/core/model/lazygen/LazyStandaloneSetup.java index c8bd91787ef..5052a4b5bff 100644 --- a/bundles/org.openhab.core.model.lazygen/src/main/java/org/openhab/core/model/lazygen/LazyStandaloneSetup.java +++ b/bundles/org.openhab.core.model.lazygen/src/main/java/org/openhab/core/model/lazygen/LazyStandaloneSetup.java @@ -146,8 +146,7 @@ private URI createURI(String path) { URI uri = URI.createURI(path); if (uri.isRelative()) { - URI resolvedURI = URI.createFileURI(new File(path).getAbsolutePath()); - return resolvedURI; + return URI.createFileURI(new File(path).getAbsolutePath()); } return uri; } diff --git a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/internal/PersistenceModelManager.java b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/internal/PersistenceModelManager.java index 4f45501276a..626da1d702c 100644 --- a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/internal/PersistenceModelManager.java +++ b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/internal/PersistenceModelManager.java @@ -133,10 +133,10 @@ private PersistenceItemConfiguration mapConfig(PersistenceConfiguration config) for (final EObject item : config.getItems()) { if (item instanceof AllConfig) { items.add(new PersistenceAllConfig()); - } else if (item instanceof GroupConfig) { - items.add(new PersistenceGroupConfig(((GroupConfig) item).getGroup())); - } else if (item instanceof ItemConfig) { - items.add(new PersistenceItemConfig(((ItemConfig) item).getItem())); + } else if (item instanceof GroupConfig groupConfig) { + items.add(new PersistenceGroupConfig(groupConfig.getGroup())); + } else if (item instanceof ItemConfig itemConfig) { + items.add(new PersistenceItemConfig(itemConfig.getItem())); } } return new PersistenceItemConfiguration(items, config.getAlias(), mapStrategies(config.getStrategies()), @@ -152,8 +152,8 @@ private List mapStrategies(List strategies) { } private PersistenceStrategy mapStrategy(Strategy strategy) { - if (strategy instanceof CronStrategy) { - return new PersistenceCronStrategy(strategy.getName(), ((CronStrategy) strategy).getCronExpression()); + if (strategy instanceof CronStrategy cronStrategy) { + return new PersistenceCronStrategy(strategy.getName(), cronStrategy.getCronExpression()); } else { return new PersistenceStrategy(strategy.getName()); } diff --git a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/scoping/GlobalStrategies.java b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/scoping/GlobalStrategies.java index a17be5af765..5d4e901edcf 100644 --- a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/scoping/GlobalStrategies.java +++ b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/scoping/GlobalStrategies.java @@ -23,21 +23,21 @@ */ public class GlobalStrategies { - static final public Strategy UPDATE = new StrategyImpl() { + public static final Strategy UPDATE = new StrategyImpl() { @Override public String getName() { return "everyUpdate"; }; }; - static final public Strategy CHANGE = new StrategyImpl() { + public static final Strategy CHANGE = new StrategyImpl() { @Override public String getName() { return "everyChange"; }; }; - static final public Strategy RESTORE = new StrategyImpl() { + public static final Strategy RESTORE = new StrategyImpl() { @Override public String getName() { return "restoreOnStartup"; diff --git a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/scoping/PersistenceGlobalScopeProvider.java b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/scoping/PersistenceGlobalScopeProvider.java index cbdfbf37b32..746d932a13e 100644 --- a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/scoping/PersistenceGlobalScopeProvider.java +++ b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/scoping/PersistenceGlobalScopeProvider.java @@ -31,7 +31,7 @@ public class PersistenceGlobalScopeProvider extends AbstractGlobalScopeProvider { - static protected Resource res = new ResourceImpl(); + protected static Resource res = new ResourceImpl(); static { res.setURI(URI.createURI("virtual://openhab.org/persistence/strategy.global")); @@ -46,8 +46,7 @@ protected IScope getScope(Resource resource, boolean ignoreCase, EClass type, IScope parentScope = super.getScope(resource, ignoreCase, type, predicate); List descs = new ArrayList<>(); for (EObject eObj : res.getContents()) { - if (eObj instanceof Strategy) { - Strategy strategy = (Strategy) eObj; + if (eObj instanceof Strategy strategy) { descs.add(EObjectDescription.create(strategy.getName(), strategy)); } } diff --git a/bundles/org.openhab.core.model.rule.runtime/src/org/openhab/core/model/rule/runtime/internal/DSLRuleProvider.java b/bundles/org.openhab.core.model.rule.runtime/src/org/openhab/core/model/rule/runtime/internal/DSLRuleProvider.java index a1ea1663852..c96a0ca5282 100644 --- a/bundles/org.openhab.core.model.rule.runtime/src/org/openhab/core/model/rule/runtime/internal/DSLRuleProvider.java +++ b/bundles/org.openhab.core.model.rule.runtime/src/org/openhab/core/model/rule/runtime/internal/DSLRuleProvider.java @@ -138,8 +138,7 @@ public void modelChanged(String modelFileName, EventType type) { switch (type) { case ADDED: EObject model = modelRepository.getModel(modelFileName); - if (model instanceof RuleModel) { - RuleModel ruleModel = (RuleModel) model; + if (model instanceof RuleModel ruleModel) { int index = 1; for (org.openhab.core.model.rule.rules.Rule rule : ruleModel.getRules()) { addRule(toRule(ruleModelName, rule, index)); @@ -152,8 +151,7 @@ public void modelChanged(String modelFileName, EventType type) { case MODIFIED: removeRuleModel(ruleModelName); EObject modifiedModel = modelRepository.getModel(modelFileName); - if (modifiedModel instanceof RuleModel) { - RuleModel ruleModel = (RuleModel) modifiedModel; + if (modifiedModel instanceof RuleModel ruleModel) { int index = 1; for (org.openhab.core.model.rule.rules.Rule rule : ruleModel.getRules()) { Rule newRule = toRule(ruleModelName, rule, index); @@ -180,8 +178,8 @@ public void modelChanged(String modelFileName, EventType type) { } case ADDED: EObject model = modelRepository.getModel(modelFileName); - if (model instanceof Script) { - addRule(toRule(modelFileName, ((Script) model))); + if (model instanceof Script script) { + addRule(toRule(modelFileName, script)); } break; case REMOVED: @@ -330,8 +328,7 @@ private String removeIndentation(String script) { cfg.put("startlevel", 20); return TriggerBuilder.create().withId(Integer.toString(triggerId++)) .withTypeUID("core.SystemStartlevelTrigger").withConfiguration(cfg).build(); - } else if (t instanceof SystemStartlevelTrigger) { - SystemStartlevelTrigger slTrigger = (SystemStartlevelTrigger) t; + } else if (t instanceof SystemStartlevelTrigger slTrigger) { Configuration cfg = new Configuration(); cfg.put("startlevel", slTrigger.getLevel()); return TriggerBuilder.create().withId(Integer.toString(triggerId++)) @@ -339,8 +336,7 @@ private String removeIndentation(String script) { } else if (t instanceof SystemOnShutdownTrigger) { logger.warn("System shutdown rule triggers are no longer supported!"); return null; - } else if (t instanceof CommandEventTrigger) { - CommandEventTrigger ceTrigger = (CommandEventTrigger) t; + } else if (t instanceof CommandEventTrigger ceTrigger) { Configuration cfg = new Configuration(); cfg.put("itemName", ceTrigger.getItem()); if (ceTrigger.getCommand() != null) { @@ -348,8 +344,7 @@ private String removeIndentation(String script) { } return TriggerBuilder.create().withId(Integer.toString(triggerId++)).withTypeUID("core.ItemCommandTrigger") .withConfiguration(cfg).build(); - } else if (t instanceof GroupMemberCommandEventTrigger) { - GroupMemberCommandEventTrigger ceTrigger = (GroupMemberCommandEventTrigger) t; + } else if (t instanceof GroupMemberCommandEventTrigger ceTrigger) { Configuration cfg = new Configuration(); cfg.put("groupName", ceTrigger.getGroup()); if (ceTrigger.getCommand() != null) { @@ -357,8 +352,7 @@ private String removeIndentation(String script) { } return TriggerBuilder.create().withId(Integer.toString(triggerId++)).withTypeUID("core.GroupCommandTrigger") .withConfiguration(cfg).build(); - } else if (t instanceof UpdateEventTrigger) { - UpdateEventTrigger ueTrigger = (UpdateEventTrigger) t; + } else if (t instanceof UpdateEventTrigger ueTrigger) { Configuration cfg = new Configuration(); cfg.put("itemName", ueTrigger.getItem()); if (ueTrigger.getState() != null) { @@ -366,8 +360,7 @@ private String removeIndentation(String script) { } return TriggerBuilder.create().withId(Integer.toString(triggerId++)) .withTypeUID("core.ItemStateUpdateTrigger").withConfiguration(cfg).build(); - } else if (t instanceof GroupMemberUpdateEventTrigger) { - GroupMemberUpdateEventTrigger ueTrigger = (GroupMemberUpdateEventTrigger) t; + } else if (t instanceof GroupMemberUpdateEventTrigger ueTrigger) { Configuration cfg = new Configuration(); cfg.put("groupName", ueTrigger.getGroup()); if (ueTrigger.getState() != null) { @@ -375,8 +368,7 @@ private String removeIndentation(String script) { } return TriggerBuilder.create().withId(Integer.toString(triggerId++)) .withTypeUID("core.GroupStateUpdateTrigger").withConfiguration(cfg).build(); - } else if (t instanceof ChangedEventTrigger) { - ChangedEventTrigger ceTrigger = (ChangedEventTrigger) t; + } else if (t instanceof ChangedEventTrigger ceTrigger) { Configuration cfg = new Configuration(); cfg.put("itemName", ceTrigger.getItem()); if (ceTrigger.getNewState() != null) { @@ -387,8 +379,7 @@ private String removeIndentation(String script) { } return TriggerBuilder.create().withId(Integer.toString(triggerId++)) .withTypeUID("core.ItemStateChangeTrigger").withConfiguration(cfg).build(); - } else if (t instanceof GroupMemberChangedEventTrigger) { - GroupMemberChangedEventTrigger ceTrigger = (GroupMemberChangedEventTrigger) t; + } else if (t instanceof GroupMemberChangedEventTrigger ceTrigger) { Configuration cfg = new Configuration(); cfg.put("groupName", ceTrigger.getGroup()); if (ceTrigger.getNewState() != null) { @@ -399,8 +390,7 @@ private String removeIndentation(String script) { } return TriggerBuilder.create().withId(Integer.toString(triggerId++)) .withTypeUID("core.GroupStateChangeTrigger").withConfiguration(cfg).build(); - } else if (t instanceof TimerTrigger) { - TimerTrigger tt = (TimerTrigger) t; + } else if (t instanceof TimerTrigger tt) { Configuration cfg = new Configuration(); String id; if (tt.getCron() != null) { @@ -419,15 +409,13 @@ private String removeIndentation(String script) { } return TriggerBuilder.create().withId(Integer.toString(triggerId++)).withTypeUID("timer.GenericCronTrigger") .withConfiguration(cfg).build(); - } else if (t instanceof DateTimeTrigger) { - DateTimeTrigger tt = (DateTimeTrigger) t; + } else if (t instanceof DateTimeTrigger tt) { Configuration cfg = new Configuration(); cfg.put("itemName", tt.getItem()); cfg.put("timeOnly", tt.isTimeOnly()); return TriggerBuilder.create().withId(Integer.toString((triggerId++))).withTypeUID("timer.DateTimeTrigger") .withConfiguration(cfg).build(); - } else if (t instanceof EventEmittedTrigger) { - EventEmittedTrigger eeTrigger = (EventEmittedTrigger) t; + } else if (t instanceof EventEmittedTrigger eeTrigger) { Configuration cfg = new Configuration(); cfg.put("channelUID", eeTrigger.getChannel()); if (eeTrigger.getTrigger() != null) { @@ -435,15 +423,13 @@ private String removeIndentation(String script) { } return TriggerBuilder.create().withId(Integer.toString(triggerId++)).withTypeUID("core.ChannelEventTrigger") .withConfiguration(cfg).build(); - } else if (t instanceof ThingStateUpdateEventTrigger) { - ThingStateUpdateEventTrigger tsuTrigger = (ThingStateUpdateEventTrigger) t; + } else if (t instanceof ThingStateUpdateEventTrigger tsuTrigger) { Configuration cfg = new Configuration(); cfg.put("thingUID", tsuTrigger.getThing()); cfg.put("status", tsuTrigger.getState()); return TriggerBuilder.create().withId(Integer.toString(triggerId++)) .withTypeUID("core.ThingStatusUpdateTrigger").withConfiguration(cfg).build(); - } else if (t instanceof ThingStateChangedEventTrigger) { - ThingStateChangedEventTrigger tscTrigger = (ThingStateChangedEventTrigger) t; + } else if (t instanceof ThingStateChangedEventTrigger tscTrigger) { Configuration cfg = new Configuration(); cfg.put("thingUID", tscTrigger.getThing()); cfg.put("status", tscTrigger.getNewState()); @@ -461,8 +447,7 @@ public void onReadyMarkerAdded(ReadyMarker readyMarker) { for (String ruleFileName : modelRepository.getAllModelNamesOfType("rules")) { EObject model = modelRepository.getModel(ruleFileName); String ruleModelName = ruleFileName.substring(0, ruleFileName.indexOf(".")); - if (model instanceof RuleModel) { - RuleModel ruleModel = (RuleModel) model; + if (model instanceof RuleModel ruleModel) { int index = 1; for (org.openhab.core.model.rule.rules.Rule rule : ruleModel.getRules()) { addRule(toRule(ruleModelName, rule, index)); diff --git a/bundles/org.openhab.core.model.rule.runtime/src/org/openhab/core/model/rule/runtime/internal/RuleContextHelper.java b/bundles/org.openhab.core.model.rule.runtime/src/org/openhab/core/model/rule/runtime/internal/RuleContextHelper.java index 6986eaba626..b7b01cc2cf1 100644 --- a/bundles/org.openhab.core.model.rule.runtime/src/org/openhab/core/model/rule/runtime/internal/RuleContextHelper.java +++ b/bundles/org.openhab.core.model.rule.runtime/src/org/openhab/core/model/rule/runtime/internal/RuleContextHelper.java @@ -50,8 +50,8 @@ public static synchronized IEvaluationContext getContext(RuleModel ruleModel) { // check if a context already exists on the resource for (Adapter adapter : ruleModel.eAdapters()) { - if (adapter instanceof RuleContextAdapter) { - return ((RuleContextAdapter) adapter).getContext(); + if (adapter instanceof RuleContextAdapter contextAdapter) { + return contextAdapter.getContext(); } } Provider<@NonNull IEvaluationContext> contextProvider = injector.getProvider(IEvaluationContext.class); diff --git a/bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/DSLScriptEngine.java b/bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/DSLScriptEngine.java index 1f5a0812496..5ff5e75bf0b 100644 --- a/bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/DSLScriptEngine.java +++ b/bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/DSLScriptEngine.java @@ -142,12 +142,12 @@ public Object eval(String script) throws ScriptException { private DefaultEvaluationContext createEvaluationContext(Script script, IEvaluationContext specificContext) { IEvaluationContext parentContext = specificContext; - if (specificContext == null && script instanceof ScriptImpl) { - XExpression xExpression = ((ScriptImpl) script).getXExpression(); + if (specificContext == null && script instanceof ScriptImpl impl) { + XExpression xExpression = impl.getXExpression(); if (xExpression != null) { Resource resource = xExpression.eResource(); - if (resource instanceof XtextResource) { - IResourceServiceProvider provider = ((XtextResource) resource).getResourceServiceProvider(); + if (resource instanceof XtextResource xtextResource) { + IResourceServiceProvider provider = xtextResource.getResourceServiceProvider(); parentContext = provider.get(IEvaluationContext.class); } } @@ -171,19 +171,16 @@ private DefaultEvaluationContext createEvaluationContext(Script script, IEvaluat evalContext.newValue(QualifiedName.create("privateCache"), cachePreset.get("privateCache")); // now add specific implicit vars, where we have to map the right content Object value = context.getAttribute(OUTPUT_EVENT); - if (value instanceof ChannelTriggeredEvent) { - ChannelTriggeredEvent event = (ChannelTriggeredEvent) value; + if (value instanceof ChannelTriggeredEvent event) { evalContext.newValue(QualifiedName.create(ScriptJvmModelInferrer.VAR_RECEIVED_EVENT), event.getEvent()); evalContext.newValue(QualifiedName.create(ScriptJvmModelInferrer.VAR_TRIGGERING_CHANNEL), event.getChannel().getAsString()); } - if (value instanceof ItemEvent) { - ItemEvent event = (ItemEvent) value; + if (value instanceof ItemEvent event) { evalContext.newValue(QualifiedName.create(ScriptJvmModelInferrer.VAR_TRIGGERING_ITEM_NAME), event.getItemName()); } - if (value instanceof ThingStatusInfoChangedEvent) { - ThingStatusInfoChangedEvent event = (ThingStatusInfoChangedEvent) value; + if (value instanceof ThingStatusInfoChangedEvent event) { evalContext.newValue(QualifiedName.create(ScriptJvmModelInferrer.VAR_TRIGGERING_THING), event.getThingUID().toString()); evalContext.newValue(QualifiedName.create(ScriptJvmModelInferrer.VAR_PREVIOUS_STATUS), diff --git a/bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/ScriptImpl.java b/bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/ScriptImpl.java index 92be06f4a0e..985ed287af0 100644 --- a/bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/ScriptImpl.java +++ b/bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/ScriptImpl.java @@ -54,8 +54,8 @@ public Object execute() throws ScriptExecutionException { if (xExpression != null) { Resource resource = xExpression.eResource(); IEvaluationContext evaluationContext = null; - if (resource instanceof XtextResource) { - IResourceServiceProvider provider = ((XtextResource) resource).getResourceServiceProvider(); + if (resource instanceof XtextResource xtextResource) { + IResourceServiceProvider provider = xtextResource.getResourceServiceProvider(); evaluationContext = provider.get(IEvaluationContext.class); } return execute(evaluationContext); @@ -69,8 +69,8 @@ public Object execute(final IEvaluationContext evaluationContext) throws ScriptE if (xExpression != null) { Resource resource = xExpression.eResource(); IExpressionInterpreter interpreter = null; - if (resource instanceof XtextResource) { - IResourceServiceProvider provider = ((XtextResource) resource).getResourceServiceProvider(); + if (resource instanceof XtextResource xtextResource) { + IResourceServiceProvider provider = xtextResource.getResourceServiceProvider(); interpreter = provider.get(IExpressionInterpreter.class); } if (interpreter == null) { @@ -89,8 +89,8 @@ public Object execute(final IEvaluationContext evaluationContext) throws ScriptE } return result.getResult(); } catch (Throwable e) { - if (e instanceof ScriptExecutionException) { - throw (ScriptExecutionException) e; + if (e instanceof ScriptExecutionException exception) { + throw exception; } else { throw new ScriptExecutionException( "An error occurred during the script execution: " + e.getMessage(), e); diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/BusEvent.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/BusEvent.java index 68b0f54db21..4c1771378f4 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/BusEvent.java +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/BusEvent.java @@ -47,7 +47,7 @@ public class BusEvent { * @param item the item to send the command to * @param commandString the command to send */ - static public Object sendCommand(Item item, String commandString) { + public static Object sendCommand(Item item, String commandString) { if (item != null) { return sendCommand(item.getName(), commandString); } else { @@ -61,7 +61,7 @@ static public Object sendCommand(Item item, String commandString) { * @param item the item to send the command to * @param number the number to send as a command */ - static public Object sendCommand(Item item, Number number) { + public static Object sendCommand(Item item, Number number) { if (item != null && number != null) { return sendCommand(item.getName(), number.toString()); } else { @@ -75,7 +75,7 @@ static public Object sendCommand(Item item, Number number) { * @param itemName the name of the item to send the command to * @param commandString the command to send */ - static public Object sendCommand(String itemName, String commandString) { + public static Object sendCommand(String itemName, String commandString) { ItemRegistry registry = ScriptServiceUtil.getItemRegistry(); EventPublisher publisher = ScriptServiceUtil.getEventPublisher(); if (publisher != null && registry != null) { @@ -107,7 +107,7 @@ private static List getAcceptedCommandNames(Item item) * @param item the item to send the command to * @param command the command to send */ - static public Object sendCommand(Item item, Command command) { + public static Object sendCommand(Item item, Command command) { EventPublisher publisher = ScriptServiceUtil.getEventPublisher(); if (publisher != null && item != null) { publisher.post(ItemEventFactory.createCommandEvent(item.getName(), command)); @@ -121,7 +121,7 @@ static public Object sendCommand(Item item, Command command) { * @param item the item to send the status update for * @param state the new state of the item as a number */ - static public Object postUpdate(Item item, Number state) { + public static Object postUpdate(Item item, Number state) { if (item != null && state != null) { return postUpdate(item.getName(), state.toString()); } else { @@ -135,7 +135,7 @@ static public Object postUpdate(Item item, Number state) { * @param item the item to send the status update for * @param stateAsString the new state of the item */ - static public Object postUpdate(Item item, String stateAsString) { + public static Object postUpdate(Item item, String stateAsString) { if (item != null) { return postUpdate(item.getName(), stateAsString); } else { @@ -149,7 +149,7 @@ static public Object postUpdate(Item item, String stateAsString) { * @param itemName the name of the item to send the status update for * @param stateAsString the new state of the item */ - static public Object postUpdate(String itemName, String stateString) { + public static Object postUpdate(String itemName, String stateString) { ItemRegistry registry = ScriptServiceUtil.getItemRegistry(); EventPublisher publisher = ScriptServiceUtil.getEventPublisher(); if (publisher != null && registry != null) { @@ -181,7 +181,7 @@ private static List getAcceptedDataTypeNames(Item item * @param item the item to send the status update for * @param state the new state of the item */ - static public Object postUpdate(Item item, State state) { + public static Object postUpdate(Item item, State state) { EventPublisher publisher = ScriptServiceUtil.getEventPublisher(); if (publisher != null && item != null) { publisher.post(ItemEventFactory.createStateEvent(item.getName(), state)); @@ -196,7 +196,7 @@ static public Object postUpdate(Item item, State state) { * @param items the items for which the state should be stored * @return the map of items with their states */ - static public Map storeStates(Item... items) { + public static Map storeStates(Item... items) { Map statesMap = new HashMap<>(); if (items != null) { for (Item item : items) { @@ -222,7 +222,7 @@ static public Map storeStates(Item... items) { * @param statesMap a map with ({@link Item}, {@link State}) entries * @return null */ - static public Object restoreStates(Map statesMap) { + public static Object restoreStates(Map statesMap) { if (statesMap != null) { for (Entry entry : statesMap.entrySet()) { if (entry.getValue() instanceof Command) { diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/HTTP.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/HTTP.java index 30db3358257..6c589d241b7 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/HTTP.java +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/HTTP.java @@ -43,7 +43,7 @@ public class HTTP { * @param url the URL to be used for the GET request. * @return the response body or NULL when the request went wrong */ - static public String sendHttpGetRequest(String url) { + public static String sendHttpGetRequest(String url) { return sendHttpGetRequest(url, 5000); } @@ -54,7 +54,7 @@ static public String sendHttpGetRequest(String url) { * @param timeout timeout in ms * @return the response body or NULL when the request went wrong */ - static public String sendHttpGetRequest(String url, int timeout) { + public static String sendHttpGetRequest(String url, int timeout) { String response = null; try { return HttpUtil.executeUrl(HttpMethod.GET.name(), url, timeout); @@ -89,7 +89,7 @@ public static String sendHttpGetRequest(String url, Map headers, * @param url the URL to be used for the PUT request. * @return the response body or NULL when the request went wrong */ - static public String sendHttpPutRequest(String url) { + public static String sendHttpPutRequest(String url) { return sendHttpPutRequest(url, 1000); } @@ -100,7 +100,7 @@ static public String sendHttpPutRequest(String url) { * @param timeout timeout in ms * @return the response body or NULL when the request went wrong */ - static public String sendHttpPutRequest(String url, int timeout) { + public static String sendHttpPutRequest(String url, int timeout) { String response = null; try { response = HttpUtil.executeUrl(HttpMethod.PUT.name(), url, timeout); @@ -119,7 +119,7 @@ static public String sendHttpPutRequest(String url, int timeout) { * send. * @return the response body or NULL when the request went wrong */ - static public String sendHttpPutRequest(String url, String contentType, String content) { + public static String sendHttpPutRequest(String url, String contentType, String content) { return sendHttpPutRequest(url, contentType, content, 1000); } @@ -133,7 +133,7 @@ static public String sendHttpPutRequest(String url, String contentType, String c * @param timeout timeout in ms * @return the response body or NULL when the request went wrong */ - static public String sendHttpPutRequest(String url, String contentType, String content, int timeout) { + public static String sendHttpPutRequest(String url, String contentType, String content, int timeout) { String response = null; try { response = HttpUtil.executeUrl(HttpMethod.PUT.name(), url, @@ -155,7 +155,7 @@ static public String sendHttpPutRequest(String url, String contentType, String c * @param timeout timeout in ms * @return the response body or NULL when the request went wrong */ - static public String sendHttpPutRequest(String url, String contentType, String content, Map headers, + public static String sendHttpPutRequest(String url, String contentType, String content, Map headers, int timeout) { try { Properties headerProperties = new Properties(); @@ -174,7 +174,7 @@ static public String sendHttpPutRequest(String url, String contentType, String c * @param url the URL to be used for the POST request. * @return the response body or NULL when the request went wrong */ - static public String sendHttpPostRequest(String url) { + public static String sendHttpPostRequest(String url) { return sendHttpPostRequest(url, 1000); } @@ -185,7 +185,7 @@ static public String sendHttpPostRequest(String url) { * @param timeout timeout in ms * @return the response body or NULL when the request went wrong */ - static public String sendHttpPostRequest(String url, int timeout) { + public static String sendHttpPostRequest(String url, int timeout) { String response = null; try { response = HttpUtil.executeUrl(HttpMethod.POST.name(), url, timeout); @@ -204,7 +204,7 @@ static public String sendHttpPostRequest(String url, int timeout) { * send. * @return the response body or NULL when the request went wrong */ - static public String sendHttpPostRequest(String url, String contentType, String content) { + public static String sendHttpPostRequest(String url, String contentType, String content) { return sendHttpPostRequest(url, contentType, content, 1000); } @@ -218,7 +218,7 @@ static public String sendHttpPostRequest(String url, String contentType, String * @param timeout timeout in ms * @return the response body or NULL when the request went wrong */ - static public String sendHttpPostRequest(String url, String contentType, String content, int timeout) { + public static String sendHttpPostRequest(String url, String contentType, String content, int timeout) { String response = null; try { response = HttpUtil.executeUrl(HttpMethod.POST.name(), url, @@ -259,7 +259,7 @@ public static String sendHttpPostRequest(String url, String contentType, String * @param url the URL to be used for the DELETE request. * @return the response body or NULL when the request went wrong */ - static public String sendHttpDeleteRequest(String url) { + public static String sendHttpDeleteRequest(String url) { return sendHttpDeleteRequest(url, 1000); } @@ -270,7 +270,7 @@ static public String sendHttpDeleteRequest(String url) { * @param timeout timeout in ms * @return the response body or NULL when the request went wrong */ - static public String sendHttpDeleteRequest(String url, int timeout) { + public static String sendHttpDeleteRequest(String url, int timeout) { String response = null; try { response = HttpUtil.executeUrl(HttpMethod.DELETE.name(), url, timeout); @@ -288,7 +288,7 @@ static public String sendHttpDeleteRequest(String url, int timeout) { * @param timeout timeout in ms * @return the response body or NULL when the request went wrong */ - static public String sendHttpDeleteRequest(String url, Map headers, int timeout) { + public static String sendHttpDeleteRequest(String url, Map headers, int timeout) { try { Properties headerProperties = new Properties(); headerProperties.putAll(headers); diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Log.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Log.java index b293f1b38c6..fb44dec1a9f 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Log.java +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Log.java @@ -35,7 +35,7 @@ public class Log { * * @see Logger */ - static public void logDebug(String loggerName, String format, Object... args) { + public static void logDebug(String loggerName, String format, Object... args) { LoggerFactory.getLogger(LOGGER_NAME_PREFIX.concat(loggerName)).debug(format, args); } @@ -49,7 +49,7 @@ static public void logDebug(String loggerName, String format, Object... args) { * * @see Logger */ - static public void logInfo(String loggerName, String format, Object... args) { + public static void logInfo(String loggerName, String format, Object... args) { LoggerFactory.getLogger(LOGGER_NAME_PREFIX.concat(loggerName)).info(format, args); } @@ -63,7 +63,7 @@ static public void logInfo(String loggerName, String format, Object... args) { * * @see Logger */ - static public void logWarn(String loggerName, String format, Object... args) { + public static void logWarn(String loggerName, String format, Object... args) { LoggerFactory.getLogger(LOGGER_NAME_PREFIX.concat(loggerName)).warn(format, args); } @@ -77,7 +77,7 @@ static public void logWarn(String loggerName, String format, Object... args) { * * @see Logger */ - static public void logError(String loggerName, String format, Object... args) { + public static void logError(String loggerName, String format, Object... args) { LoggerFactory.getLogger(LOGGER_NAME_PREFIX.concat(loggerName)).error(format, args); } } diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/internal/engine/action/ThingActionService.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/internal/engine/action/ThingActionService.java index 04c4d06e150..b127c2b425f 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/internal/engine/action/ThingActionService.java +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/internal/engine/action/ThingActionService.java @@ -80,8 +80,7 @@ public Class getActionClass() { if (thing != null) { ThingHandler handler = thing.getHandler(); if (handler != null) { - ThingActions thingActions = THING_ACTIONS_MAP.get(getKey(scope, thingUid)); - return thingActions; + return THING_ACTIONS_MAP.get(getKey(scope, thingUid)); } } return null; diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/jvmmodel/ScriptTypeComputer.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/jvmmodel/ScriptTypeComputer.java index 60b5f07ed71..1da6596e0a0 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/jvmmodel/ScriptTypeComputer.java +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/jvmmodel/ScriptTypeComputer.java @@ -32,8 +32,8 @@ public class ScriptTypeComputer extends XbaseTypeComputer { @Override public void computeTypes(XExpression expression, ITypeComputationState state) { - if (expression instanceof QuantityLiteral) { - _computeTypes((QuantityLiteral) expression, state); + if (expression instanceof QuantityLiteral literal) { + _computeTypes(literal, state); } else { super.computeTypes(expression, state); } diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ActionClassLoader.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ActionClassLoader.java index ba00db0ca1f..49be1a7ec4f 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ActionClassLoader.java +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ActionClassLoader.java @@ -22,7 +22,7 @@ * * @author Kai Kreuzer - Initial contribution */ -final public class ActionClassLoader extends ClassLoader { +public final class ActionClassLoader extends ClassLoader { public ActionClassLoader(ClassLoader cl) { super(cl); @@ -31,8 +31,7 @@ public ActionClassLoader(ClassLoader cl) { @Override public Class loadClass(String name) throws ClassNotFoundException { try { - Class clazz = getParent().loadClass(name); - return clazz; + return getParent().loadClass(name); } catch (ClassNotFoundException e) { for (ActionService actionService : ScriptServiceUtil.getActionServices()) { if (actionService.getActionClassName().equals(name)) { diff --git a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/internal/SitemapProviderImpl.java b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/internal/SitemapProviderImpl.java index adbf92a5e3d..16242356752 100644 --- a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/internal/SitemapProviderImpl.java +++ b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/internal/SitemapProviderImpl.java @@ -97,8 +97,8 @@ public void modelChanged(String modelName, EventType type) { } else { EObject sitemap = modelRepo.getModel(modelName); // if the sitemap file is empty it will not be in the repo and thus there is no need to cache it here - if (sitemap instanceof Sitemap) { - sitemapModelCache.put(modelName, (Sitemap) sitemap); + if (sitemap instanceof Sitemap sitemap1) { + sitemapModelCache.put(modelName, sitemap1); } } } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/ItemHistoryDTO.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/ItemHistoryDTO.java index c493405766b..2bf26b1e9bf 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/ItemHistoryDTO.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/ItemHistoryDTO.java @@ -45,9 +45,9 @@ public ItemHistoryDTO() { public void addData(Long time, State state) { HistoryDataBean newVal = new HistoryDataBean(); newVal.time = time; - if (state instanceof QuantityType) { + if (state instanceof QuantityType type) { // we strip the unit from the state, since historic item states are expected to be all in the default unit - newVal.state = ((QuantityType) state).toBigDecimal().toString(); + newVal.state = type.toBigDecimal().toString(); } else { newVal.state = state.toString(); } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java index 1b8984c8109..9bd3801e736 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java @@ -1132,8 +1132,8 @@ public static long countBetween(Item item, ZonedDateTime begin, @Nullable ZonedD */ public static long countBetween(Item item, ZonedDateTime begin, @Nullable ZonedDateTime end, String serviceId) { Iterable historicItems = getAllStatesBetween(item, begin, end, serviceId); - if (historicItems instanceof Collection) { - return ((Collection) historicItems).size(); + if (historicItems instanceof Collection collection) { + return collection.size(); } else { return StreamSupport.stream(historicItems.spliterator(), false).count(); } @@ -1240,8 +1240,7 @@ public static long countStateChangesBetween(Item item, ZonedDateTime begin, @Nul private static Iterable getAllStatesBetween(Item item, ZonedDateTime begin, @Nullable ZonedDateTime end, String serviceId) { PersistenceService service = getService(serviceId); - if (service instanceof QueryablePersistenceService) { - QueryablePersistenceService qService = (QueryablePersistenceService) service; + if (service instanceof QueryablePersistenceService qService) { FilterCriteria filter = new FilterCriteria(); filter.setBeginDate(begin); if (end != null) { diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java index dade0f3df82..be9dcf0e5bb 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java @@ -192,18 +192,15 @@ private boolean appliesToItem(PersistenceItemConfiguration config, Item item) { if (itemCfg instanceof PersistenceAllConfig) { return true; } - if (itemCfg instanceof PersistenceItemConfig) { - PersistenceItemConfig singleItemConfig = (PersistenceItemConfig) itemCfg; + if (itemCfg instanceof PersistenceItemConfig singleItemConfig) { if (item.getName().equals(singleItemConfig.getItem())) { return true; } } - if (itemCfg instanceof PersistenceGroupConfig) { - PersistenceGroupConfig groupItemConfig = (PersistenceGroupConfig) itemCfg; + if (itemCfg instanceof PersistenceGroupConfig groupItemConfig) { try { Item gItem = itemRegistry.getItem(groupItemConfig.getGroup()); - if (gItem instanceof GroupItem) { - GroupItem groupItem = (GroupItem) gItem; + if (gItem instanceof GroupItem groupItem) { if (groupItem.getAllMembers().contains(item)) { return true; } @@ -233,8 +230,7 @@ Iterable getAllItems(PersistenceItemConfiguration config) { // otherwise, go through the detailed definitions Set items = new HashSet<>(); for (Object itemCfg : config.getItems()) { - if (itemCfg instanceof PersistenceItemConfig) { - PersistenceItemConfig singleItemConfig = (PersistenceItemConfig) itemCfg; + if (itemCfg instanceof PersistenceItemConfig singleItemConfig) { String itemName = singleItemConfig.getItem(); try { items.add(itemRegistry.getItem(itemName)); @@ -242,13 +238,11 @@ Iterable getAllItems(PersistenceItemConfiguration config) { logger.debug("Item '{}' does not exist.", itemName); } } - if (itemCfg instanceof PersistenceGroupConfig) { - PersistenceGroupConfig groupItemConfig = (PersistenceGroupConfig) itemCfg; + if (itemCfg instanceof PersistenceGroupConfig groupItemConfig) { String groupName = groupItemConfig.getGroup(); try { Item gItem = itemRegistry.getItem(groupName); - if (gItem instanceof GroupItem) { - GroupItem groupItem = (GroupItem) gItem; + if (gItem instanceof GroupItem groupItem) { items.addAll(groupItem.getAllMembers()); } } catch (ItemNotFoundException e) { @@ -270,7 +264,7 @@ Iterable getAllItems(PersistenceItemConfiguration config) { @SuppressWarnings("null") private void initialize(Item item) { // get the last persisted state from the persistence service if no state is yet set - if (UnDefType.NULL.equals(item.getState()) && item instanceof GenericItem) { + if (UnDefType.NULL.equals(item.getState()) && item instanceof GenericItem genericItem) { for (Entry entry : persistenceServiceConfigs .entrySet()) { final String serviceName = entry.getKey(); @@ -280,8 +274,7 @@ private void initialize(Item item) { if (hasStrategy(config, itemConfig, PersistenceStrategy.Globals.RESTORE)) { if (appliesToItem(itemConfig, item)) { PersistenceService service = persistenceServices.get(serviceName); - if (service instanceof QueryablePersistenceService) { - QueryablePersistenceService queryService = (QueryablePersistenceService) service; + if (service instanceof QueryablePersistenceService queryService) { FilterCriteria filter = new FilterCriteria().setItemName(item.getName()) .setPageSize(1); Iterable result = safeCaller @@ -297,7 +290,6 @@ private void initialize(Item item) { Iterator it = result.iterator(); if (it.hasNext()) { HistoricItem historicItem = it.next(); - GenericItem genericItem = (GenericItem) item; genericItem.removeStateChangeListener(this); genericItem.setState(historicItem.getState()); genericItem.addStateChangeListener(this); @@ -325,8 +317,8 @@ private void initialize(Item item) { private void removeItemStateChangeListeners() { for (Item item : itemRegistry.getAll()) { - if (item instanceof GenericItem) { - ((GenericItem) item).removeStateChangeListener(this); + if (item instanceof GenericItem genericItem) { + genericItem.removeStateChangeListener(this); } } } @@ -340,8 +332,7 @@ private void removeItemStateChangeListeners() { */ private void createTimers(final String dbId, List strategies) { for (PersistenceStrategy strategy : strategies) { - if (strategy instanceof PersistenceCronStrategy) { - PersistenceCronStrategy cronStrategy = (PersistenceCronStrategy) strategy; + if (strategy instanceof PersistenceCronStrategy cronStrategy) { String cronExpression = cronStrategy.getCronExpression(); final PersistItemsJob job = new PersistItemsJob(this, dbId, cronStrategy.getName()); @@ -448,16 +439,14 @@ public void allItemsChanged(Collection oldItemNames) { @Override public void added(Item item) { initialize(item); - if (item instanceof GenericItem) { - GenericItem genericItem = (GenericItem) item; + if (item instanceof GenericItem genericItem) { genericItem.addStateChangeListener(this); } } @Override public void removed(Item item) { - if (item instanceof GenericItem) { - GenericItem genericItem = (GenericItem) item; + if (item instanceof GenericItem genericItem) { genericItem.removeStateChangeListener(this); } } diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestPersistenceService.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestPersistenceService.java index 8c5aec88c24..419e1aec34a 100644 --- a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestPersistenceService.java +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestPersistenceService.java @@ -133,7 +133,7 @@ public ZonedDateTime getTimestamp() { @Override public State getState() { Item item = itemRegistry.get(filter.getItemName()); - Unit unit = item instanceof NumberItem ? ((NumberItem) item).getUnit() : null; + Unit unit = item instanceof NumberItem ni ? ni.getUnit() : null; return unit == null ? new DecimalType(year) : QuantityType.valueOf(year, unit); } diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticsMetadataProvider.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticsMetadataProvider.java index df5b4921c5a..2f64548c0e7 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticsMetadataProvider.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticsMetadataProvider.java @@ -125,8 +125,8 @@ private void processItem(Item item) { } } - if (item instanceof GroupItem) { - for (Item memberItem : ((GroupItem) item).getMembers()) { + if (item instanceof GroupItem groupItem) { + for (Item memberItem : groupItem.getMembers()) { processItem(memberItem); } } @@ -169,8 +169,7 @@ private void processHierarchy(Item item, Map configuration) { processParent(type, parentItem, configuration); } } - if (item instanceof GroupItem) { - GroupItem gItem = (GroupItem) item; + if (item instanceof GroupItem gItem) { for (Item memberItem : gItem.getMembers()) { processMember(type, memberItem, configuration); } @@ -258,8 +257,8 @@ public void removed(Item item) { if (removedMd != null) { notifyListenersAboutRemovedElement(removedMd); - if (item instanceof GroupItem) { - for (Item memberItem : ((GroupItem) item).getMembers()) { + if (item instanceof GroupItem groupItem) { + for (Item memberItem : groupItem.getMembers()) { processItem(memberItem); } } diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticsServiceImpl.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticsServiceImpl.java index 9bcf7413d5c..4ef13018d74 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticsServiceImpl.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticsServiceImpl.java @@ -65,8 +65,7 @@ public Set getItemsInLocation(Class locationType) { Set locationItems = itemRegistry.stream().filter(SemanticsPredicates.isA(locationType)) .collect(Collectors.toSet()); for (Item locationItem : locationItems) { - if (locationItem instanceof GroupItem) { - GroupItem gItem = (GroupItem) locationItem; + if (locationItem instanceof GroupItem gItem) { items.addAll(gItem .getMembers(SemanticsPredicates.isA(Point.class).or(SemanticsPredicates.isA(Equipment.class)))); } @@ -89,8 +88,7 @@ public Set getItemsInLocation(String labelOrSynonym, Locale locale) { Set locationItems = itemRegistry.stream().filter(ItemPredicates.hasLabel(labelOrSynonym) .or(hasSynonym(labelOrSynonym)).and(SemanticsPredicates.isLocation())).collect(Collectors.toSet()); for (Item locationItem : locationItems) { - if (locationItem instanceof GroupItem) { - GroupItem gItem = (GroupItem) locationItem; + if (locationItem instanceof GroupItem gItem) { items.addAll(gItem.getMembers( SemanticsPredicates.isA(Point.class).or(SemanticsPredicates.isA(Equipment.class)))); } diff --git a/bundles/org.openhab.core.storage.json/src/test/java/org/openhab/core/storage/json/internal/JsonStorageTest.java b/bundles/org.openhab.core.storage.json/src/test/java/org/openhab/core/storage/json/internal/JsonStorageTest.java index 0be2f7c2acb..ad21e9d8817 100644 --- a/bundles/org.openhab.core.storage.json/src/test/java/org/openhab/core/storage/json/internal/JsonStorageTest.java +++ b/bundles/org.openhab.core.storage.json/src/test/java/org/openhab/core/storage/json/internal/JsonStorageTest.java @@ -53,7 +53,7 @@ public class JsonStorageTest extends JavaTest { @BeforeEach public void setUp() throws IOException { - tmpFile = File.createTempFile("storage-debug", ".json"); + tmpFile = Files.createTempFile("storage-debug", ".json").toFile(); tmpFile.deleteOnExit(); objectStorage = new JsonStorage<>(tmpFile, this.getClass().getClassLoader(), 0, 0, 0, List.of()); } diff --git a/bundles/org.openhab.core.storage.json/src/test/java/org/openhab/core/storage/json/internal/MigrationTest.java b/bundles/org.openhab.core.storage.json/src/test/java/org/openhab/core/storage/json/internal/MigrationTest.java index 2e4dbcf1b87..cd24b2ced7f 100644 --- a/bundles/org.openhab.core.storage.json/src/test/java/org/openhab/core/storage/json/internal/MigrationTest.java +++ b/bundles/org.openhab.core.storage.json/src/test/java/org/openhab/core/storage/json/internal/MigrationTest.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.List; import java.util.Objects; @@ -48,7 +49,7 @@ public class MigrationTest { @BeforeEach public void setup() throws IOException { - tmpFile = File.createTempFile("storage-debug", ".json"); + tmpFile = Files.createTempFile("storage-debug", ".json").toFile(); tmpFile.deleteOnExit(); // store old class diff --git a/bundles/org.openhab.core.test.magic/src/main/java/org/openhab/core/magic/binding/handler/MagicDelayedOnlineHandler.java b/bundles/org.openhab.core.test.magic/src/main/java/org/openhab/core/magic/binding/handler/MagicDelayedOnlineHandler.java index d1c60ac4a91..6bb90d6d6e2 100644 --- a/bundles/org.openhab.core.test.magic/src/main/java/org/openhab/core/magic/binding/handler/MagicDelayedOnlineHandler.java +++ b/bundles/org.openhab.core.test.magic/src/main/java/org/openhab/core/magic/binding/handler/MagicDelayedOnlineHandler.java @@ -44,9 +44,8 @@ public void initialize() { @Override public void handleCommand(ChannelUID channelUID, Command command) { - if (channelUID.getId().equals("number")) { - if (command instanceof DecimalType) { - DecimalType cmd = (DecimalType) command; + if ("number".equals(channelUID.getId())) { + if (command instanceof DecimalType cmd) { int cmdInt = cmd.intValue(); ThingStatus status = cmdInt > 0 ? ThingStatus.ONLINE : ThingStatus.OFFLINE; int waitTime = Math.abs(cmd.intValue()); diff --git a/bundles/org.openhab.core.test.magic/src/main/java/org/openhab/core/magic/binding/internal/automation/modules/MagicThingActionsService.java b/bundles/org.openhab.core.test.magic/src/main/java/org/openhab/core/magic/binding/internal/automation/modules/MagicThingActionsService.java index ae8f4e023a7..f7a0fa956c3 100644 --- a/bundles/org.openhab.core.test.magic/src/main/java/org/openhab/core/magic/binding/internal/automation/modules/MagicThingActionsService.java +++ b/bundles/org.openhab.core.test.magic/src/main/java/org/openhab/core/magic/binding/internal/automation/modules/MagicThingActionsService.java @@ -59,8 +59,8 @@ public class MagicThingActionsService implements ThingActions { @Override public void setThingHandler(ThingHandler handler) { - if (handler instanceof MagicActionModuleThingHandler) { - this.handler = (MagicActionModuleThingHandler) handler; + if (handler instanceof MagicActionModuleThingHandler thingHandler) { + this.handler = thingHandler; } } diff --git a/bundles/org.openhab.core.test.magic/src/main/java/org/openhab/core/magic/binding/internal/firmware/MagicFirmwareProvider.java b/bundles/org.openhab.core.test.magic/src/main/java/org/openhab/core/magic/binding/internal/firmware/MagicFirmwareProvider.java index 36492d1ea67..be7486e51ae 100644 --- a/bundles/org.openhab.core.test.magic/src/main/java/org/openhab/core/magic/binding/internal/firmware/MagicFirmwareProvider.java +++ b/bundles/org.openhab.core.test.magic/src/main/java/org/openhab/core/magic/binding/internal/firmware/MagicFirmwareProvider.java @@ -73,8 +73,7 @@ public class MagicFirmwareProvider implements FirmwareProvider { private static Firmware createFirmware(final @Nullable String model, final String version, boolean modelRestricted) { - Firmware firmware = FirmwareBuilder.create(MagicBindingConstants.THING_TYPE_FIRMWARE_UPDATE, version) - .withModel(model).withModelRestricted(modelRestricted).build(); - return firmware; + return FirmwareBuilder.create(MagicBindingConstants.THING_TYPE_FIRMWARE_UPDATE, version).withModel(model) + .withModelRestricted(modelRestricted).build(); } } diff --git a/bundles/org.openhab.core.test/src/main/java/org/openhab/core/test/SyntheticBundleInstaller.java b/bundles/org.openhab.core.test/src/main/java/org/openhab/core/test/SyntheticBundleInstaller.java index 5731af89e39..15fa91b289f 100644 --- a/bundles/org.openhab.core.test/src/main/java/org/openhab/core/test/SyntheticBundleInstaller.java +++ b/bundles/org.openhab.core.test/src/main/java/org/openhab/core/test/SyntheticBundleInstaller.java @@ -232,10 +232,7 @@ public static Bundle installFragment(BundleContext bundleContext, String testBun String bundlePath = BUNDLE_POOL_PATH + "/" + testBundleName + "/"; byte[] syntheticBundleBytes = createSyntheticBundle(bundleContext.getBundle(), bundlePath, testBundleName, extensionsToInclude); - - Bundle syntheticBundle = bundleContext.installBundle(testBundleName, - new ByteArrayInputStream(syntheticBundleBytes)); - return syntheticBundle; + return bundleContext.installBundle(testBundleName, new ByteArrayInputStream(syntheticBundleBytes)); } private static boolean isBundleAvailable(BundleContext context, String bsn) { @@ -396,8 +393,7 @@ private static List collectEntries(Bundle bundle, String path, Set private static String convertToFileEntry(URI baseURI, URL entryURL) throws URISyntaxException { URI entryURI = entryURL.toURI(); URI relativeURI = baseURI.relativize(entryURI); - String fileEntry = relativeURI.toString(); - return fileEntry; + return relativeURI.toString(); } private static Manifest getManifest(Bundle bundle, String bundlePath) throws IOException { diff --git a/bundles/org.openhab.core.test/src/main/java/org/openhab/core/test/java/JavaOSGiTest.java b/bundles/org.openhab.core.test/src/main/java/org/openhab/core/test/java/JavaOSGiTest.java index dfbb0fba468..27f55a748a9 100644 --- a/bundles/org.openhab.core.test/src/main/java/org/openhab/core/test/java/JavaOSGiTest.java +++ b/bundles/org.openhab.core.test/src/main/java/org/openhab/core/test/java/JavaOSGiTest.java @@ -111,7 +111,7 @@ public void bindBundleContext() { * @return List of OSGi services or empty List if no service can be found for the given class and filter */ protected List getServices(Class clazz, Predicate> filter) { - final ServiceReference<@Nullable T> serviceReferences[] = getServices(clazz); + final ServiceReference<@Nullable T>[] serviceReferences = getServices(clazz); if (serviceReferences == null) { new MissingServiceAnalyzer(System.out, bundleContext).printMissingServiceDetails(clazz); @@ -192,7 +192,7 @@ protected List getServices(Class clazz, Predicate> * @return List of OSGi service or empty List if no matching services can be found for the given classes */ protected List getServices(Class clazz, Class implementationClass) { - final ServiceReference<@Nullable T> serviceReferences[] = getServices(clazz); + final ServiceReference<@Nullable T>[] serviceReferences = getServices(clazz); if (serviceReferences == null) { new MissingServiceAnalyzer(System.out, bundleContext).printMissingServiceDetails(clazz); diff --git a/bundles/org.openhab.core.test/src/main/java/org/openhab/core/test/java/JavaTest.java b/bundles/org.openhab.core.test/src/main/java/org/openhab/core/test/java/JavaTest.java index 90f55e7686f..5bd878314c5 100644 --- a/bundles/org.openhab.core.test/src/main/java/org/openhab/core/test/java/JavaTest.java +++ b/bundles/org.openhab.core.test/src/main/java/org/openhab/core/test/java/JavaTest.java @@ -98,7 +98,7 @@ protected void assertNoLogMessage(Class clazz) { if (appender == null) { Assertions.fail("Logger for class '" + clazz + "' not found."); } - if (appender.list.size() != 0) { + if (!appender.list.isEmpty()) { Assertions.fail("Expected no log message for class '" + clazz + "', but found '" + appender.list + "'."); } } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ThingStatus.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ThingStatus.java index 19f2b8eee6a..747c6b0bebd 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ThingStatus.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ThingStatus.java @@ -28,5 +28,5 @@ public enum ThingStatus { ONLINE, OFFLINE, REMOVING, - REMOVED; + REMOVED } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/BaseThingHandlerFactory.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/BaseThingHandlerFactory.java index c15aaf5bba6..b991c7117c9 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/BaseThingHandlerFactory.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/BaseThingHandlerFactory.java @@ -148,8 +148,8 @@ private void registerServices(Thing thing, ThingHandler thingHandler) { Object serviceInstance = c.getConstructor().newInstance(); ThingHandlerService ths = null; - if (serviceInstance instanceof ThingHandlerService) { - ths = (ThingHandlerService) serviceInstance; + if (serviceInstance instanceof ThingHandlerService service) { + ths = service; ths.setThingHandler(thingHandler); } else { logger.warn( @@ -340,9 +340,8 @@ public void removeThing(ThingUID thingUID) { ThingUID effectiveUID = thingUID != null ? thingUID : ThingFactory.generateRandomThingUID(thingTypeUID); ThingType thingType = getThingTypeByUID(thingTypeUID); if (thingType != null) { - Thing thing = ThingFactory.createThing(thingType, effectiveUID, configuration, bridgeUID, + return ThingFactory.createThing(thingType, effectiveUID, configuration, bridgeUID, getConfigDescriptionRegistry()); - return thing; } else { return null; } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/firmware/ProgressStep.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/firmware/ProgressStep.java index 0bfea244cb1..6bcd881f617 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/firmware/ProgressStep.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/firmware/ProgressStep.java @@ -43,5 +43,5 @@ public enum ProgressStep { UPDATING, /** The {@link FirmwareUpdateHandler} is going to reboot the device. */ - REBOOTING; + REBOOTING } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/firmware/FirmwareStatus.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/firmware/FirmwareStatus.java index 3e240984075..b7bd713739b 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/firmware/FirmwareStatus.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/firmware/FirmwareStatus.java @@ -45,6 +45,6 @@ public enum FirmwareStatus { UPDATE_AVAILABLE, /** There is a newer firmware of the thing available and the firmware update for the thing can be executed. */ - UPDATE_EXECUTABLE; + UPDATE_EXECUTABLE } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/firmware/FirmwareUpdateResult.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/firmware/FirmwareUpdateResult.java index b987fac35c6..58c22154115 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/firmware/FirmwareUpdateResult.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/firmware/FirmwareUpdateResult.java @@ -29,5 +29,5 @@ public enum FirmwareUpdateResult { ERROR, /** Indicates that the firmware update was canceled. */ - CANCELED; + CANCELED } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/AutoUpdateManager.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/AutoUpdateManager.java index 2acd86de2a6..ec9cb4912a5 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/AutoUpdateManager.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/AutoUpdateManager.java @@ -141,8 +141,7 @@ public void receiveCommand(ItemCommandEvent commandEvent, Item item) { } final String itemName = commandEvent.getItemName(); final Command command = commandEvent.getItemCommand(); - if (command instanceof State) { - final State state = (State) command; + if (command instanceof State state) { Recommendation autoUpdate = shouldAutoUpdate(item); diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/BridgeImpl.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/BridgeImpl.java index e4d75cbc1cc..c24b3e8433e 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/BridgeImpl.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/BridgeImpl.java @@ -82,8 +82,8 @@ public List getThings() { public @Nullable BridgeHandler getHandler() { BridgeHandler bridgeHandler = null; ThingHandler thingHandler = super.getHandler(); - if (thingHandler instanceof BridgeHandler) { - bridgeHandler = (BridgeHandler) thingHandler; + if (thingHandler instanceof BridgeHandler handler) { + bridgeHandler = handler; } else if (thingHandler != null) { logger.warn("Handler of bridge '{}' must implement BridgeHandler interface.", getUID()); } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java index c47737a6af4..6bf05ad282b 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java @@ -180,20 +180,20 @@ public Set getSubscribedEventTypes() { @Override public void receive(Event event) { - if (event instanceof ItemStateUpdatedEvent) { - receiveUpdate((ItemStateUpdatedEvent) event); - } else if (event instanceof ItemCommandEvent) { - receiveCommand((ItemCommandEvent) event); - } else if (event instanceof ChannelTriggeredEvent) { - receiveTrigger((ChannelTriggeredEvent) event); - } else if (event instanceof AbstractItemRegistryEvent) { - String itemName = ((AbstractItemRegistryEvent) event).getItem().name; + if (event instanceof ItemStateUpdatedEvent updatedEvent) { + receiveUpdate(updatedEvent); + } else if (event instanceof ItemCommandEvent commandEvent) { + receiveCommand(commandEvent); + } else if (event instanceof ChannelTriggeredEvent triggeredEvent) { + receiveTrigger(triggeredEvent); + } else if (event instanceof AbstractItemRegistryEvent registryEvent) { + String itemName = registryEvent.getItem().name; profiles.entrySet().removeIf(entry -> { ItemChannelLink link = itemChannelLinkRegistry.get(entry.getKey()); return link != null && itemName.equals(link.getItemName()); }); - } else if (event instanceof AbstractThingRegistryEvent) { - ThingUID thingUid = new ThingUID(((AbstractThingRegistryEvent) event).getThing().UID); + } else if (event instanceof AbstractThingRegistryEvent registryEvent) { + ThingUID thingUid = new ThingUID(registryEvent.getThing().UID); profiles.entrySet().removeIf(entry -> { ItemChannelLink link = itemChannelLinkRegistry.get(entry.getKey()); return link != null && thingUid.equals(link.getLinkedUID().getThingUID()); @@ -341,17 +341,16 @@ private void receiveCommand(ItemCommandEvent commandEvent) { handleEvent(itemName, command, commandEvent.getSource(), s -> acceptedCommandTypeMap.get(s), (profile, thing, convertedCommand) -> { - if (profile instanceof StateProfile) { + if (profile instanceof StateProfile stateProfile) { int key = Objects.hash("COMMAND", profile, thing); - Profile p = profileSafeCallCache.putIfAbsentAndGet(key, () -> { - return safeCaller.create(((StateProfile) profile), StateProfile.class) // - .withAsync() // - .withIdentifier(thing) // - .withTimeout(THINGHANDLER_EVENT_TIMEOUT) // - .build(); - }); - if (p instanceof StateProfile) { - ((StateProfile) p).onCommandFromItem(convertedCommand); + Profile p = profileSafeCallCache.putIfAbsentAndGet(key, + () -> safeCaller.create(stateProfile, StateProfile.class) // + .withAsync() // + .withIdentifier(thing) // + .withTimeout(THINGHANDLER_EVENT_TIMEOUT) // + .build()); + if (p instanceof StateProfile profileP) { + profileP.onCommandFromItem(convertedCommand); } else { throw new IllegalStateException("ExpiringCache didn't provide a StateProfile instance!"); } @@ -365,13 +364,12 @@ private void receiveUpdate(ItemStateUpdatedEvent updateEvent) { handleEvent(itemName, newState, updateEvent.getSource(), s -> acceptedStateTypeMap.get(s), (profile, thing, convertedState) -> { int key = Objects.hash("UPDATE", profile, thing); - Profile p = profileSafeCallCache.putIfAbsentAndGet(key, () -> { - return safeCaller.create(profile, Profile.class) // - .withAsync() // - .withIdentifier(thing) // - .withTimeout(THINGHANDLER_EVENT_TIMEOUT) // - .build(); - }); + Profile p = profileSafeCallCache.putIfAbsentAndGet(key, + () -> safeCaller.create(profile, Profile.class) // + .withAsync() // + .withIdentifier(thing) // + .withTimeout(THINGHANDLER_EVENT_TIMEOUT) // + .build()); if (p != null) { p.onStateUpdateFromItem(convertedState); } else { @@ -432,9 +430,9 @@ private void handleEvent(String itemName, T type, @Nullable Str String acceptedItemType = channel.getAcceptedItemType(); // DecimalType command sent to a NumberItem with dimension defined: - if (originalType instanceof DecimalType && hasDimension(item, acceptedItemType)) { + if (originalType instanceof DecimalType type && hasDimension(item, acceptedItemType)) { @Nullable - QuantityType quantityType = convertToQuantityType((DecimalType) originalType, item, acceptedItemType); + QuantityType quantityType = convertToQuantityType(type, item, acceptedItemType); if (quantityType != null) { return (T) quantityType; } @@ -446,14 +444,13 @@ private void handleEvent(String itemName, T type, @Nullable Str // the ChannelType provides. // This can be removed once a suitable solution for https://github.com/eclipse/smarthome/issues/2555 (Thing // migration) is found. - if (originalType instanceof DecimalType && !hasDimension(item, acceptedItemType) + if (originalType instanceof DecimalType type && !hasDimension(item, acceptedItemType) && channelTypeDefinesDimension(channel.getChannelTypeUID())) { ChannelType channelType = channelTypeRegistry.getChannelType(channel.getChannelTypeUID()); String acceptedItemTypeFromChannelType = channelType != null ? channelType.getItemType() : null; @Nullable - QuantityType quantityType = convertToQuantityType((DecimalType) originalType, item, - acceptedItemTypeFromChannelType); + QuantityType quantityType = convertToQuantityType(type, item, acceptedItemTypeFromChannelType); if (quantityType != null) { return (T) quantityType; } @@ -474,8 +471,8 @@ && channelTypeDefinesDimension(channel.getChannelTypeUID())) { // Look for class hierarchy and convert appropriately for (Class typeClass : acceptedTypes) { if (!typeClass.isEnum() && typeClass.isAssignableFrom(originalType.getClass()) // - && State.class.isAssignableFrom(typeClass) && originalType instanceof State) { - T ret = (T) ((State) originalType).as((Class) typeClass); + && State.class.isAssignableFrom(typeClass) && originalType instanceof State state) { + T ret = (T) state.as((Class) typeClass); if (logger.isDebugEnabled()) { logger.debug("Converted '{}' ({}) to accepted type '{}' ({}) for channel '{}' ", originalType, originalType.getClass().getSimpleName(), ret, ret.getClass().getName(), @@ -500,8 +497,7 @@ private boolean channelTypeDefinesDimension(@Nullable ChannelTypeUID channelType } private boolean hasDimension(Item item, @Nullable String acceptedItemType) { - return (item instanceof NumberItem && ((NumberItem) item).getDimension() != null) - || getDimension(acceptedItemType) != null; + return (item instanceof NumberItem ni && ni.getDimension() != null) || getDimension(acceptedItemType) != null; } private @Nullable QuantityType convertToQuantityType(DecimalType originalType, Item item, @@ -555,8 +551,8 @@ private void receiveTrigger(ChannelTriggeredEvent channelTriggeredEvent) { final Thing thing = getThing(channelUID.getThingUID()); handleCallFromHandler(channelUID, thing, profile -> { - if (profile instanceof TriggerProfile) { - ((TriggerProfile) profile).onTriggerFromHandler(event); + if (profile instanceof TriggerProfile triggerProfile) { + triggerProfile.onTriggerFromHandler(event); } }); } @@ -565,8 +561,8 @@ public void stateUpdated(ChannelUID channelUID, State state) { final Thing thing = getThing(channelUID.getThingUID()); handleCallFromHandler(channelUID, thing, profile -> { - if (profile instanceof StateProfile) { - ((StateProfile) profile).onStateUpdateFromHandler(state); + if (profile instanceof StateProfile stateProfile) { + stateProfile.onStateUpdateFromHandler(state); } }); } @@ -575,8 +571,8 @@ public void postCommand(ChannelUID channelUID, Command command) { final Thing thing = getThing(channelUID.getThingUID()); handleCallFromHandler(channelUID, thing, profile -> { - if (profile instanceof StateProfile) { - ((StateProfile) profile).onCommandFromHandler(command); + if (profile instanceof StateProfile stateProfile) { + stateProfile.onCommandFromHandler(command); } }); } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingConfigDescriptionAliasProvider.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingConfigDescriptionAliasProvider.java index 7557703145f..3b140c0bec7 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingConfigDescriptionAliasProvider.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingConfigDescriptionAliasProvider.java @@ -88,10 +88,8 @@ public ThingConfigDescriptionAliasProvider(final @Reference ThingRegistry thingR if (thingType == null) { return null; } - // Get the config description URI for this thing type - URI configURI = thingType.getConfigDescriptionURI(); - return configURI; + return thingType.getConfigDescriptionURI(); } private @Nullable URI getChannelConfigDescriptionURI(URI uri) { @@ -117,9 +115,7 @@ public ThingConfigDescriptionAliasProvider(final @Reference ThingRegistry thingR if (channelType == null) { return null; } - // Get the config description URI for this channel type - URI configURI = channelType.getConfigDescriptionURI(); - return configURI; + return channelType.getConfigDescriptionURI(); } } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingFactoryHelper.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingFactoryHelper.java index 41a6f80fe14..eb3194fcd55 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingFactoryHelper.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingFactoryHelper.java @@ -170,11 +170,9 @@ static ChannelBuilder createChannelBuilder(ChannelUID channelUID, ChannelType ch static ChannelBuilder createChannelBuilder(ChannelUID channelUID, ChannelDefinition channelDefinition, ConfigDescriptionRegistry configDescriptionRegistry) { - ChannelType channelType = withChannelTypeRegistry(channelTypeRegistry -> { - return (channelTypeRegistry != null) - ? channelTypeRegistry.getChannelType(channelDefinition.getChannelTypeUID()) - : null; - }); + ChannelType channelType = withChannelTypeRegistry(channelTypeRegistry -> (channelTypeRegistry != null) + ? channelTypeRegistry.getChannelType(channelDefinition.getChannelTypeUID()) + : null); if (channelType == null) { logger.warn("Could not create channel '{}', because channel type '{}' could not be found.", channelDefinition.getId(), channelDefinition.getChannelTypeUID()); diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingManagerImpl.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingManagerImpl.java index ec0a6c002b7..32a6b2a93b0 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingManagerImpl.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingManagerImpl.java @@ -725,7 +725,7 @@ private boolean isHandlerRegistered(Thing thing) { return null; } Thing bridge = thingRegistry.get(bridgeUID); - return bridge instanceof Bridge ? (Bridge) bridge : null; + return bridge instanceof Bridge b ? b : null; } private void unregisterHandler(Thing thing, ThingHandlerFactory thingHandlerFactory) { diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingRegistryImpl.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingRegistryImpl.java index 120cd75af17..7416b310a4f 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingRegistryImpl.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingRegistryImpl.java @@ -149,8 +149,8 @@ protected void notifyListenersAboutUpdatedElement(Thing oldElement, Thing elemen @Override protected void onAddElement(Thing thing) throws IllegalArgumentException { addThingToBridge(thing); - if (thing instanceof Bridge) { - addThingsToBridge((Bridge) thing); + if (thing instanceof Bridge bridge) { + addThingsToBridge(bridge); } } @@ -161,8 +161,8 @@ protected void onRemoveElement(Thing thing) { ThingUID bridgeUID = thing.getBridgeUID(); if (bridgeUID != null) { Thing bridge = this.get(bridgeUID); - if (bridge instanceof BridgeImpl) { - ((BridgeImpl) bridge).removeThing(thing); + if (bridge instanceof BridgeImpl impl) { + impl.removeThing(thing); } } } @@ -187,8 +187,8 @@ private void addThingsToBridge(Bridge bridge) { forEach(thing -> { ThingUID bridgeUID = thing.getBridgeUID(); if (bridgeUID != null && bridgeUID.equals(bridge.getUID())) { - if (bridge instanceof BridgeImpl && !bridge.getThings().contains(thing)) { - ((BridgeImpl) bridge).addThing(thing); + if (bridge instanceof BridgeImpl impl && !bridge.getThings().contains(thing)) { + impl.addThing(thing); } } }); @@ -198,8 +198,8 @@ private void addThingToBridge(Thing thing) { ThingUID bridgeUID = thing.getBridgeUID(); if (bridgeUID != null) { Thing bridge = this.get(bridgeUID); - if (bridge instanceof BridgeImpl && !((Bridge) bridge).getThings().contains(thing)) { - ((BridgeImpl) bridge).addThing(thing); + if (bridge instanceof BridgeImpl impl && !impl.getThings().contains(thing)) { + impl.addThing(thing); } } } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/console/LinkConsoleCommandExtension.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/console/LinkConsoleCommandExtension.java index 23cdab935cf..21b2e3d4971 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/console/LinkConsoleCommandExtension.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/console/LinkConsoleCommandExtension.java @@ -77,7 +77,7 @@ public void execute(String[] args, Console console) { list(console, itemChannelLinkRegistry.getAll()); return; case SUBCMD_ORPHAN: - if (args.length == 2 && (args[1].equals("list") || args[1].equals("purge"))) { + if (args.length == 2 && ("list".equals(args[1]) || "purge".equals(args[1]))) { orphan(console, args[1], itemChannelLinkRegistry.getAll(), thingRegistry.getAll(), itemRegistry.getAll()); } else { diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/firmware/FirmwareUpdateServiceImpl.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/firmware/FirmwareUpdateServiceImpl.java index 6c6d40954d4..a52ee0b030c 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/firmware/FirmwareUpdateServiceImpl.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/firmware/FirmwareUpdateServiceImpl.java @@ -265,8 +265,7 @@ public Set getSubscribedEventTypes() { @Override public void receive(Event event) { - if (event instanceof ThingStatusInfoChangedEvent) { - ThingStatusInfoChangedEvent changedEvent = (ThingStatusInfoChangedEvent) event; + if (event instanceof ThingStatusInfoChangedEvent changedEvent) { if (changedEvent.getStatusInfo().getStatus() != ThingStatus.ONLINE) { return; } @@ -327,11 +326,10 @@ private synchronized void processFirmwareStatusInfo(FirmwareUpdateHandler firmwa eventPublisher.post(FirmwareEventFactory.createFirmwareStatusInfoEvent(newFirmwareStatusInfo)); if (newFirmwareStatusInfo.getFirmwareStatus() == FirmwareStatus.UPDATE_AVAILABLE - && firmwareUpdateHandler instanceof FirmwareUpdateBackgroundTransferHandler + && firmwareUpdateHandler instanceof FirmwareUpdateBackgroundTransferHandler handler && !firmwareUpdateHandler.isUpdateExecutable()) { if (latestFirmware != null) { - transferLatestFirmware((FirmwareUpdateBackgroundTransferHandler) firmwareUpdateHandler, - latestFirmware, previousFirmwareStatusInfo); + transferLatestFirmware(handler, latestFirmware, previousFirmwareStatusInfo); } } } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/firmware/ProgressCallbackImpl.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/firmware/ProgressCallbackImpl.java index 5ca8f5b3de1..28db0c576da 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/firmware/ProgressCallbackImpl.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/firmware/ProgressCallbackImpl.java @@ -190,8 +190,7 @@ void failedInternal(String errorMessageKey) { private String getMessage(Class clazz, String errorMessageKey, Object... arguments) { Bundle bundle = bundleResolver.resolveBundle(clazz); - String errorMessage = i18nProvider.getText(bundle, errorMessageKey, null, locale, arguments); - return errorMessage; + return i18nProvider.getText(bundle, errorMessageKey, null, locale, arguments); } private void postResultInfoEvent(FirmwareUpdateResult result, String message) { diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/link/ItemChannelLinkConfigDescriptionProvider.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/link/ItemChannelLinkConfigDescriptionProvider.java index bc0f4fd4ce0..4261f215504 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/link/ItemChannelLinkConfigDescriptionProvider.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/link/ItemChannelLinkConfigDescriptionProvider.java @@ -111,8 +111,8 @@ private List getOptions(ItemChannelLink link, Item item, Channe case STATE: return profileType instanceof StateProfileType && isSupportedItemType(profileType, item); case TRIGGER: - return profileType instanceof TriggerProfileType && isSupportedItemType(profileType, item) - && isSupportedChannelType((TriggerProfileType) profileType, channel); + return profileType instanceof TriggerProfileType tpt && isSupportedItemType(profileType, item) + && isSupportedChannelType(tpt, channel); default: throw new IllegalArgumentException("Unknown channel kind: " + channel.getKind()); } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemHysteresisStateProfile.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemHysteresisStateProfile.java index 83852688947..1213fad1c44 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemHysteresisStateProfile.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemHysteresisStateProfile.java @@ -84,14 +84,13 @@ public SystemHysteresisStateProfile(ProfileCallback callback, ProfileContext con private @Nullable QuantityType getParam(ProfileContext context, String param) { final Object paramValue = context.getConfiguration().get(param); logger.debug("Configuring profile with {} parameter '{}'", param, paramValue); - if (paramValue instanceof String) { + if (paramValue instanceof String string) { try { - return new QuantityType<>((String) paramValue); + return new QuantityType<>(string); } catch (IllegalArgumentException e) { logger.error("Cannot convert value '{}' of parameter {} into a valid QuantityType.", paramValue, param); } - } else if (paramValue instanceof BigDecimal) { - final BigDecimal value = (BigDecimal) paramValue; + } else if (paramValue instanceof BigDecimal value) { return QuantityType.valueOf(value.doubleValue(), AbstractUnit.ONE); } return null; @@ -111,8 +110,8 @@ public void onStateUpdateFromItem(State state) { public void onCommandFromHandler(Command command) { final Type mappedCommand = mapValue(command); logger.trace("Mapped command from '{}' to command '{}'.", command, mappedCommand); - if (mappedCommand instanceof Command) { - callback.sendCommand((Command) mappedCommand); + if (mappedCommand instanceof Command command1) { + callback.sendCommand(command1); } } @@ -125,14 +124,13 @@ public void onCommandFromItem(Command command) { public void onStateUpdateFromHandler(State state) { final Type mappedState = mapValue(state); logger.trace("Mapped state from '{}' to state '{}'.", state, mappedState); - if (mappedState instanceof State) { - callback.sendUpdate((State) mappedState); + if (mappedState instanceof State state1) { + callback.sendUpdate(state1); } } private Type mapValue(Type value) { - if (value instanceof QuantityType) { - final QuantityType qtState = (QuantityType) value; + if (value instanceof QuantityType qtState) { final QuantityType finalLower; final QuantityType finalUpper; if (Units.ONE.equals(lower.getUnit()) && Units.ONE.equals(upper.getUnit())) { @@ -156,9 +154,8 @@ private Type mapValue(Type value) { } } return previousType = mapValue(finalLower.doubleValue(), finalUpper.doubleValue(), qtState.doubleValue()); - } else if (value instanceof DecimalType) { - return previousType = mapValue(lower.doubleValue(), upper.doubleValue(), - ((DecimalType) value).doubleValue()); + } else if (value instanceof DecimalType type) { + return previousType = mapValue(lower.doubleValue(), upper.doubleValue(), type.doubleValue()); } return previousType; } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemOffsetProfile.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemOffsetProfile.java index 364b0b4d2ea..bf92f3cb083 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemOffsetProfile.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemOffsetProfile.java @@ -55,16 +55,15 @@ public SystemOffsetProfile(ProfileCallback callback, ProfileContext context) { Object paramValue = context.getConfiguration().get(OFFSET_PARAM); logger.debug("Configuring profile with {} parameter '{}'", OFFSET_PARAM, paramValue); - if (paramValue instanceof String) { + if (paramValue instanceof String string) { try { - offset = new QuantityType<>((String) paramValue); + offset = new QuantityType<>(string); } catch (IllegalArgumentException e) { logger.error( "Cannot convert value '{}' of parameter '{}' into a valid offset of type QuantityType. Using offset 0 now.", paramValue, OFFSET_PARAM); } - } else if (paramValue instanceof BigDecimal) { - BigDecimal bd = (BigDecimal) paramValue; + } else if (paramValue instanceof BigDecimal bd) { offset = new QuantityType<>(bd.toString()); } else { logger.error( @@ -106,8 +105,7 @@ private Type applyOffset(Type state, boolean towardsItem) { QuantityType finalOffset = towardsItem ? offset : offset.negate(); Type result = UnDefType.UNDEF; - if (state instanceof QuantityType) { - QuantityType qtState = (QuantityType) state; + if (state instanceof QuantityType qtState) { try { if (Units.ONE.equals(finalOffset.getUnit()) && !Units.ONE.equals(qtState.getUnit())) { // allow offsets without unit -> implicitly assume its the same as the one from the state, but warn @@ -129,8 +127,7 @@ private Type applyOffset(Type state, boolean towardsItem) { } catch (UnconvertibleException e) { logger.warn("Cannot apply offset '{}' to state '{}' because types do not match.", finalOffset, qtState); } - } else if (state instanceof DecimalType && Units.ONE.equals(finalOffset.getUnit())) { - DecimalType decState = (DecimalType) state; + } else if (state instanceof DecimalType decState && Units.ONE.equals(finalOffset.getUnit())) { result = new DecimalType(decState.toBigDecimal().add(finalOffset.toBigDecimal())); } else { logger.warn( diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemRangeStateProfile.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemRangeStateProfile.java index 8bed81cb0fb..5de09b33d59 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemRangeStateProfile.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemRangeStateProfile.java @@ -91,14 +91,13 @@ public SystemRangeStateProfile(ProfileCallback callback, ProfileContext context) private @Nullable QuantityType getParam(ProfileContext context, String param) { final Object paramValue = context.getConfiguration().get(param); logger.debug("Configuring profile with {} parameter '{}'", param, paramValue); - if (paramValue instanceof String) { + if (paramValue instanceof String string) { try { - return new QuantityType<>((String) paramValue); + return new QuantityType<>(string); } catch (IllegalArgumentException e) { logger.error("Cannot convert value '{}' of parameter {} into a valid QuantityType.", paramValue, param); } - } else if (paramValue instanceof BigDecimal) { - final BigDecimal value = (BigDecimal) paramValue; + } else if (paramValue instanceof BigDecimal value) { return QuantityType.valueOf(value.doubleValue(), AbstractUnit.ONE); } return null; @@ -118,8 +117,8 @@ public void onStateUpdateFromItem(State state) { public void onCommandFromHandler(Command command) { final Type mappedCommand = mapValue(command); logger.trace("Mapped command from '{}' to command '{}'.", command, mappedCommand); - if (mappedCommand instanceof Command) { - callback.sendCommand((Command) mappedCommand); + if (mappedCommand instanceof Command command1) { + callback.sendCommand(command1); } } @@ -132,14 +131,13 @@ public void onCommandFromItem(Command command) { public void onStateUpdateFromHandler(State state) { final Type mappedState = mapValue(state); logger.trace("Mapped state from '{}' to state '{}'.", state, mappedState); - if (mappedState instanceof State) { - callback.sendUpdate((State) mappedState); + if (mappedState instanceof State state1) { + callback.sendUpdate(state1); } } private Type mapValue(Type value) { - if (value instanceof QuantityType) { - final QuantityType qtState = (QuantityType) value; + if (value instanceof QuantityType qtState) { final QuantityType finalLower; final QuantityType finalUpper; if (Units.ONE.equals(lower.getUnit()) && Units.ONE.equals(upper.getUnit())) { @@ -163,9 +161,8 @@ private Type mapValue(Type value) { } } return previousType = mapValue(finalLower.doubleValue(), finalUpper.doubleValue(), qtState.doubleValue()); - } else if (value instanceof DecimalType) { - return previousType = mapValue(lower.doubleValue(), upper.doubleValue(), - ((DecimalType) value).doubleValue()); + } else if (value instanceof DecimalType type) { + return previousType = mapValue(lower.doubleValue(), upper.doubleValue(), type.doubleValue()); } return previousType; } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/TimestampOffsetProfile.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/TimestampOffsetProfile.java index c62421e884a..58a808d2347 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/TimestampOffsetProfile.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/TimestampOffsetProfile.java @@ -58,11 +58,9 @@ public TimestampOffsetProfile(ProfileCallback callback, ProfileContext context) Object offsetParam = context.getConfiguration().get(OFFSET_PARAM); logger.debug("Configuring profile with {} parameter '{}'", OFFSET_PARAM, offsetParam); - if (offsetParam instanceof Number) { - Number bd = (Number) offsetParam; + if (offsetParam instanceof Number bd) { offset = Duration.ofSeconds(bd.longValue()); - } else if (offsetParam instanceof String) { - String s = (String) offsetParam; + } else if (offsetParam instanceof String s) { offset = Duration.ofSeconds(Long.valueOf(s)); } else { logger.error( @@ -121,8 +119,8 @@ private Type applyOffsetAndTimezone(Type type, boolean towardsItem) { Duration finalOffset = towardsItem ? offset : offset.negated(); Type result = UnDefType.UNDEF; - if (type instanceof DateTimeType) { - ZonedDateTime zdt = ((DateTimeType) type).getZonedDateTime(); + if (type instanceof DateTimeType timeType) { + ZonedDateTime zdt = timeType.getZonedDateTime(); // apply offset if (!Duration.ZERO.equals(offset)) { diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/link/AbstractLink.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/link/AbstractLink.java index 70b0fd5919b..952a30edb07 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/link/AbstractLink.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/link/AbstractLink.java @@ -56,8 +56,7 @@ public AbstractLink(String itemName) { @Override public boolean equals(@Nullable Object obj) { - if (obj instanceof AbstractLink) { - AbstractLink link = (AbstractLink) obj; + if (obj instanceof AbstractLink link) { return getUID().equals(link.getUID()); } return false; diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/link/ItemChannelLink.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/link/ItemChannelLink.java index badcdc9cc9a..fb6ee1d2eaa 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/link/ItemChannelLink.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/link/ItemChannelLink.java @@ -62,8 +62,7 @@ public Configuration getConfiguration() { @Override public boolean equals(@Nullable Object obj) { - if (obj instanceof ItemChannelLink) { - ItemChannelLink link = (ItemChannelLink) obj; + if (obj instanceof ItemChannelLink link) { return super.equals(obj) && configuration.equals(link.getConfiguration()); } return false; diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/profiles/i18n/ProfileTypeI18nLocalizationService.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/profiles/i18n/ProfileTypeI18nLocalizationService.java index 74cefbe9b9b..76e8a15ea8a 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/profiles/i18n/ProfileTypeI18nLocalizationService.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/profiles/i18n/ProfileTypeI18nLocalizationService.java @@ -49,16 +49,14 @@ public ProfileType createLocalizedProfileType(Bundle bundle, ProfileType profile String defaultLabel = profileType.getLabel(); String label = profileI18nUtil.getProfileLabel(bundle, profileTypeUID, defaultLabel, locale); - if (profileType instanceof StateProfileType) { + if (profileType instanceof StateProfileType type) { return ProfileTypeBuilder.newState(profileTypeUID, label != null ? label : defaultLabel) .withSupportedItemTypes(profileType.getSupportedItemTypes()) - .withSupportedItemTypesOfChannel(((StateProfileType) profileType).getSupportedItemTypesOfChannel()) - .build(); - } else if (profileType instanceof TriggerProfileType) { + .withSupportedItemTypesOfChannel(type.getSupportedItemTypesOfChannel()).build(); + } else if (profileType instanceof TriggerProfileType type) { return ProfileTypeBuilder.newTrigger(profileTypeUID, label != null ? label : defaultLabel) .withSupportedItemTypes(profileType.getSupportedItemTypes()) - .withSupportedChannelTypeUIDs(((TriggerProfileType) profileType).getSupportedChannelTypeUIDs()) - .build(); + .withSupportedChannelTypeUIDs(type.getSupportedChannelTypeUIDs()).build(); } else { return profileType; } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/util/ThingHelper.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/util/ThingHelper.java index c43b7d4de7f..16a90ebeaf7 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/util/ThingHelper.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/util/ThingHelper.java @@ -211,9 +211,7 @@ public static Thing merge(Thing thing, ThingDTO updatedContents) { Thing mergedThing = builder.build(); // keep all child things in place on a merged bridge - if (mergedThing instanceof BridgeImpl && thing instanceof Bridge) { - Bridge bridge = (Bridge) thing; - BridgeImpl mergedBridge = (BridgeImpl) mergedThing; + if (mergedThing instanceof BridgeImpl mergedBridge && thing instanceof Bridge bridge) { for (Thing child : bridge.getThings()) { mergedBridge.addThing(child); } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/xml/internal/AbstractDescriptionTypeConverter.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/xml/internal/AbstractDescriptionTypeConverter.java index 56d794c1131..323b8d2b4c2 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/xml/internal/AbstractDescriptionTypeConverter.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/xml/internal/AbstractDescriptionTypeConverter.java @@ -125,8 +125,8 @@ protected String readLabel(NodeIterator nodeIterator) throws ConversionException Object nextNode = nodeIterator.next(); if (nextNode != null) { - if (nextNode instanceof ConfigDescription) { - return (ConfigDescription) nextNode; + if (nextNode instanceof ConfigDescription description) { + return description; } nodeIterator.revert(); diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/xml/internal/ChannelTypeConverter.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/xml/internal/ChannelTypeConverter.java index 03e37f67eb4..d6a7b404bd7 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/xml/internal/ChannelTypeConverter.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/xml/internal/ChannelTypeConverter.java @@ -123,8 +123,8 @@ private boolean readBoolean(Map attributes, String attributeName Object nextNode = nodeIterator.next(); if (nextNode != null) { - if (nextNode instanceof StateDescription) { - return (StateDescription) nextNode; + if (nextNode instanceof StateDescription description) { + return description; } nodeIterator.revert(); @@ -137,8 +137,8 @@ private boolean readBoolean(Map attributes, String attributeName Object nextNode = nodeIterator.next(); if (nextNode != null) { - if (nextNode instanceof EventDescription) { - return (EventDescription) nextNode; + if (nextNode instanceof EventDescription description) { + return description; } nodeIterator.revert(); @@ -151,8 +151,8 @@ private boolean readBoolean(Map attributes, String attributeName Object nextNode = nodeIterator.next(); if (nextNode != null) { - if (nextNode instanceof CommandDescription) { - return (CommandDescription) nextNode; + if (nextNode instanceof CommandDescription description) { + return description; } nodeIterator.revert(); diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/xml/internal/ThingTypeXmlProvider.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/xml/internal/ThingTypeXmlProvider.java index c8fc4d64d41..fd74c108928 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/xml/internal/ThingTypeXmlProvider.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/xml/internal/ThingTypeXmlProvider.java @@ -83,15 +83,12 @@ public ThingTypeXmlProvider(Bundle bundle, AbstractXmlConfigDescriptionProvider @Override public synchronized void addingObject(List types) { for (Object type : types) { - if (type instanceof ThingTypeXmlResult) { - ThingTypeXmlResult typeResult = (ThingTypeXmlResult) type; + if (type instanceof ThingTypeXmlResult typeResult) { addConfigDescription(typeResult.getConfigDescription()); thingTypeRefs.add(typeResult); - } else if (type instanceof ChannelGroupTypeXmlResult) { - ChannelGroupTypeXmlResult typeResult = (ChannelGroupTypeXmlResult) type; + } else if (type instanceof ChannelGroupTypeXmlResult typeResult) { channelGroupTypeRefs.add(typeResult); - } else if (type instanceof ChannelTypeXmlResult) { - ChannelTypeXmlResult typeResult = (ChannelTypeXmlResult) type; + } else if (type instanceof ChannelTypeXmlResult typeResult) { channelTypeRefs.add(typeResult); addConfigDescription(typeResult.getConfigDescription()); } else { diff --git a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/profiles/SystemHysteresisStateProfileTest.java b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/profiles/SystemHysteresisStateProfileTest.java index 21f3c796c3a..c5e33e9ab84 100644 --- a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/profiles/SystemHysteresisStateProfileTest.java +++ b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/profiles/SystemHysteresisStateProfileTest.java @@ -77,7 +77,7 @@ public ParameterSet(List sources, List results, this.commands = (List) sources; this.resultingCommands = new ArrayList<>(results.size()); results.forEach(result -> { - resultingCommands.add(result instanceof Command ? (Command) result : null); + resultingCommands.add(result instanceof Command c ? c : null); }); this.lower = lower; this.upper = upper; diff --git a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/profiles/SystemRangeStateProfileTest.java b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/profiles/SystemRangeStateProfileTest.java index 564d5d84aa3..d211ae82281 100644 --- a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/profiles/SystemRangeStateProfileTest.java +++ b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/profiles/SystemRangeStateProfileTest.java @@ -77,7 +77,7 @@ public ParameterSet(List sources, List results, this.commands = (List) sources; this.resultingCommands = new ArrayList<>(results.size()); results.forEach(result -> { - resultingCommands.add(result instanceof Command ? (Command) result : null); + resultingCommands.add(result instanceof Command c ? c : null); }); this.lower = lower; this.upper = upper; diff --git a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/profiles/ToggleProfileTest.java b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/profiles/ToggleProfileTest.java index 3dae3df0b84..0ace19f08c0 100644 --- a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/profiles/ToggleProfileTest.java +++ b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/profiles/ToggleProfileTest.java @@ -153,7 +153,7 @@ public void testWrongUserConfiguredEvent() { private void initializeContextMock(@Nullable String triggerEvent) { Map params = triggerEvent == null ? Collections.emptyMap() - : Collections.singletonMap(ToggleProfile.EVENT_PARAM, triggerEvent); + : Map.of(ToggleProfile.EVENT_PARAM, triggerEvent); when(contextMock.getConfiguration()).thenReturn(new Configuration(params)); } diff --git a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/xml/internal/ThingDescriptionReaderTest.java b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/xml/internal/ThingDescriptionReaderTest.java index 492d94852aa..2efcae6af11 100644 --- a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/xml/internal/ThingDescriptionReaderTest.java +++ b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/xml/internal/ThingDescriptionReaderTest.java @@ -47,12 +47,12 @@ public void readFromXML() throws Exception { List channelTypeXmlResults = new ArrayList<>(); for (Object type : types) { - if (type instanceof ThingTypeXmlResult) { - thingTypeXmlResults.add((ThingTypeXmlResult) type); - } else if (type instanceof ChannelGroupTypeXmlResult) { - channelGroupTypeXmlResults.add((ChannelGroupTypeXmlResult) type); - } else if (type instanceof ChannelTypeXmlResult) { - channelTypeXmlResults.add((ChannelTypeXmlResult) type); + if (type instanceof ThingTypeXmlResult result) { + thingTypeXmlResults.add(result); + } else if (type instanceof ChannelGroupTypeXmlResult result) { + channelGroupTypeXmlResults.add(result); + } else if (type instanceof ChannelTypeXmlResult result) { + channelTypeXmlResults.add(result); } } diff --git a/bundles/org.openhab.core.ui.icon/src/main/java/org/openhab/core/ui/icon/internal/IconServlet.java b/bundles/org.openhab.core.ui.icon/src/main/java/org/openhab/core/ui/icon/internal/IconServlet.java index 058296c0dbe..a03a77f9f8a 100644 --- a/bundles/org.openhab.core.ui.icon/src/main/java/org/openhab/core/ui/icon/internal/IconServlet.java +++ b/bundles/org.openhab.core.ui.icon/src/main/java/org/openhab/core/ui/icon/internal/IconServlet.java @@ -87,8 +87,8 @@ protected void activate(Map config) { @Modified protected void modified(Map config) { Object iconSetId = config.get("default"); - if (iconSetId instanceof String) { - defaultIconSetId = (String) iconSetId; + if (iconSetId instanceof String string) { + defaultIconSetId = string; } } diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/chart/ChartProvider.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/chart/ChartProvider.java index e9ab7768c2c..fd6d8cf3e21 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/chart/ChartProvider.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/chart/ChartProvider.java @@ -109,6 +109,6 @@ default BufferedImage createChart(@Nullable String service, @Nullable String the public enum ImageType { png, jpg, - gif; + gif } } diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/DefaultChartProvider.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/DefaultChartProvider.java index b0090bbe8ad..1c8304a336e 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/DefaultChartProvider.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/DefaultChartProvider.java @@ -150,8 +150,7 @@ public BufferedImage createChart(@Nullable String serviceId, @Nullable String th : persistenceServiceRegistry.get(serviceId); // Did we find a service? - QueryablePersistenceService persistenceService = (service instanceof QueryablePersistenceService) - ? (QueryablePersistenceService) service + QueryablePersistenceService persistenceService = (service instanceof QueryablePersistenceService qps) ? qps : (QueryablePersistenceService) persistenceServiceRegistry.getAll() // .stream() // .filter(it -> it instanceof QueryablePersistenceService) // @@ -231,8 +230,7 @@ public BufferedImage createChart(@Nullable String serviceId, @Nullable String th String[] groupNames = groups.split(","); for (String groupName : groupNames) { Item item = itemUIRegistry.getItem(groupName); - if (item instanceof GroupItem) { - GroupItem groupItem = (GroupItem) item; + if (item instanceof GroupItem groupItem) { for (Item member : groupItem.getMembers()) { if (addItem(chart, persistenceService, startTime, endTime, member, seriesCounter, chartTheme, dpi, legendPositionDecider)) { @@ -293,10 +291,10 @@ public BufferedImage createChart(@Nullable String serviceId, @Nullable String th } private double convertData(State state) { - if (state instanceof DecimalType) { - return ((DecimalType) state).doubleValue(); - } else if (state instanceof QuantityType) { - return ((QuantityType) state).doubleValue(); + if (state instanceof DecimalType type) { + return type.doubleValue(); + } else if (state instanceof QuantityType type) { + return type.doubleValue(); } else if (state instanceof OnOffType) { return state == OnOffType.OFF ? 0 : 1; } else if (state instanceof OpenClosedType) { diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentSitemapProvider.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentSitemapProvider.java index e274c23bbce..a9bee32fd9c 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentSitemapProvider.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentSitemapProvider.java @@ -269,8 +269,7 @@ protected Sitemap buildSitemap(RootUIComponent rootComponent) { setWidgetPropertyFromComponentConfig(widget, component, "icon", SitemapPackage.WIDGET__ICON); setWidgetPropertyFromComponentConfig(widget, component, "item", SitemapPackage.WIDGET__ITEM); - if (widget instanceof LinkableWidget) { - LinkableWidget linkableWidget = (LinkableWidget) widget; + if (widget instanceof LinkableWidget linkableWidget) { if (component.getSlots() != null && component.getSlots().containsKey("widgets")) { for (UIComponent childComponent : component.getSlot("widgets")) { Widget childWidget = buildWidget(childComponent); @@ -303,7 +302,7 @@ private void setWidgetPropertyFromComponentConfig(Widget widget, @Nullable UICom WidgetImpl widgetImpl = (WidgetImpl) widget; Object normalizedValue = ConfigUtil.normalizeType(value); if (widgetImpl.eGet(feature, false, false) instanceof Integer) { - normalizedValue = (normalizedValue instanceof BigDecimal) ? ((BigDecimal) normalizedValue).intValue() + normalizedValue = (normalizedValue instanceof BigDecimal bd) ? bd.intValue() : Integer.valueOf(normalizedValue.toString()); } else if (widgetImpl.eGet(feature, false, false) instanceof Boolean && !(normalizedValue instanceof Boolean)) { diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java index 672408e4fef..4be4185cddc 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java @@ -367,7 +367,7 @@ private Switch createPlayerButtons() { // for fraction digits in state we don't want to risk format exceptions, // so treat everything as floats: - formatPattern = formatPattern.replaceAll("%d", "%.0f"); + formatPattern = formatPattern.replace("%d", "%.0f"); } } } catch (ItemNotFoundException e) { @@ -410,8 +410,7 @@ private Switch createPlayerButtons() { if (formatPattern.contains(UnitUtils.UNIT_PLACEHOLDER)) { formatPattern = formatPattern.replaceAll(UnitUtils.UNIT_PLACEHOLDER, "").stripTrailing(); } - } else if (state instanceof QuantityType) { - QuantityType quantityState = (QuantityType) state; + } else if (state instanceof QuantityType quantityState) { // sanity convert current state to the item state description unit in case it was updated in the // meantime. The item state is still in the "original" unit while the state description will // display the new unit: @@ -425,10 +424,10 @@ private Switch createPlayerButtons() { quantityState = convertStateToWidgetUnit(quantityState, w); state = quantityState; } - } else if (state instanceof DateTimeType) { + } else if (state instanceof DateTimeType type) { // Translate a DateTimeType state to the local time zone try { - state = ((DateTimeType) state).toLocaleZone(); + state = type.toLocaleZone(); } catch (DateTimeException ignored) { } } @@ -628,8 +627,8 @@ private String transform(String label, boolean matchTransform, @Nullable String State returnState = null; State itemState = i.getState(); - if (itemState instanceof QuantityType) { - itemState = convertStateToWidgetUnit((QuantityType) itemState, w); + if (itemState instanceof QuantityType type) { + itemState = convertStateToWidgetUnit(type, w); } if (w instanceof Switch && i instanceof RollershutterItem) { @@ -641,8 +640,7 @@ private String transform(String label, boolean matchTransform, @Nullable String } else { returnState = itemState.as(DecimalType.class); } - } else if (w instanceof Switch) { - Switch sw = (Switch) w; + } else if (w instanceof Switch sw) { if (sw.getMappings().isEmpty()) { returnState = itemState.as(OnOffType.class); } @@ -698,8 +696,8 @@ public EList getChildren(Sitemap sitemap) { @Override public EList getChildren(LinkableWidget w) { EList widgets; - if (w instanceof Group && w.getChildren().isEmpty()) { - widgets = getDynamicGroupChildren((Group) w); + if (w instanceof Group group && w.getChildren().isEmpty()) { + widgets = getDynamicGroupChildren(group); } else { widgets = w.getChildren(); } @@ -761,8 +759,7 @@ private EList getDynamicGroupChildren(Group group) { try { if (itemName != null) { Item item = getItem(itemName); - if (item instanceof GroupItem) { - GroupItem groupItem = (GroupItem) item; + if (item instanceof GroupItem groupItem) { List members = new ArrayList<>(groupItem.getMembers()); switch (groupMembersSorting) { case "LABEL": @@ -991,8 +988,8 @@ private boolean matchStateToValue(State state, String value, @Nullable String ma try { double compareDoubleValue = Double.parseDouble(unquotedValue); double stateDoubleValue; - if (state instanceof DecimalType) { - stateDoubleValue = ((DecimalType) state).doubleValue(); + if (state instanceof DecimalType type) { + stateDoubleValue = type.doubleValue(); } else { stateDoubleValue = ((QuantityType) state).doubleValue(); } @@ -1032,8 +1029,8 @@ private boolean matchStateToValue(State state, String value, @Nullable String ma } catch (NumberFormatException e) { logger.debug("matchStateToValue: Decimal format exception: ", e); } - } else if (state instanceof DateTimeType) { - ZonedDateTime val = ((DateTimeType) state).getZonedDateTime(); + } else if (state instanceof DateTimeType type) { + ZonedDateTime val = type.getZonedDateTime(); ZonedDateTime now = ZonedDateTime.now(); long secsDif = ChronoUnit.SECONDS.between(val, now); @@ -1327,10 +1324,10 @@ public void removeRegistryHook(RegistryHook hook) { Item item = getItem(w.getItem()); // we require the item to define a dimension, otherwise no unit will be reported to the UIs. - if (item instanceof NumberItem && ((NumberItem) item).getDimension() != null) { + if (item instanceof NumberItem numberItem && numberItem.getDimension() != null) { if (w.getLabel() == null) { // if no Label was assigned to the Widget we fallback to the items unit - return ((NumberItem) item).getUnitSymbol(); + return numberItem.getUnitSymbol(); } String unit = getUnitFromLabel(w.getLabel()); @@ -1338,7 +1335,7 @@ public void removeRegistryHook(RegistryHook hook) { return unit; } - return ((NumberItem) item).getUnitSymbol(); + return numberItem.getUnitSymbol(); } } catch (ItemNotFoundException e) { logger.debug("Failed to retrieve item during widget rendering: {}", e.getMessage()); diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/proxy/ProxyServletService.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/proxy/ProxyServletService.java index 9cd464dba0a..e1fbc1d2952 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/proxy/ProxyServletService.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/proxy/ProxyServletService.java @@ -243,10 +243,10 @@ URI uriFromRequest(HttpServletRequest request) { } String uriString = null; - if (widget instanceof Image) { - uriString = ((Image) widget).getUrl(); - } else if (widget instanceof Video) { - uriString = ((Video) widget).getUrl(); + if (widget instanceof Image image) { + uriString = image.getUrl(); + } else if (widget instanceof Video video) { + uriString = video.getUrl(); } else { throw new ProxyServletException(HttpServletResponse.SC_FORBIDDEN, String.format("Widget type '%s' is not supported!", widget.getClass().getName())); diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/DialogProcessor.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/DialogProcessor.java index 7fd5a8bfa24..2760edbd98e 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/DialogProcessor.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/DialogProcessor.java @@ -294,9 +294,8 @@ public void ksEventReceived(KSEvent ksEvent) { } catch (IllegalStateException e) { logger.warn("{}", e.getMessage()); } - } else if (ksEvent instanceof KSErrorEvent) { + } else if (ksEvent instanceof KSErrorEvent kse) { logger.debug("KSErrorEvent event received"); - KSErrorEvent kse = (KSErrorEvent) ksEvent; String text = i18nProvider.getText(bundle, "error.ks-error", null, dialogContext.locale()); say(text == null ? kse.getMessage() : text.replace("{0}", kse.getMessage())); } @@ -305,10 +304,9 @@ public void ksEventReceived(KSEvent ksEvent) { @Override public synchronized void sttEventReceived(STTEvent sttEvent) { - if (sttEvent instanceof SpeechRecognitionEvent) { + if (sttEvent instanceof SpeechRecognitionEvent sre) { logger.debug("SpeechRecognitionEvent event received"); if (!isSTTServerAborting) { - SpeechRecognitionEvent sre = (SpeechRecognitionEvent) sttEvent; String question = sre.getTranscript(); logger.debug("Text recognized: {}", question); toggleProcessing(false); @@ -335,12 +333,11 @@ public synchronized void sttEventReceived(STTEvent sttEvent) { } else if (sttEvent instanceof RecognitionStopEvent) { logger.debug("RecognitionStopEvent event received"); toggleProcessing(false); - } else if (sttEvent instanceof SpeechRecognitionErrorEvent) { + } else if (sttEvent instanceof SpeechRecognitionErrorEvent sre) { logger.debug("SpeechRecognitionErrorEvent event received"); if (!isSTTServerAborting) { abortSTT(); toggleProcessing(false); - SpeechRecognitionErrorEvent sre = (SpeechRecognitionErrorEvent) sttEvent; String text = i18nProvider.getText(bundle, "error.stt-error", null, dialogContext.locale()); say(text == null ? sre.getMessage() : text.replace("{0}", sre.getMessage())); } diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/ASTNode.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/ASTNode.java index 38a84b16e77..fe131534cf8 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/ASTNode.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/ASTNode.java @@ -66,7 +66,7 @@ public ASTNode findNode(String name) { * @return the value of this node as {@code String[]} */ public String[] getValueAsStringArray() { - Object[] objs = value instanceof Object[] ? (Object[]) value : new Object[] { value }; + Object[] objs = value instanceof Object[] os ? os : new Object[] { value }; String[] result = new String[objs.length]; for (int i = 0; i < objs.length; i++) { result[i] = objs[i] == null ? "" : ("" + objs[i]); diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/AbstractRuleBasedInterpreter.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/AbstractRuleBasedInterpreter.java index e95881e8a00..e942a7863a2 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/AbstractRuleBasedInterpreter.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/AbstractRuleBasedInterpreter.java @@ -190,8 +190,8 @@ private void addItem(Locale locale, Map>> target, Set()); } list.add(nt); - if (item instanceof GroupItem) { - for (Item member : ((GroupItem) item).getMembers()) { + if (item instanceof GroupItem groupItem) { + for (Item member : groupItem.getMembers()) { addItem(locale, target, nt, member); } } @@ -308,10 +308,10 @@ public InterpretationResult interpretAST(ResourceBundle language, ASTNode node) Object tag = cmdNode.getTag(); Object value = cmdNode.getValue(); Command command; - if (tag instanceof Command) { - command = (Command) tag; - } else if (value instanceof Number) { - command = new DecimalType(((Number) value).longValue()); + if (tag instanceof Command command1) { + command = command1; + } else if (value instanceof Number number) { + command = new DecimalType(number.longValue()); } else { command = new StringType(cmdNode.getValueAsString()); } @@ -335,8 +335,8 @@ public InterpretationResult interpretAST(ResourceBundle language, ASTNode node) * @return resulting expression */ protected @Nullable Expression exp(@Nullable Object obj) { - if (obj instanceof Expression) { - return (Expression) obj; + if (obj instanceof Expression expression) { + return expression; } else { return obj == null ? null : new ExpressionMatch(obj.toString()); } @@ -501,9 +501,8 @@ protected String executeSingle(ResourceBundle language, String[] labelFragments, throw new InterpretationException(language.getString(MULTIPLE_OBJECTS)); } else { Item item = items.get(0); - if (command instanceof State) { + if (command instanceof State newState) { try { - State newState = (State) command; State oldState = item.getStateAs(newState.getClass()); if (newState.equals(oldState)) { String template = language.getString(STATE_ALREADY_SINGULAR); @@ -669,8 +668,7 @@ private int addExpression(Expression exp) { private int addExportedExpression(Expression exp) { shared.add(exp); exported.add(exp); - int id = addExpression(exp); - return id; + return addExpression(exp); } private Expression unwrapLet(Expression expression) { @@ -717,16 +715,16 @@ private void emitUse(Expression expression) { private void emitExpression(Expression expression) { Expression unwrappedExpression = unwrapLet(expression); - if (unwrappedExpression instanceof ExpressionMatch) { - emitMatchExpression((ExpressionMatch) unwrappedExpression); - } else if (unwrappedExpression instanceof ExpressionSequence) { - emitSequenceExpression((ExpressionSequence) unwrappedExpression); - } else if (unwrappedExpression instanceof ExpressionAlternatives) { - emitAlternativesExpression((ExpressionAlternatives) unwrappedExpression); - } else if (unwrappedExpression instanceof ExpressionCardinality) { - emitCardinalExpression((ExpressionCardinality) unwrappedExpression); - } else if (unwrappedExpression instanceof ExpressionIdentifier) { - emitItemIdentifierExpression((ExpressionIdentifier) unwrappedExpression); + if (unwrappedExpression instanceof ExpressionMatch match) { + emitMatchExpression(match); + } else if (unwrappedExpression instanceof ExpressionSequence sequence) { + emitSequenceExpression(sequence); + } else if (unwrappedExpression instanceof ExpressionAlternatives alternatives) { + emitAlternativesExpression(alternatives); + } else if (unwrappedExpression instanceof ExpressionCardinality cardinality) { + emitCardinalExpression(cardinality); + } else if (unwrappedExpression instanceof ExpressionIdentifier identifier) { + emitItemIdentifierExpression(identifier); } } @@ -816,8 +814,8 @@ String getGrammar() { addExportedExpression(e); } for (Expression e : ids.keySet()) { - if (e instanceof ExpressionIdentifier) { - Expression stopper = ((ExpressionIdentifier) e).getStopper(); + if (e instanceof ExpressionIdentifier identifier) { + Expression stopper = identifier.getStopper(); if (stopper != null) { identifierExcludes.addAll(stopper.getFirsts(language)); } diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/ExpressionAlternatives.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/ExpressionAlternatives.java index 5b0cd5521fd..72f3a02d61f 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/ExpressionAlternatives.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/ExpressionAlternatives.java @@ -13,7 +13,6 @@ package org.openhab.core.voice.text; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.ResourceBundle; import java.util.Set; @@ -34,8 +33,7 @@ final class ExpressionAlternatives extends Expression { */ public ExpressionAlternatives(Expression... subExpressions) { super(); - this.subExpressions = Collections - .unmodifiableList(Arrays.asList(Arrays.copyOf(subExpressions, subExpressions.length))); + this.subExpressions = List.of(Arrays.copyOf(subExpressions, subExpressions.length)); } @Override diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/ExpressionCardinality.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/ExpressionCardinality.java index ffce6d8716a..78661fdb002 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/ExpressionCardinality.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/ExpressionCardinality.java @@ -13,8 +13,6 @@ package org.openhab.core.voice.text; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.ResourceBundle; import java.util.Set; @@ -70,7 +68,7 @@ ASTNode parse(ResourceBundle language, TokenList tokenList) { @Override List getChildExpressions() { - return Collections.unmodifiableList(Arrays.asList(subExpression)); + return List.of(subExpression); } @Override diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/ExpressionSequence.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/ExpressionSequence.java index 0f810d9d754..4ccddd94aaa 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/ExpressionSequence.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/ExpressionSequence.java @@ -13,7 +13,6 @@ package org.openhab.core.voice.text; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.ResourceBundle; import java.util.Set; @@ -34,8 +33,7 @@ public final class ExpressionSequence extends Expression { */ public ExpressionSequence(Expression... subExpressions) { super(); - this.subExpressions = Collections - .unmodifiableList(Arrays.asList(Arrays.copyOf(subExpressions, subExpressions.length))); + this.subExpressions = List.of(Arrays.copyOf(subExpressions, subExpressions.length)); } @Override diff --git a/bundles/org.openhab.core.voice/src/test/java/org/openhab/core/voice/internal/cache/TTSLRUCacheImplTest.java b/bundles/org.openhab.core.voice/src/test/java/org/openhab/core/voice/internal/cache/TTSLRUCacheImplTest.java index 3828dea964d..09c00e7b773 100644 --- a/bundles/org.openhab.core.voice/src/test/java/org/openhab/core/voice/internal/cache/TTSLRUCacheImplTest.java +++ b/bundles/org.openhab.core.voice/src/test/java/org/openhab/core/voice/internal/cache/TTSLRUCacheImplTest.java @@ -71,8 +71,7 @@ private TTSLRUCacheImpl createTTSCache(long size) throws IOException { Map config = new HashMap<>(); config.put(TTSLRUCacheImpl.CONFIG_CACHE_SIZE_TTS, size); config.put(TTSLRUCacheImpl.CONFIG_ENABLE_CACHE_TTS, true); - TTSLRUCacheImpl voiceLRUCache = new TTSLRUCacheImpl(storageService, config); - return voiceLRUCache; + return new TTSLRUCacheImpl(storageService, config); } @Test diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/ByteArrayFileCacheTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/ByteArrayFileCacheTest.java index d10eefd6130..a4e764ae3fe 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/ByteArrayFileCacheTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/ByteArrayFileCacheTest.java @@ -218,7 +218,7 @@ public void clearExpiredIfExpired() { } private static File createTempTxtFile() throws IOException { - final File file = File.createTempFile("doorbell", "txt"); + final File file = Files.createTempFile("doorbell", "txt").toFile(); file.deleteOnExit(); return file; } From cfb2fa1be822f97b61142a9810636461b6e4f352 Mon Sep 17 00:00:00 2001 From: lolodomo Date: Tue, 18 Apr 2023 10:48:39 +0200 Subject: [PATCH 046/126] Icon server: remove old style request with the state in the URL (#3548) The state is now expected to be passed only in the "state" parameter of the request. As a result, the icon name can now contain hyphen(s). Closes #3543 Signed-off-by: Laurent Garnier --- .../core/ui/icon/internal/IconServlet.java | 21 ++----------- .../AbstractResourceIconProviderTest.java | 31 +++++++++++++++++++ .../ui/icon/internal/IconServletTest.java | 17 ---------- 3 files changed, 33 insertions(+), 36 deletions(-) diff --git a/bundles/org.openhab.core.ui.icon/src/main/java/org/openhab/core/ui/icon/internal/IconServlet.java b/bundles/org.openhab.core.ui.icon/src/main/java/org/openhab/core/ui/icon/internal/IconServlet.java index a03a77f9f8a..1cf34e79965 100644 --- a/bundles/org.openhab.core.ui.icon/src/main/java/org/openhab/core/ui/icon/internal/IconServlet.java +++ b/bundles/org.openhab.core.ui.icon/src/main/java/org/openhab/core/ui/icon/internal/IconServlet.java @@ -106,7 +106,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se return; } - String state = getState(req); + String state = req.getParameter(PARAM_STATE); String iconSetId = getIconSetId(req); Format format = getFormat(req); @@ -173,8 +173,7 @@ private String substringBeforeLast(String str, String separator) { private String getCategory(HttpServletRequest req) { String category = substringAfterLast(req.getRequestURI(), "/"); - category = substringBeforeLast(category, "."); - return substringBeforeLast(category, "-"); + return substringBeforeLast(category, "."); } private Format getFormat(HttpServletRequest req) { @@ -206,22 +205,6 @@ private String getIconSetId(HttpServletRequest req) { } } - private @Nullable String getState(HttpServletRequest req) { - String state = req.getParameter(PARAM_STATE); - if (state != null) { - return state; - } else { - String filename = substringAfterLast(req.getRequestURI(), "/"); - state = substringAfterLast(filename, "-"); - state = substringBeforeLast(state, "."); - if (!state.isEmpty()) { - return state; - } else { - return null; - } - } - } - private @Nullable IconProvider getIconProvider(String category, String iconSetId, Format format) { IconProvider topProvider = null; int maxPrio = Integer.MIN_VALUE; diff --git a/bundles/org.openhab.core.ui.icon/src/test/java/org/openhab/core/ui/icon/AbstractResourceIconProviderTest.java b/bundles/org.openhab.core.ui.icon/src/test/java/org/openhab/core/ui/icon/AbstractResourceIconProviderTest.java index 132c286f23e..c86ec9284cc 100644 --- a/bundles/org.openhab.core.ui.icon/src/test/java/org/openhab/core/ui/icon/AbstractResourceIconProviderTest.java +++ b/bundles/org.openhab.core.ui.icon/src/test/java/org/openhab/core/ui/icon/AbstractResourceIconProviderTest.java @@ -57,6 +57,10 @@ public void setUp() { return new ByteArrayInputStream("x-30.png".getBytes()); case "x-y z.png": return new ByteArrayInputStream("x-y z.png".getBytes()); + case "a-bb-ccc-30.png": + return new ByteArrayInputStream("a-bb-ccc-30.png".getBytes()); + case "a-bb-ccc-y z.png": + return new ByteArrayInputStream("a-bb-ccc-y z.png".getBytes()); default: return null; } @@ -96,6 +100,7 @@ public Integer getPriority() { public void testScanningForState() throws IOException { try (InputStream is = provider.getIcon("x", "classic", "34", Format.PNG)) { assertNotNull(is); + assertThat(new String(is.readAllBytes(), StandardCharsets.UTF_8), is("x-30.png")); } try (InputStream is = provider.getIcon("x", "classic", "25", Format.PNG)) { @@ -103,6 +108,18 @@ public void testScanningForState() throws IOException { } } + @Test + public void testScanningIconWithHyphensForState() throws IOException { + try (InputStream is = provider.getIcon("a-bb-ccc", "classic", "34", Format.PNG)) { + assertNotNull(is); + assertThat(new String(is.readAllBytes(), StandardCharsets.UTF_8), is("a-bb-ccc-30.png")); + } + + try (InputStream is = provider.getIcon("a-bb-ccc", "classic", "25", Format.PNG)) { + assertNull(is); + } + } + @Test public void testWithQuantityTypeState() throws IOException { try (InputStream is = provider.getIcon("x", "classic", "34 °C", Format.PNG)) { @@ -110,10 +127,24 @@ public void testWithQuantityTypeState() throws IOException { } } + @Test + public void testIconWithHyphensWithQuantityTypeState() throws IOException { + try (InputStream is = provider.getIcon("a-bb-ccc", "classic", "34 °C", Format.PNG)) { + assertThat(new String(is.readAllBytes(), StandardCharsets.UTF_8), is("a-bb-ccc-30.png")); + } + } + @Test public void testWithStringTypeState() throws IOException { try (InputStream is = provider.getIcon("x", "classic", "y z", Format.PNG)) { assertThat(new String(is.readAllBytes(), StandardCharsets.UTF_8), is("x-y z.png")); } } + + @Test + public void testIconWithHyphensWithStringTypeState() throws IOException { + try (InputStream is = provider.getIcon("a-bb-ccc", "classic", "y z", Format.PNG)) { + assertThat(new String(is.readAllBytes(), StandardCharsets.UTF_8), is("a-bb-ccc-y z.png")); + } + } } diff --git a/bundles/org.openhab.core.ui.icon/src/test/java/org/openhab/core/ui/icon/internal/IconServletTest.java b/bundles/org.openhab.core.ui.icon/src/test/java/org/openhab/core/ui/icon/internal/IconServletTest.java index 6c80b7a94ea..84a6f711f6c 100644 --- a/bundles/org.openhab.core.ui.icon/src/test/java/org/openhab/core/ui/icon/internal/IconServletTest.java +++ b/bundles/org.openhab.core.ui.icon/src/test/java/org/openhab/core/ui/icon/internal/IconServletTest.java @@ -89,23 +89,6 @@ public void before() throws IOException { responseOutputStream.reset(); } - @Test - public void testOldUrlStyle() throws ServletException, IOException { - when(requestMock.getRequestURI()).thenReturn("/icon/y-34.png"); - - when(responseMock.getOutputStream()).thenReturn(responseOutputStream); - - when(provider1Mock.hasIcon("y", "classic", Format.PNG)).thenReturn(0); - when(provider1Mock.getIcon("y", "classic", "34", Format.PNG)) - .thenReturn(new ByteArrayInputStream("provider 1 icon: y classic 34 png".getBytes())); - - servlet.addIconProvider(provider1Mock); - servlet.doGet(requestMock, responseMock); - - assertEquals("provider 1 icon: y classic 34 png", responseOutputStream.getOutput()); - verify(responseMock, never()).sendError(anyInt()); - } - @Test public void testPriority() throws ServletException, IOException { when(requestMock.getRequestURI()).thenReturn("/icon/x"); From 3c6d855d4b18345cb80ec6b17415efcb48033461 Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Thu, 20 Apr 2023 20:22:58 +0200 Subject: [PATCH 047/126] Remove redundant public and abstract modifiers from interfaces (#3560) * Remove redundant public modifiers from interfaces * Remove redundant abstract modifiers from interfaces Signed-off-by: Wouter Born --- .../ScriptedActionHandlerFactory.java | 3 +- .../ScriptedConditionHandlerFactory.java | 3 +- .../ScriptedTriggerHandlerFactory.java | 3 +- .../simple/SimpleTriggerHandlerCallback.java | 2 +- .../openhab/core/automation/RuleRegistry.java | 6 ++-- .../automation/handler/ConditionHandler.java | 2 +- .../automation/handler/ModuleHandler.java | 2 +- .../handler/ModuleHandlerFactory.java | 7 ++-- .../handler/TimeBasedConditionHandler.java | 2 +- .../core/automation/parser/Parser.java | 16 ++++----- .../core/automation/template/Template.java | 12 ++++--- .../automation/template/TemplateRegistry.java | 13 +++---- .../automation/type/ModuleTypeRegistry.java | 22 ++++++------ .../internal/normalization/Normalizer.java | 2 +- .../UsbSerialDiscoveryParticipant.java | 8 +++-- .../internal/ConsoleCommandsContainer.java | 2 +- .../AuthenticationSecurityContext.java | 2 +- .../core/io/transport/mdns/MDNSClient.java | 18 +++++----- .../core/io/transport/mdns/MDNSService.java | 4 +-- .../modbus/ModbusCommunicationInterface.java | 15 ++++---- .../io/transport/modbus/ModbusManager.java | 4 +-- .../io/transport/modbus/ModbusResponse.java | 2 +- .../ModbusWriteRequestBlueprintVisitor.java | 4 +-- .../modbus/endpoint/ModbusSlaveEndpoint.java | 2 +- .../io/transport/mqtt/MqttActionCallback.java | 4 +-- .../mqtt/MqttConnectionObserver.java | 2 +- .../transport/mqtt/MqttMessageSubscriber.java | 2 +- .../transport/serial/SerialPortProvider.java | 7 ++-- .../io/transport/upnp/UpnpIOParticipant.java | 8 ++--- .../core/io/transport/upnp/UpnpIOService.java | 18 +++++----- .../core/ModelRepositoryChangeListener.java | 2 +- .../core/model/script/engine/Script.java | 6 ++-- .../model/script/engine/ScriptEngine.java | 6 ++-- .../org/openhab/core/semantics/Location.java | 2 +- .../org/openhab/core/thing/ThingManager.java | 4 +-- .../core/thing/binding/firmware/Firmware.java | 2 +- .../core/thing/internal/ThingTracker.java | 2 +- .../thing/profiles/ProfileTypeRegistry.java | 4 +-- .../openhab/core/ui/chart/ChartProvider.java | 2 +- .../defaultchartprovider/ChartTheme.java | 30 ++++++++-------- .../openhab/core/ui/items/ItemUIProvider.java | 12 ++++--- .../openhab/core/ui/tiles/TileProvider.java | 2 +- .../org/openhab/core/voice/KSListener.java | 2 +- .../org/openhab/core/voice/KSService.java | 10 +++--- .../openhab/core/voice/KSServiceHandle.java | 2 +- .../org/openhab/core/voice/STTListener.java | 2 +- .../org/openhab/core/voice/STTService.java | 12 +++---- .../openhab/core/voice/STTServiceHandle.java | 2 +- .../java/org/openhab/core/voice/Voice.java | 6 ++-- .../voice/text/HumanLanguageInterpreter.java | 4 +-- .../main/java/org/openhab/core/auth/User.java | 2 +- .../org/openhab/core/auth/UserRegistry.java | 14 ++++---- .../core/common/registry/Registry.java | 11 +++--- .../openhab/core/events/EventSubscriber.java | 2 +- .../org/openhab/core/items/ActiveItem.java | 18 +++++----- .../java/org/openhab/core/items/Item.java | 35 +++++++++++-------- .../org/openhab/core/items/ItemRegistry.java | 19 +++++----- .../openhab/core/items/MetadataRegistry.java | 4 +-- .../core/items/StateChangeListener.java | 4 +-- .../core/net/NetworkAddressService.java | 4 +-- .../org/openhab/core/scheduler/CronJob.java | 2 +- .../org/openhab/core/types/ComplexType.java | 2 +- 62 files changed, 225 insertions(+), 203 deletions(-) diff --git a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/factories/ScriptedActionHandlerFactory.java b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/factories/ScriptedActionHandlerFactory.java index 09500aca0a5..dc98181e234 100644 --- a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/factories/ScriptedActionHandlerFactory.java +++ b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/factories/ScriptedActionHandlerFactory.java @@ -24,5 +24,6 @@ */ @NonNullByDefault public interface ScriptedActionHandlerFactory extends ScriptedHandler { - public @Nullable ActionHandler get(Action action); + @Nullable + ActionHandler get(Action action); } diff --git a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/factories/ScriptedConditionHandlerFactory.java b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/factories/ScriptedConditionHandlerFactory.java index bd6514227d8..e98fcd76c38 100644 --- a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/factories/ScriptedConditionHandlerFactory.java +++ b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/factories/ScriptedConditionHandlerFactory.java @@ -24,5 +24,6 @@ */ @NonNullByDefault public interface ScriptedConditionHandlerFactory extends ScriptedHandler { - public @Nullable ConditionHandler get(Condition module); + @Nullable + ConditionHandler get(Condition module); } diff --git a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/factories/ScriptedTriggerHandlerFactory.java b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/factories/ScriptedTriggerHandlerFactory.java index d21bf0138e8..bfaa6484177 100644 --- a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/factories/ScriptedTriggerHandlerFactory.java +++ b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/factories/ScriptedTriggerHandlerFactory.java @@ -24,5 +24,6 @@ */ @NonNullByDefault public interface ScriptedTriggerHandlerFactory extends ScriptedHandler { - public @Nullable TriggerHandler get(Trigger module); + @Nullable + TriggerHandler get(Trigger module); } diff --git a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/simple/SimpleTriggerHandlerCallback.java b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/simple/SimpleTriggerHandlerCallback.java index 9ea2aea099e..196a9271bf1 100644 --- a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/simple/SimpleTriggerHandlerCallback.java +++ b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/simple/SimpleTriggerHandlerCallback.java @@ -23,5 +23,5 @@ */ @NonNullByDefault public interface SimpleTriggerHandlerCallback extends TriggerHandlerCallback { - public void triggered(Map context); + void triggered(Map context); } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/RuleRegistry.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/RuleRegistry.java index 6382bec9a8f..6b1dde5aba7 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/RuleRegistry.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/RuleRegistry.java @@ -68,7 +68,7 @@ public interface RuleRegistry extends Registry { * @throws IllegalStateException when the RuleManagedProvider is unavailable. */ @Override - public Rule add(Rule rule); + Rule add(Rule rule); /** * Gets a collection of {@link Rule}s which shares same tag. @@ -76,7 +76,7 @@ public interface RuleRegistry extends Registry { * @param tag specifies a tag that will filter the rules. * @return collection of {@link Rule}s having specified tag. */ - public Collection getByTag(@Nullable String tag); + Collection getByTag(@Nullable String tag); /** * Gets a collection of {@link Rule}s which has specified tags. @@ -84,5 +84,5 @@ public interface RuleRegistry extends Registry { * @param tags specifies tags that will filter the rules. * @return collection of {@link Rule}s having specified tags. */ - public Collection getByTags(String... tags); + Collection getByTags(String... tags); } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ConditionHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ConditionHandler.java index 41047cf32e3..1bf0363151c 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ConditionHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ConditionHandler.java @@ -37,5 +37,5 @@ public interface ConditionHandler extends ModuleHandler { * and the inputs of the {@link Condition}. * @return {@code true} if {@link Condition} is satisfied, {@code false} otherwise. */ - public boolean isSatisfied(Map context); + boolean isSatisfied(Map context); } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ModuleHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ModuleHandler.java index 574e878c6a3..81a1ccc0a03 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ModuleHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ModuleHandler.java @@ -28,7 +28,7 @@ public interface ModuleHandler { /** * The method is called by RuleManager to free resources when {@link ModuleHandler} is released. */ - public void dispose(); + void dispose(); /** * The callback is injected to the handler through this method. diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ModuleHandlerFactory.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ModuleHandlerFactory.java index 0320848237c..fc6b9199bf3 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ModuleHandlerFactory.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ModuleHandlerFactory.java @@ -44,7 +44,7 @@ public interface ModuleHandlerFactory { * * @return collection of module type UIDs supported by this factory. */ - public Collection getTypes(); + Collection getTypes(); /** * Creates a {@link ModuleHandler} instance needed for the operation of the {@link Module}s @@ -55,7 +55,8 @@ public interface ModuleHandlerFactory { * @return a new {@link ModuleHandler} instance, or {@code null} if the type of the * {@code module} parameter is not supported by this factory. */ - public @Nullable ModuleHandler getHandler(Module module, String ruleUID); + @Nullable + ModuleHandler getHandler(Module module, String ruleUID); /** * Releases the {@link ModuleHandler} instance when it is not needed anymore @@ -66,5 +67,5 @@ public interface ModuleHandlerFactory { * @param ruleUID the identifier of the {@link Rule} that the given module belongs to. * @param handler the {@link ModuleHandler} instance that is no longer needed. */ - public void ungetHandler(Module module, String ruleUID, ModuleHandler handler); + void ungetHandler(Module module, String ruleUID, ModuleHandler handler); } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/TimeBasedConditionHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/TimeBasedConditionHandler.java index 5f65b7914bc..6e571faae3f 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/TimeBasedConditionHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/TimeBasedConditionHandler.java @@ -30,5 +30,5 @@ public interface TimeBasedConditionHandler extends ConditionHandler { * @param time The time to check. * @return true if and only if the given time satisfies this condition. */ - public abstract boolean isSatisfiedAt(ZonedDateTime time); + boolean isSatisfiedAt(ZonedDateTime time); } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/parser/Parser.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/parser/Parser.java index 3ad62dbc864..5c398991dae 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/parser/Parser.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/parser/Parser.java @@ -32,22 +32,22 @@ public interface Parser { * Example : "parser.type" = "parser.module.type"; * It is used as registration property of the corresponding service. */ - public static String PARSER_TYPE = "parser.type"; + static String PARSER_TYPE = "parser.type"; /** * Defines one of the possible values of property {@link #PARSER_TYPE}. */ - public static String PARSER_MODULE_TYPE = "parser.module.type"; + static String PARSER_MODULE_TYPE = "parser.module.type"; /** * Defines one of the possible values of property {@link #PARSER_TYPE}. */ - public static String PARSER_TEMPLATE = "parser.template"; + static String PARSER_TEMPLATE = "parser.template"; /** * Defines one of the possible values of property {@link #PARSER_TYPE}. */ - public static String PARSER_RULE = "parser.rule"; + static String PARSER_RULE = "parser.rule"; /** * Defines a service registration property used for recognition of which file format is supported by the parser. @@ -55,12 +55,12 @@ public interface Parser { * Example : "format" = "json"; * It is used as registration property of the corresponding service. */ - public static String FORMAT = "format"; + static String FORMAT = "format"; /** * Defines the possible value of property {@link #FORMAT}. It means that the parser supports json format. */ - public static String FORMAT_JSON = "json"; + static String FORMAT_JSON = "json"; /** * Loads a file with some particular format and parse it to the corresponding automation objects. @@ -70,7 +70,7 @@ public interface Parser { * @throws ParsingException is thrown when json format is wrong or there is a semantic error in description of * the automation objects. */ - public Set parse(InputStreamReader reader) throws ParsingException; + Set parse(InputStreamReader reader) throws ParsingException; /** * Records the automation objects in a file with some particular format. @@ -80,5 +80,5 @@ public interface Parser { * @throws Exception is thrown when I/O operation has failed or has been interrupted or generating of the text fails * for some reasons. */ - public void serialize(Set dataObjects, OutputStreamWriter writer) throws Exception; + void serialize(Set dataObjects, OutputStreamWriter writer) throws Exception; } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/template/Template.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/template/Template.java index 8d366fee646..f814a246dfa 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/template/Template.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/template/Template.java @@ -40,7 +40,7 @@ public interface Template extends Identifiable { * @return the identifier of the Template. */ @Override - public String getUID(); + String getUID(); /** * Gets the assigned tags to a Template. The templates can have {@code tags} - non-hierarchical keywords or terms @@ -48,7 +48,7 @@ public interface Template extends Identifiable { * * @return the tags assigned to the template. */ - public Set getTags(); + Set getTags(); /** * Gets the label of a Template. The label is a short, human-readable description of the Template defined by its @@ -56,7 +56,8 @@ public interface Template extends Identifiable { * * @return the label of the Template. */ - public @Nullable String getLabel(); + @Nullable + String getLabel(); /** * Gets the description of a Template. The description is a detailed, human-understandable description of the @@ -64,12 +65,13 @@ public interface Template extends Identifiable { * * @return the description of the Template. */ - public @Nullable String getDescription(); + @Nullable + String getDescription(); /** * Shows the visibility of a Template. * * @return the visibility of the Template. */ - public Visibility getVisibility(); + Visibility getVisibility(); } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/template/TemplateRegistry.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/template/TemplateRegistry.java index 09d332bb1a8..32a474c284e 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/template/TemplateRegistry.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/template/TemplateRegistry.java @@ -41,7 +41,8 @@ public interface TemplateRegistry extends Registry extends Registry getByTag(@Nullable String tag); + Collection getByTag(@Nullable String tag); /** * Gets the templates filtered by tag. @@ -62,7 +63,7 @@ public interface TemplateRegistry extends Registry getByTag(@Nullable String tag, @Nullable Locale locale); + Collection getByTag(@Nullable String tag, @Nullable Locale locale); /** * Gets the templates filtered by tags. @@ -71,7 +72,7 @@ public interface TemplateRegistry extends Registry getByTags(String... tags); + Collection getByTags(String... tags); /** * Gets the templates filtered by tags. @@ -83,7 +84,7 @@ public interface TemplateRegistry extends Registry getByTags(@Nullable Locale locale, String... tags); + Collection getByTags(@Nullable Locale locale, String... tags); /** * Gets all available templates, localized by specified locale. @@ -93,5 +94,5 @@ public interface TemplateRegistry extends Registry getAll(@Nullable Locale locale); + Collection getAll(@Nullable Locale locale); } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/type/ModuleTypeRegistry.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/type/ModuleTypeRegistry.java index a97bcfec75f..f1b69f8c359 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/type/ModuleTypeRegistry.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/type/ModuleTypeRegistry.java @@ -40,7 +40,7 @@ public interface ModuleTypeRegistry extends Registry { * @return the desired {@link ModuleType} instance or {@code null} if a module type with such UID does not exist or * the passed UID is {@code null}. */ - public @Nullable T get(String moduleTypeUID, @Nullable Locale locale); + @Nullable T get(String moduleTypeUID, @Nullable Locale locale); /** * Gets the {@link ModuleType}s filtered by tag. @@ -50,7 +50,7 @@ public interface ModuleTypeRegistry extends Registry { * @param the type of the required object. * @return the {@link ModuleType}s, which correspond to the specified filter. */ - public Collection getByTag(@Nullable String moduleTypeTag); + Collection getByTag(@Nullable String moduleTypeTag); /** * This method is used for getting the {@link ModuleType}s filtered by tag. @@ -61,7 +61,7 @@ public interface ModuleTypeRegistry extends Registry { * @param the type of the required object. * @return the {@link ModuleType}s, which correspond to the specified filter. */ - public Collection getByTag(@Nullable String moduleTypeTag, @Nullable Locale locale); + Collection getByTag(@Nullable String moduleTypeTag, @Nullable Locale locale); /** * This method is used for getting the {@link ModuleType}s filtered by tags. @@ -71,7 +71,7 @@ public interface ModuleTypeRegistry extends Registry { * @param the type of the required object. * @return the {@link ModuleType}s, which correspond to the filter. */ - public Collection getByTags(String... tags); + Collection getByTags(String... tags); /** * This method is used for getting the {@link ModuleType}s filtered by tags. @@ -82,7 +82,7 @@ public interface ModuleTypeRegistry extends Registry { * @param the type of the required object. * @return the {@link ModuleType}s, which correspond to the filter. */ - public Collection getByTags(@Nullable Locale locale, String... tags); + Collection getByTags(@Nullable Locale locale, String... tags); /** * This method is used for getting the {@link TriggerType}s. The returned {@link TriggerType}s are @@ -92,7 +92,7 @@ public interface ModuleTypeRegistry extends Registry { * {@link TriggerType}s. * @return collection of all available {@link TriggerType}s, localized by default locale. */ - public Collection getTriggers(String... tags); + Collection getTriggers(String... tags); /** * This method is used for getting the {@link TriggerType}s, localized depending on passed locale parameter. @@ -105,7 +105,7 @@ public interface ModuleTypeRegistry extends Registry { * @return a collection of all available {@link TriggerType}s, localized by default locale or the passed locale * parameter. */ - public Collection getTriggers(@Nullable Locale locale, String... tags); + Collection getTriggers(@Nullable Locale locale, String... tags); /** * This method is used for getting the {@link ConditionType}s. The returned {@link ConditionType}s are @@ -115,7 +115,7 @@ public interface ModuleTypeRegistry extends Registry { * {@link ConditionType}s. * @return collection of all available {@link ConditionType}s, localized by default locale. */ - public Collection getConditions(String... tags); + Collection getConditions(String... tags); /** * This method is used for getting the {@link ConditionType}s, localized depending on passed locale parameter. @@ -128,7 +128,7 @@ public interface ModuleTypeRegistry extends Registry { * @return a collection of all available {@link ConditionType}s, localized by default locale or the passed locale * parameter. */ - public Collection getConditions(@Nullable Locale locale, String... tags); + Collection getConditions(@Nullable Locale locale, String... tags); /** * This method is used for getting the {@link ActionType}s. The returned {@link ActionType}s are @@ -138,7 +138,7 @@ public interface ModuleTypeRegistry extends Registry { * {@link ActionType}s. * @return collection of all available {@link ActionType}s, localized by default locale. */ - public Collection getActions(String... tags); + Collection getActions(String... tags); /** * This method is used for getting the {@link ActionType}s, localized depending on passed locale parameter. @@ -151,5 +151,5 @@ public interface ModuleTypeRegistry extends Registry { * @return a collection of all available {@link ActionType}s, localized by default locale or the passed locale * parameter. */ - public Collection getActions(@Nullable Locale locale, String... tags); + Collection getActions(@Nullable Locale locale, String... tags); } diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/internal/normalization/Normalizer.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/internal/normalization/Normalizer.java index 2b2c577677b..593b8a96e6b 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/internal/normalization/Normalizer.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/internal/normalization/Normalizer.java @@ -31,5 +31,5 @@ public interface Normalizer { * @param value the object to be normalized * @return the well-defined type or the given object, if it was not possible to convert it */ - public Object normalize(Object value); + Object normalize(Object value); } diff --git a/bundles/org.openhab.core.config.discovery.usbserial/src/main/java/org/openhab/core/config/discovery/usbserial/UsbSerialDiscoveryParticipant.java b/bundles/org.openhab.core.config.discovery.usbserial/src/main/java/org/openhab/core/config/discovery/usbserial/UsbSerialDiscoveryParticipant.java index 44aba1654d2..d9c68ee69f5 100644 --- a/bundles/org.openhab.core.config.discovery.usbserial/src/main/java/org/openhab/core/config/discovery/usbserial/UsbSerialDiscoveryParticipant.java +++ b/bundles/org.openhab.core.config.discovery.usbserial/src/main/java/org/openhab/core/config/discovery/usbserial/UsbSerialDiscoveryParticipant.java @@ -36,7 +36,7 @@ public interface UsbSerialDiscoveryParticipant { * * @return a set of thing type UIDs for which results can be created */ - public Set getSupportedThingTypeUIDs(); + Set getSupportedThingTypeUIDs(); /** * Creates a discovery result for a USB device with corresponding serial port. @@ -45,7 +45,8 @@ public interface UsbSerialDiscoveryParticipant { * @return the according discovery result or null if the device is not * supported by this participant */ - public @Nullable DiscoveryResult createResult(UsbSerialDeviceInformation deviceInformation); + @Nullable + DiscoveryResult createResult(UsbSerialDeviceInformation deviceInformation); /** * Returns the thing UID for a USB device with corresponding serial port. @@ -54,5 +55,6 @@ public interface UsbSerialDiscoveryParticipant { * @return a thing UID or null if the device is not supported * by this participant */ - public @Nullable ThingUID getThingUID(UsbSerialDeviceInformation deviceInformation); + @Nullable + ThingUID getThingUID(UsbSerialDeviceInformation deviceInformation); } diff --git a/bundles/org.openhab.core.io.console.rfc147/src/main/java/org/openhab/core/io/console/rfc147/internal/ConsoleCommandsContainer.java b/bundles/org.openhab.core.io.console.rfc147/src/main/java/org/openhab/core/io/console/rfc147/internal/ConsoleCommandsContainer.java index 2b6affbac80..c988051b774 100644 --- a/bundles/org.openhab.core.io.console.rfc147/src/main/java/org/openhab/core/io/console/rfc147/internal/ConsoleCommandsContainer.java +++ b/bundles/org.openhab.core.io.console.rfc147/src/main/java/org/openhab/core/io/console/rfc147/internal/ConsoleCommandsContainer.java @@ -24,5 +24,5 @@ @NonNullByDefault public interface ConsoleCommandsContainer { - public Collection getConsoleCommandExtensions(); + Collection getConsoleCommandExtensions(); } diff --git a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthenticationSecurityContext.java b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthenticationSecurityContext.java index a0272e2bde1..045c38a4515 100644 --- a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthenticationSecurityContext.java +++ b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthenticationSecurityContext.java @@ -27,5 +27,5 @@ public interface AuthenticationSecurityContext extends SecurityContext { * * @return the authentication instance */ - public Authentication getAuthentication(); + Authentication getAuthentication(); } diff --git a/bundles/org.openhab.core.io.transport.mdns/src/main/java/org/openhab/core/io/transport/mdns/MDNSClient.java b/bundles/org.openhab.core.io.transport.mdns/src/main/java/org/openhab/core/io/transport/mdns/MDNSClient.java index b4d3011b3c4..8ea31733ff2 100644 --- a/bundles/org.openhab.core.io.transport.mdns/src/main/java/org/openhab/core/io/transport/mdns/MDNSClient.java +++ b/bundles/org.openhab.core.io.transport.mdns/src/main/java/org/openhab/core/io/transport/mdns/MDNSClient.java @@ -33,7 +33,7 @@ public interface MDNSClient { * * @return a set of JmDNS instances */ - public Set getClientInstances(); + Set getClientInstances(); /** * Listen for services of a given type @@ -41,7 +41,7 @@ public interface MDNSClient { * @param type full qualified service type * @param listener listener for service updates */ - public void addServiceListener(String type, ServiceListener listener); + void addServiceListener(String type, ServiceListener listener); /** * Remove listener for services of a given type @@ -49,27 +49,27 @@ public interface MDNSClient { * @param type full qualified service type * @param listener listener for service updates */ - public void removeServiceListener(String type, ServiceListener listener); + void removeServiceListener(String type, ServiceListener listener); /** * Register a service * * @param description service to register, described by (@link ServiceDescription) */ - public void registerService(ServiceDescription description) throws IOException; + void registerService(ServiceDescription description) throws IOException; /** * Unregister a service. The service should have been registered. * * @param description service to remove, described by (@link ServiceDescription) */ - public void unregisterService(ServiceDescription description); + void unregisterService(ServiceDescription description); /** * Unregister all services * */ - public void unregisterAllServices(); + void unregisterAllServices(); /** * Returns a list of service infos of the specified type @@ -77,7 +77,7 @@ public interface MDNSClient { * @param type service type name * @return an array of service instances */ - public ServiceInfo[] list(String type); + ServiceInfo[] list(String type); /** * Returns a list of service infos of the specified type within timeout @@ -86,11 +86,11 @@ public interface MDNSClient { * @param timeout the amount of time it should wait if no service info is found. * @return an array of service instances */ - public ServiceInfo[] list(String type, Duration timeout); + ServiceInfo[] list(String type, Duration timeout); /** * Close properly JmDNS instances * */ - public void close(); + void close(); } diff --git a/bundles/org.openhab.core.io.transport.mdns/src/main/java/org/openhab/core/io/transport/mdns/MDNSService.java b/bundles/org.openhab.core.io.transport.mdns/src/main/java/org/openhab/core/io/transport/mdns/MDNSService.java index 791dd74dd13..28331c81d22 100644 --- a/bundles/org.openhab.core.io.transport.mdns/src/main/java/org/openhab/core/io/transport/mdns/MDNSService.java +++ b/bundles/org.openhab.core.io.transport.mdns/src/main/java/org/openhab/core/io/transport/mdns/MDNSService.java @@ -26,12 +26,12 @@ public interface MDNSService { * * @param serviceDescription the {@link ServiceDescription} instance with all details to identify the service */ - public void registerService(ServiceDescription description); + void registerService(ServiceDescription description); /** * This method unregisters a service not to be announced through Bonjour/MDNS * * @param serviceDescription the {@link ServiceDescription} instance with all details to identify the service */ - public void unregisterService(ServiceDescription description); + void unregisterService(ServiceDescription description); } diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusCommunicationInterface.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusCommunicationInterface.java index ffbeca1f4b5..27f72d7301f 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusCommunicationInterface.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusCommunicationInterface.java @@ -38,7 +38,7 @@ public interface ModbusCommunicationInterface extends AutoCloseable { * * @return modbus slave endpoint */ - public ModbusSlaveEndpoint getEndpoint(); + ModbusSlaveEndpoint getEndpoint(); /** * Submit one-time poll task. The method returns immediately, and the execution of the poll task will happen in @@ -50,7 +50,7 @@ public interface ModbusCommunicationInterface extends AutoCloseable { * @return future representing the polled task * @throws IllegalStateException when this communication has been closed already */ - public Future submitOneTimePoll(ModbusReadRequestBlueprint request, ModbusReadCallback resultCallback, + Future submitOneTimePoll(ModbusReadRequestBlueprint request, ModbusReadCallback resultCallback, ModbusFailureCallback failureCallback); /** @@ -67,9 +67,8 @@ public Future submitOneTimePoll(ModbusReadRequestBlueprint request, ModbusRea * @return poll task representing the regular poll * @throws IllegalStateException when this communication has been closed already */ - public PollTask registerRegularPoll(ModbusReadRequestBlueprint request, long pollPeriodMillis, - long initialDelayMillis, ModbusReadCallback resultCallback, - ModbusFailureCallback failureCallback); + PollTask registerRegularPoll(ModbusReadRequestBlueprint request, long pollPeriodMillis, long initialDelayMillis, + ModbusReadCallback resultCallback, ModbusFailureCallback failureCallback); /** * Unregister regularly polled task @@ -80,7 +79,7 @@ public PollTask registerRegularPoll(ModbusReadRequestBlueprint request, long pol * @return whether poll task was unregistered. Poll task is not unregistered in case of unexpected errors or * in the case where the poll task is not registered in the first place */ - public boolean unregisterRegularPoll(PollTask task); + boolean unregisterRegularPoll(PollTask task); /** * Submit one-time write task. The method returns immediately, and the execution of the task will happen in @@ -92,7 +91,7 @@ public PollTask registerRegularPoll(ModbusReadRequestBlueprint request, long pol * @return future representing the task * @throws IllegalStateException when this communication has been closed already */ - public Future submitOneTimeWrite(ModbusWriteRequestBlueprint request, ModbusWriteCallback resultCallback, + Future submitOneTimeWrite(ModbusWriteRequestBlueprint request, ModbusWriteCallback resultCallback, ModbusFailureCallback failureCallback); /** @@ -105,5 +104,5 @@ public Future submitOneTimeWrite(ModbusWriteRequestBlueprint request, ModbusW * */ @Override - public void close() throws Exception; + void close() throws Exception; } diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusManager.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusManager.java index 4595e54f4e8..e8d23f26a6b 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusManager.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusManager.java @@ -34,7 +34,7 @@ public interface ModbusManager { * @throws IllegalArgumentException if there is already open communication interface with same endpoint but * differing configuration */ - public ModbusCommunicationInterface newModbusCommunicationInterface(ModbusSlaveEndpoint endpoint, + ModbusCommunicationInterface newModbusCommunicationInterface(ModbusSlaveEndpoint endpoint, @Nullable EndpointPoolConfiguration configuration) throws IllegalArgumentException; /** @@ -45,5 +45,5 @@ public ModbusCommunicationInterface newModbusCommunicationInterface(ModbusSlaveE * @param endpoint endpoint to query * @return general connection settings of the given endpoint */ - public EndpointPoolConfiguration getEndpointPoolConfiguration(ModbusSlaveEndpoint endpoint); + EndpointPoolConfiguration getEndpointPoolConfiguration(ModbusSlaveEndpoint endpoint); } diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusResponse.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusResponse.java index f1de8d7385c..9d59a7cbc57 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusResponse.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusResponse.java @@ -32,5 +32,5 @@ public interface ModbusResponse { * * @return function code of the response */ - public int getFunctionCode(); + int getFunctionCode(); } diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusWriteRequestBlueprintVisitor.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusWriteRequestBlueprintVisitor.java index 0794760ccaa..4f66ae34a93 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusWriteRequestBlueprintVisitor.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusWriteRequestBlueprintVisitor.java @@ -29,12 +29,12 @@ public interface ModbusWriteRequestBlueprintVisitor { * * @param blueprint */ - public void visit(ModbusWriteCoilRequestBlueprint blueprint); + void visit(ModbusWriteCoilRequestBlueprint blueprint); /** * Visit request writing register data * * @param blueprint */ - public void visit(ModbusWriteRegisterRequestBlueprint blueprint); + void visit(ModbusWriteRegisterRequestBlueprint blueprint); } diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/endpoint/ModbusSlaveEndpoint.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/endpoint/ModbusSlaveEndpoint.java index d980d94fd53..fda03c56b08 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/endpoint/ModbusSlaveEndpoint.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/endpoint/ModbusSlaveEndpoint.java @@ -27,5 +27,5 @@ */ @NonNullByDefault public interface ModbusSlaveEndpoint { - public R accept(ModbusSlaveEndpointVisitor visitor); + R accept(ModbusSlaveEndpointVisitor visitor); } diff --git a/bundles/org.openhab.core.io.transport.mqtt/src/main/java/org/openhab/core/io/transport/mqtt/MqttActionCallback.java b/bundles/org.openhab.core.io.transport.mqtt/src/main/java/org/openhab/core/io/transport/mqtt/MqttActionCallback.java index 3a5d7d85886..aacae8ab688 100644 --- a/bundles/org.openhab.core.io.transport.mqtt/src/main/java/org/openhab/core/io/transport/mqtt/MqttActionCallback.java +++ b/bundles/org.openhab.core.io.transport.mqtt/src/main/java/org/openhab/core/io/transport/mqtt/MqttActionCallback.java @@ -22,7 +22,7 @@ */ @NonNullByDefault public interface MqttActionCallback { - public void onSuccess(String topic); + void onSuccess(String topic); - public void onFailure(String topic, Throwable error); + void onFailure(String topic, Throwable error); } diff --git a/bundles/org.openhab.core.io.transport.mqtt/src/main/java/org/openhab/core/io/transport/mqtt/MqttConnectionObserver.java b/bundles/org.openhab.core.io.transport.mqtt/src/main/java/org/openhab/core/io/transport/mqtt/MqttConnectionObserver.java index 9e460abc55d..392f34ea09f 100644 --- a/bundles/org.openhab.core.io.transport.mqtt/src/main/java/org/openhab/core/io/transport/mqtt/MqttConnectionObserver.java +++ b/bundles/org.openhab.core.io.transport.mqtt/src/main/java/org/openhab/core/io/transport/mqtt/MqttConnectionObserver.java @@ -33,5 +33,5 @@ public interface MqttConnectionObserver { * @param error An exception object (might be a MqttException) with the reason why * a connection failed. */ - public void connectionStateChanged(MqttConnectionState state, @Nullable Throwable error); + void connectionStateChanged(MqttConnectionState state, @Nullable Throwable error); } diff --git a/bundles/org.openhab.core.io.transport.mqtt/src/main/java/org/openhab/core/io/transport/mqtt/MqttMessageSubscriber.java b/bundles/org.openhab.core.io.transport.mqtt/src/main/java/org/openhab/core/io/transport/mqtt/MqttMessageSubscriber.java index 41737725d19..4c3a7246d34 100644 --- a/bundles/org.openhab.core.io.transport.mqtt/src/main/java/org/openhab/core/io/transport/mqtt/MqttMessageSubscriber.java +++ b/bundles/org.openhab.core.io.transport.mqtt/src/main/java/org/openhab/core/io/transport/mqtt/MqttMessageSubscriber.java @@ -28,5 +28,5 @@ public interface MqttMessageSubscriber { * @param topic The mqtt topic on which the message was received. * @param payload content of the message. */ - public void processMessage(String topic, byte[] payload); + void processMessage(String topic, byte[] payload); } diff --git a/bundles/org.openhab.core.io.transport.serial/src/main/java/org/openhab/core/io/transport/serial/SerialPortProvider.java b/bundles/org.openhab.core.io.transport.serial/src/main/java/org/openhab/core/io/transport/serial/SerialPortProvider.java index 21bd7f13d85..caf68b4fe47 100644 --- a/bundles/org.openhab.core.io.transport.serial/src/main/java/org/openhab/core/io/transport/serial/SerialPortProvider.java +++ b/bundles/org.openhab.core.io.transport.serial/src/main/java/org/openhab/core/io/transport/serial/SerialPortProvider.java @@ -35,14 +35,15 @@ public interface SerialPortProvider { * @throws UnsupportedCommOperationException * @throws PortInUseException */ - public @Nullable SerialPortIdentifier getPortIdentifier(URI portName); + @Nullable + SerialPortIdentifier getPortIdentifier(URI portName); /** * Gets all protocol types which this provider is able to create. * * @return The protocol type. */ - public Stream getAcceptedProtocols(); + Stream getAcceptedProtocols(); /** * Gets all the available {@link SerialPortIdentifier}s for this {@link SerialPortProvider}. @@ -51,5 +52,5 @@ public interface SerialPortProvider { * * @return The available ports */ - public Stream getSerialPortIdentifiers(); + Stream getSerialPortIdentifiers(); } diff --git a/bundles/org.openhab.core.io.transport.upnp/src/main/java/org/openhab/core/io/transport/upnp/UpnpIOParticipant.java b/bundles/org.openhab.core.io.transport.upnp/src/main/java/org/openhab/core/io/transport/upnp/UpnpIOParticipant.java index 0fc80310d30..f85db550044 100644 --- a/bundles/org.openhab.core.io.transport.upnp/src/main/java/org/openhab/core/io/transport/upnp/UpnpIOParticipant.java +++ b/bundles/org.openhab.core.io.transport.upnp/src/main/java/org/openhab/core/io/transport/upnp/UpnpIOParticipant.java @@ -22,10 +22,10 @@ public interface UpnpIOParticipant { /** Get the UDN of the participant **/ - public String getUDN(); + String getUDN(); /** Called when the UPNP IO service receives a {variable,value} tuple for the given UPNP service **/ - public void onValueReceived(String variable, String value, String service); + void onValueReceived(String variable, String value, String service); /** * Called to notify if a GENA subscription succeeded or failed. @@ -33,7 +33,7 @@ public interface UpnpIOParticipant { * @param service the UPnP service subscribed * @param succeeded true if the subscription succeeded; false if failed */ - public void onServiceSubscribed(String service, boolean succeeded); + void onServiceSubscribed(String service, boolean succeeded); /** * Called when the UPNP IO service is unable to poll the UDN of the participant, given that @@ -42,5 +42,5 @@ public interface UpnpIOParticipant { * @param status false, if the poll fails when the polling was previously successful; true if the poll succeeds * when the polling was previously failing */ - public void onStatusChanged(boolean status); + void onStatusChanged(boolean status); } diff --git a/bundles/org.openhab.core.io.transport.upnp/src/main/java/org/openhab/core/io/transport/upnp/UpnpIOService.java b/bundles/org.openhab.core.io.transport.upnp/src/main/java/org/openhab/core/io/transport/upnp/UpnpIOService.java index 5686b403230..36168b44855 100644 --- a/bundles/org.openhab.core.io.transport.upnp/src/main/java/org/openhab/core/io/transport/upnp/UpnpIOService.java +++ b/bundles/org.openhab.core.io.transport.upnp/src/main/java/org/openhab/core/io/transport/upnp/UpnpIOService.java @@ -32,7 +32,7 @@ public interface UpnpIOService { * @param actionID the Action to invoke * @param inputs a map of {variable,values} to parameterize the Action that will be invoked */ - public Map invokeAction(UpnpIOParticipant participant, String serviceID, String actionID, + Map invokeAction(UpnpIOParticipant participant, String serviceID, String actionID, Map inputs); /** @@ -42,7 +42,7 @@ public Map invokeAction(UpnpIOParticipant participant, String se * @param serviceID the UPNP service we want to subscribe to * @param duration the duration of the subscription */ - public void addSubscription(UpnpIOParticipant participant, String serviceID, int duration); + void addSubscription(UpnpIOParticipant participant, String serviceID, int duration); /** * Unsubscribe from a GENA subscription @@ -50,7 +50,7 @@ public Map invokeAction(UpnpIOParticipant participant, String se * @param participant the participant of the subscription * @param serviceID the UPNP service we want to unsubscribe from */ - public void removeSubscription(UpnpIOParticipant participant, String serviceID); + void removeSubscription(UpnpIOParticipant participant, String serviceID); /** * Verify if the a participant is registered @@ -58,21 +58,21 @@ public Map invokeAction(UpnpIOParticipant participant, String se * @param participant the participant whom's participation we want to verify * @return true of the participant is registered with the UpnpIOService */ - public boolean isRegistered(UpnpIOParticipant participant); + boolean isRegistered(UpnpIOParticipant participant); /** * Register a participant with the UPNP IO Service * * @param participant the participant whose participation we want to register */ - public void registerParticipant(UpnpIOParticipant participant); + void registerParticipant(UpnpIOParticipant participant); /** * Unregister a participant with the UPNP IO Service * * @param participant the participant whose participation we want to unregister */ - public void unregisterParticipant(UpnpIOParticipant participant); + void unregisterParticipant(UpnpIOParticipant participant); /** * Retrieves the descriptor url for the participant @@ -80,7 +80,7 @@ public Map invokeAction(UpnpIOParticipant participant, String se * @param participant the participant whom's descriptor url is requested * @return the url of the descriptor as provided by the upnp device */ - public URL getDescriptorURL(UpnpIOParticipant participant); + URL getDescriptorURL(UpnpIOParticipant participant); /** * Establish a polling mechanism to check the status of a specific UDN device. The polling mechanism @@ -92,12 +92,12 @@ public Map invokeAction(UpnpIOParticipant participant, String se * @param actionID the action to call * @param interval the interval in seconds */ - public void addStatusListener(UpnpIOParticipant participant, String serviceID, String actionID, int interval); + void addStatusListener(UpnpIOParticipant participant, String serviceID, String actionID, int interval); /** * Stops the polling mechanism to check the status of a specific UDN device. * * @param participant the participant for whom we want to remove the polling */ - public void removeStatusListener(UpnpIOParticipant participant); + void removeStatusListener(UpnpIOParticipant participant); } diff --git a/bundles/org.openhab.core.model.core/src/main/java/org/openhab/core/model/core/ModelRepositoryChangeListener.java b/bundles/org.openhab.core.model.core/src/main/java/org/openhab/core/model/core/ModelRepositoryChangeListener.java index 519195ee43d..9b8c736853a 100644 --- a/bundles/org.openhab.core.model.core/src/main/java/org/openhab/core/model/core/ModelRepositoryChangeListener.java +++ b/bundles/org.openhab.core.model.core/src/main/java/org/openhab/core/model/core/ModelRepositoryChangeListener.java @@ -25,5 +25,5 @@ public interface ModelRepositoryChangeListener { * Performs dispatch of all binding configs and * fires all {@link ItemsChangeListener}s if {@code modelName} ends with "items". */ - public void modelChanged(String modelName, EventType type); + void modelChanged(String modelName, EventType type); } diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/engine/Script.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/engine/Script.java index 0e461f43e75..7f821da605c 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/engine/Script.java +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/engine/Script.java @@ -22,7 +22,7 @@ @SuppressWarnings("restriction") public interface Script { - public static final String SCRIPT_FILEEXT = "script"; + static final String SCRIPT_FILEEXT = "script"; /** * Executes the script instance and returns the execution result @@ -30,7 +30,7 @@ public interface Script { * @return the execution result or null, if the script does not have a return value * @throws ScriptExecutionException if an error occurs during the execution */ - public Object execute() throws ScriptExecutionException; + Object execute() throws ScriptExecutionException; /** * Executes the script instance with a given evaluation context and returns the execution result @@ -40,5 +40,5 @@ public interface Script { * @return the execution result or null, if the script does not have a return value * @throws ScriptExecutionException if an error occurs during the execution */ - public Object execute(IEvaluationContext evaluationContext) throws ScriptExecutionException; + Object execute(IEvaluationContext evaluationContext) throws ScriptExecutionException; } diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/engine/ScriptEngine.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/engine/ScriptEngine.java index 4582061c527..cf4c1ed24bb 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/engine/ScriptEngine.java +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/engine/ScriptEngine.java @@ -29,7 +29,7 @@ public interface ScriptEngine { * @return Script object, which can be executed * @throws ScriptParsingException */ - public Script newScriptFromString(final String scriptAsString) throws ScriptParsingException; + Script newScriptFromString(final String scriptAsString) throws ScriptParsingException; /** * Executes a script that is passed as a string @@ -39,7 +39,7 @@ public interface ScriptEngine { * @throws ScriptParsingException * @throws ScriptExecutionException */ - public Object executeScript(final String scriptAsString) throws ScriptParsingException, ScriptExecutionException; + Object executeScript(final String scriptAsString) throws ScriptParsingException, ScriptExecutionException; /** * Wraps an Xbase XExpression in a Script instance @@ -47,5 +47,5 @@ public interface ScriptEngine { * @param expression the XExpression * @return the Script instance containing the expression */ - public Script newScriptFromXExpression(final XExpression expression); + Script newScriptFromXExpression(final XExpression expression); } diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Location.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Location.java index 591d307c212..cc4497205e1 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Location.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Location.java @@ -25,7 +25,7 @@ @TagInfo(id = "Location") public interface Location extends Tag { - public static String name() { + static String name() { return "Location"; } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ThingManager.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ThingManager.java index 6f525c84c1b..28eeead5ff7 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ThingManager.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ThingManager.java @@ -31,7 +31,7 @@ public interface ThingManager { * @return {@code false} when the {@link Thing} has {@link ThingStatus} with {@link ThingStatusDetail#DISABLED}. * Returns {@code true} in all other cases. */ - public boolean isEnabled(ThingUID thingUID); + boolean isEnabled(ThingUID thingUID); /** * This method is used for changing enabled state of the {@link Thing} @@ -41,5 +41,5 @@ public interface ThingManager { * @param thingUID UID of the {@link Thing}. * @param isEnabled a new enabled / disabled state of the {@link Thing}. */ - public void setEnabled(ThingUID thingUID, boolean isEnabled); + void setEnabled(ThingUID thingUID, boolean isEnabled); } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/firmware/Firmware.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/firmware/Firmware.java index d8fdb6523c3..913993796fd 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/firmware/Firmware.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/firmware/Firmware.java @@ -65,7 +65,7 @@ public interface Firmware extends Comparable { /** The key for the requires a factory reset property. */ - public static final String PROPERTY_REQUIRES_FACTORY_RESET = "requiresFactoryReset"; + static final String PROPERTY_REQUIRES_FACTORY_RESET = "requiresFactoryReset"; /** * Returns the thing type UID, that this firmware is associated with. diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingTracker.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingTracker.java index 2ecdfddeb4a..bc4815e88c9 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingTracker.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingTracker.java @@ -29,7 +29,7 @@ @NonNullByDefault public interface ThingTracker { - public enum ThingTrackerEvent { + enum ThingTrackerEvent { THING_ADDED, THING_REMOVING, THING_REMOVED, diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/profiles/ProfileTypeRegistry.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/profiles/ProfileTypeRegistry.java index 2d53f84cabe..1cbff58443c 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/profiles/ProfileTypeRegistry.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/profiles/ProfileTypeRegistry.java @@ -32,7 +32,7 @@ public interface ProfileTypeRegistry { * * @return all profile types */ - public List getProfileTypes(); + List getProfileTypes(); /** * Get the available {@link ProfileType}s from all providers. @@ -40,5 +40,5 @@ public interface ProfileTypeRegistry { * @param locale the language to use (may be null) * @return all profile types */ - public List getProfileTypes(@Nullable Locale locale); + List getProfileTypes(@Nullable Locale locale); } diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/chart/ChartProvider.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/chart/ChartProvider.java index fd6d8cf3e21..5ea642e32f4 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/chart/ChartProvider.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/chart/ChartProvider.java @@ -106,7 +106,7 @@ default BufferedImage createChart(@Nullable String service, @Nullable String the * Provides a list of image types * */ - public enum ImageType { + enum ImageType { png, jpg, gif diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartTheme.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartTheme.java index b432ddc52fd..23740561fca 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartTheme.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartTheme.java @@ -30,21 +30,21 @@ public interface ChartTheme { * * @return theme name */ - public String getThemeName(); + String getThemeName(); /** * Background color, plot area. * * @return background color, plot area */ - public Color getPlotBackgroundColor(); + Color getPlotBackgroundColor(); /** * Color for the grid lines. * * @return color for the grid lines */ - public Color getPlotGridLinesColor(); + Color getPlotGridLinesColor(); /** * Return the width of the grid lines. @@ -52,7 +52,7 @@ public interface ChartTheme { * @param dpi DPI dots per inch to calculate the width * @return width of the grid lines */ - public double getPlotGridLinesWidth(int dpi); + double getPlotGridLinesWidth(int dpi); /** * Return the dash spacing for the grid lines. @@ -60,28 +60,28 @@ public interface ChartTheme { * @param dpi DPI dots per inch to calculate the width * @return dash spacing for the grid lines */ - public double getPlotGridLinesDash(int dpi); + double getPlotGridLinesDash(int dpi); /** * Background color, legend area. * * @return background color, legend area */ - public Color getLegendBackgroundColor(); + Color getLegendBackgroundColor(); /** * Background color, whole chart * * @return background color, whole chart */ - public Color getChartBackgroundColor(); + Color getChartBackgroundColor(); /** * Font color, legend and general use. * * @return */ - public Color getChartFontColor(); + Color getChartFontColor(); /** * Return a color for the given series number. @@ -89,7 +89,7 @@ public interface ChartTheme { * @param series series number * @return color for the given series numer */ - public Color getLineColor(int series); + Color getLineColor(int series); /** * Return the width of the series lines. @@ -97,14 +97,14 @@ public interface ChartTheme { * @param dpi DPI dots per inch to calculate the width * @return width of the series lines */ - public double getLineWidth(int dpi); + double getLineWidth(int dpi); /** * Color for the axis labels. * * @return */ - public Color getAxisTickLabelsColor(); + Color getAxisTickLabelsColor(); /** * Font for the axis labels. @@ -113,7 +113,7 @@ public interface ChartTheme { * @param dpi the DPI to calculate the font size * @return {@link Font} for the axis labels. */ - public Font getAxisTickLabelsFont(int dpi); + Font getAxisTickLabelsFont(int dpi); /** * Font for the legend text. @@ -122,7 +122,7 @@ public interface ChartTheme { * @param dpi the DPI to calculate the font size * @return {@link Font} for the legend text */ - public Font getLegendFont(int dpi); + Font getLegendFont(int dpi); /** * Padding of the chart. @@ -130,7 +130,7 @@ public interface ChartTheme { * @param dpi the DPI to calculate the padding * @return padding of the chart */ - public int getChartPadding(int dpi); + int getChartPadding(int dpi); /** * Length of the line markers in the legend, in px. @@ -138,5 +138,5 @@ public interface ChartTheme { * @param dpi the DPI to calculate the line length * @return length of the line markers in the legend, in px */ - public int getLegendSeriesLineLength(int dpi); + int getLegendSeriesLineLength(int dpi); } diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/items/ItemUIProvider.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/items/ItemUIProvider.java index 5273cab8991..9303ac87c96 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/items/ItemUIProvider.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/items/ItemUIProvider.java @@ -32,7 +32,8 @@ public interface ItemUIProvider { * @param itemName the name of the item to return the icon for * @return the name of the category to use or null if undefined. */ - public @Nullable String getCategory(String itemName); + @Nullable + String getCategory(String itemName); /** * Returns the label text to be used for an item in the UI. @@ -40,7 +41,8 @@ public interface ItemUIProvider { * @param item the name of the item to return the label text for * @return the label text to be used in the UI or null if undefined. */ - public @Nullable String getLabel(String itemName); + @Nullable + String getLabel(String itemName); /** * Provides a default widget for a given item (class). This is used whenever @@ -52,7 +54,8 @@ public interface ItemUIProvider { * @return a widget implementation that can be used for the given item or null, if no default is available for the * type */ - public @Nullable Widget getDefaultWidget(@Nullable Class itemType, String itemName); + @Nullable + Widget getDefaultWidget(@Nullable Class itemType, String itemName); /** *

@@ -66,5 +69,6 @@ public interface ItemUIProvider { * @param itemName the item name to get the widget for * @return a widget to use for the given item or null if sitemap should not be overridden. */ - public @Nullable Widget getWidget(String itemName); + @Nullable + Widget getWidget(String itemName); } diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/tiles/TileProvider.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/tiles/TileProvider.java index 8e45677e281..911a06f12fb 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/tiles/TileProvider.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/tiles/TileProvider.java @@ -23,5 +23,5 @@ */ @NonNullByDefault public interface TileProvider { - public Stream getTiles(); + Stream getTiles(); } diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/KSListener.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/KSListener.java index 2b9fd5835b3..cf752e4bc05 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/KSListener.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/KSListener.java @@ -31,5 +31,5 @@ public interface KSListener { * * @param ksEvent The {@link KSEvent} fired by the {@link KSService} */ - public void ksEventReceived(KSEvent ksEvent); + void ksEventReceived(KSEvent ksEvent); } diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/KSService.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/KSService.java index 61a2dc930e2..1e220f390a8 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/KSService.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/KSService.java @@ -34,7 +34,7 @@ public interface KSService { * * @return an id that identifies this service */ - public String getId(); + String getId(); /** * Returns a localized human readable label that can be used within UIs. @@ -42,21 +42,21 @@ public interface KSService { * @param locale the locale to provide the label for * @return a localized string to be used in UIs */ - public String getLabel(@Nullable Locale locale); + String getLabel(@Nullable Locale locale); /** * Obtain the Locales available from this KSService * * @return The Locales available from this service */ - public Set getSupportedLocales(); + Set getSupportedLocales(); /** * Obtain the audio formats supported by this KSService * * @return The audio formats supported by this service */ - public Set getSupportedFormats(); + Set getSupportedFormats(); /** * This method starts the process of keyword spotting @@ -84,6 +84,6 @@ public interface KSService { * @return A {@link KSServiceHandle} used to abort keyword spotting * @throws A {@link KSException} if any parameter is invalid or a problem occurs */ - public KSServiceHandle spot(KSListener ksListener, AudioStream audioStream, Locale locale, String keyword) + KSServiceHandle spot(KSListener ksListener, AudioStream audioStream, Locale locale, String keyword) throws KSException; } diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/KSServiceHandle.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/KSServiceHandle.java index 289fa590a14..4a8e7f58ab8 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/KSServiceHandle.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/KSServiceHandle.java @@ -24,5 +24,5 @@ public interface KSServiceHandle { /** * Aborts keyword spotting in the associated {@link KSService} */ - public void abort(); + void abort(); } diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/STTListener.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/STTListener.java index 4234bcd887f..967041d9e8c 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/STTListener.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/STTListener.java @@ -32,5 +32,5 @@ public interface STTListener { * * @param sttEvent The {@link STTEvent} fired by the {@link STTService} */ - public void sttEventReceived(STTEvent sttEvent); + void sttEventReceived(STTEvent sttEvent); } diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/STTService.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/STTService.java index 02241e69f98..5f494e10603 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/STTService.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/STTService.java @@ -33,7 +33,7 @@ public interface STTService { * * @return an id that identifies this service */ - public String getId(); + String getId(); /** * Returns a localized human readable label that can be used within UIs. @@ -41,21 +41,21 @@ public interface STTService { * @param locale the locale to provide the label for * @return a localized string to be used in UIs */ - public String getLabel(@Nullable Locale locale); + String getLabel(@Nullable Locale locale); /** * Obtain the Locales available from this STTService * * @return The Locales available from this service */ - public Set getSupportedLocales(); + Set getSupportedLocales(); /** * Obtain the audio formats supported by this STTService * * @return The audio formats supported by this service */ - public Set getSupportedFormats(); + Set getSupportedFormats(); /** * This method starts the process of speech recognition. @@ -85,6 +85,6 @@ public interface STTService { * @return A {@link STTServiceHandle} used to abort recognition * @throws A {@link SSTException} if any parameter is invalid or a STT problem occurs */ - public STTServiceHandle recognize(STTListener sttListener, AudioStream audioStream, Locale locale, - Set grammars) throws STTException; + STTServiceHandle recognize(STTListener sttListener, AudioStream audioStream, Locale locale, Set grammars) + throws STTException; } diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/STTServiceHandle.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/STTServiceHandle.java index 11aacbd6758..58773fe53ba 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/STTServiceHandle.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/STTServiceHandle.java @@ -24,5 +24,5 @@ public interface STTServiceHandle { /** * Aborts recognition in the associated {@link STTService} */ - public void abort(); + void abort(); } diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/Voice.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/Voice.java index 86851c493d4..c64677aeacc 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/Voice.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/Voice.java @@ -30,19 +30,19 @@ public interface Voice { * * @return A String uniquely identifying the voice. */ - public String getUID(); + String getUID(); /** * The voice label, usually used for GUIs * * @return The voice label, may not be globally unique */ - public String getLabel(); + String getLabel(); /** * Locale of the voice * * @return Locale of the voice */ - public Locale getLocale(); + Locale getLocale(); } diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/HumanLanguageInterpreter.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/HumanLanguageInterpreter.java index 607a320a7d0..9a5364d1ffd 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/HumanLanguageInterpreter.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/text/HumanLanguageInterpreter.java @@ -31,7 +31,7 @@ public interface HumanLanguageInterpreter { * * @return an id that identifies this service */ - public String getId(); + String getId(); /** * Returns a localized human readable label that can be used within UIs. @@ -39,7 +39,7 @@ public interface HumanLanguageInterpreter { * @param locale the locale to provide the label for * @return a localized string to be used in UIs */ - public String getLabel(@Nullable Locale locale); + String getLabel(@Nullable Locale locale); /** * Interprets a human language text fragment of a given {@link Locale} diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/auth/User.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/auth/User.java index 756ba6af77d..b83a7e2c763 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/auth/User.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/auth/User.java @@ -32,5 +32,5 @@ public interface User extends Principal, Identifiable { * @see Role * @return role attributed to the user */ - public Set getRoles(); + Set getRoles(); } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/auth/UserRegistry.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/auth/UserRegistry.java index cd8b5c92bbe..0a19d071986 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/auth/UserRegistry.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/auth/UserRegistry.java @@ -37,7 +37,7 @@ public interface UserRegistry extends Registry, AuthenticationProv * @param roles the roles attributed to the new user * @return the new registered {@link User} instance */ - public User register(String username, String password, Set roles); + User register(String username, String password, Set roles); /** * Change the password for a {@link User} in this registry. The implementation receives the new password and is @@ -46,7 +46,7 @@ public interface UserRegistry extends Registry, AuthenticationProv * @param username the username of the existing user * @param newPassword the new password */ - public void changePassword(User user, String newPassword); + void changePassword(User user, String newPassword); /** * Adds a new session to the user profile @@ -54,7 +54,7 @@ public interface UserRegistry extends Registry, AuthenticationProv * @param user the user * @param session the session to add */ - public void addUserSession(User user, UserSession session); + void addUserSession(User user, UserSession session); /** * Removes the specified session from the user profile @@ -62,14 +62,14 @@ public interface UserRegistry extends Registry, AuthenticationProv * @param user the user * @param session the session to remove */ - public void removeUserSession(User user, UserSession session); + void removeUserSession(User user, UserSession session); /** * Clears all sessions from the user profile * * @param user the user */ - public void clearSessions(User user); + void clearSessions(User user); /** * Adds a new API token to the user profile. The implementation is responsible for storing the token in a secure way @@ -80,7 +80,7 @@ public interface UserRegistry extends Registry, AuthenticationProv * @param scope the scope this API token will be valid for * @return the string that can be used as a Bearer token to match the new API token */ - public String addUserApiToken(User user, String name, String scope); + String addUserApiToken(User user, String name, String scope); /** * Removes the specified API token from the user profile @@ -88,5 +88,5 @@ public interface UserRegistry extends Registry, AuthenticationProv * @param user the user * @param apiToken the API token */ - public void removeUserApiToken(User user, UserApiToken apiToken); + void removeUserApiToken(User user, UserApiToken apiToken); } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/common/registry/Registry.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/common/registry/Registry.java index efcef464e26..e69176dc3f3 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/common/registry/Registry.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/common/registry/Registry.java @@ -59,7 +59,8 @@ public interface Registry<@NonNull E extends Identifiable, @NonNull K> { * @param key key of the element * @return element or null if no element was found */ - public @Nullable E get(K key); + @Nullable + E get(K key); /** * Removes a {@link RegistryChangeListener} from the registry. @@ -75,7 +76,7 @@ public interface Registry<@NonNull E extends Identifiable, @NonNull K> { * @return the added element or newly created object of the same type * @throws IllegalStateException if no ManagedProvider is available */ - public E add(E element); + E add(E element); /** * Updates the given element at the according {@link ManagedProvider}. @@ -85,7 +86,8 @@ public interface Registry<@NonNull E extends Identifiable, @NonNull K> { * exists * @throws IllegalStateException if no ManagedProvider is available */ - public @Nullable E update(E element); + @Nullable + E update(E element); /** * Removes the given element from the according {@link ManagedProvider}. @@ -95,5 +97,6 @@ public interface Registry<@NonNull E extends Identifiable, @NonNull K> { * key exists * @throws IllegalStateException if no ManagedProvider is available */ - public @Nullable E remove(K key); + @Nullable + E remove(K key); } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/events/EventSubscriber.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/events/EventSubscriber.java index 2bf1bc74d6b..bc6bd1fc009 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/events/EventSubscriber.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/events/EventSubscriber.java @@ -30,7 +30,7 @@ public interface EventSubscriber { * The constant {@link #ALL_EVENT_TYPES} must be returned by the {@link #getSubscribedEventTypes()} method, if the * event subscriber should subscribe to all event types. */ - public static String ALL_EVENT_TYPES = "ALL"; + static String ALL_EVENT_TYPES = "ALL"; /** * Gets the event types to which the event subscriber is subscribed to. diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/ActiveItem.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/ActiveItem.java index 22a11daea8c..5901f206065 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/ActiveItem.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/ActiveItem.java @@ -50,59 +50,59 @@ public interface ActiveItem extends Item { * * @param tag a tag that is to be added to item's tags. */ - public void addTag(String tag); + void addTag(String tag); /** * Adds tags to the item. * * @param tags tags that are to be added to item's tags. */ - public void addTags(String... tags); + void addTags(String... tags); /** * Adds tags to the item. * * @param tags tags that are to be added to item's tags. */ - public void addTags(Collection tags); + void addTags(Collection tags); /** * Removes a tag from the item. * * @param tag a tag that is to be removed from item's tags. */ - public void removeTag(String tag); + void removeTag(String tag); /** * Clears all tags of this item. */ - public void removeAllTags(); + void removeAllTags(); /** * Removes the according item from a group. * * @param groupItemName name of the group (must not be null) */ - public abstract void removeGroupName(String groupItemName); + void removeGroupName(String groupItemName); /** * Assigns the according item to a group. * * @param groupItemName name of the group (must not be null) */ - public abstract void addGroupName(String groupItemName); + void addGroupName(String groupItemName); /** * Assigns the according item to the given groups. * * @param groupItemNames names of the groups (must not be null) */ - public abstract void addGroupNames(String... groupItemNames); + void addGroupNames(String... groupItemNames); /** * Assigns the according item to the given groups. * * @param groupItemNames names of the groups (must not be null) */ - public abstract void addGroupNames(List groupItemNames); + void addGroupNames(List groupItemNames); } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/Item.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/Item.java index 541f89f75e3..aba02c363bb 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/Item.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/Item.java @@ -46,7 +46,7 @@ public interface Item extends Identifiable { * * @return the current state */ - public State getState(); + State getState(); /** * returns the current state of the item as a specific type @@ -54,21 +54,21 @@ public interface Item extends Identifiable { * @return the current state in the requested type or * null, if state cannot be provided as the requested type */ - public @Nullable T getStateAs(Class typeClass); + @Nullable T getStateAs(Class typeClass); /** * returns the name of the item * * @return the name of the item */ - public String getName(); + String getName(); /** * returns the item type as defined by {@link ItemFactory}s * * @return the item type */ - public String getType(); + String getType(); /** *

@@ -86,7 +86,7 @@ public interface Item extends Identifiable { * * @return a list of data types that can be used to update the item state */ - public List> getAcceptedDataTypes(); + List> getAcceptedDataTypes(); /** *

@@ -99,28 +99,29 @@ public interface Item extends Identifiable { * * @return a list of all command types that can be used for this item */ - public List> getAcceptedCommandTypes(); + List> getAcceptedCommandTypes(); /** * Returns a list of the names of the groups this item belongs to. * * @return list of item group names */ - public List getGroupNames(); + List getGroupNames(); /** * Returns a set of tags. If the item is not tagged, an empty set is returned. * * @return set of tags. */ - public Set getTags(); + Set getTags(); /** * Returns the label of the item or null if no label is set. * * @return item label or null */ - public @Nullable String getLabel(); + @Nullable + String getLabel(); /** * Returns true if the item's tags contains the specific tag, otherwise false. @@ -128,14 +129,15 @@ public interface Item extends Identifiable { * @param tag a tag whose presence in the item's tags is to be tested. * @return true if the item's tags contains the specific tag, otherwise false. */ - public boolean hasTag(String tag); + boolean hasTag(String tag); /** * Returns the category of the item or null if no category is set. * * @return category or null */ - public @Nullable String getCategory(); + @Nullable + String getCategory(); /** * Returns the first provided state description (uses the default locale). @@ -143,7 +145,8 @@ public interface Item extends Identifiable { * * @return state description (can be null) */ - public @Nullable StateDescription getStateDescription(); + @Nullable + StateDescription getStateDescription(); /** * Returns the first provided state description for a given locale. @@ -152,7 +155,8 @@ public interface Item extends Identifiable { * @param locale locale (can be null) * @return state description (can be null) */ - public @Nullable StateDescription getStateDescription(@Nullable Locale locale); + @Nullable + StateDescription getStateDescription(@Nullable Locale locale); /** * Returns the {@link CommandDescription} for this item. In case no dedicated {@link CommandDescription} is @@ -161,7 +165,7 @@ public interface Item extends Identifiable { * * @return the {@link CommandDescription} for the default locale (can be null). */ - public default @Nullable CommandDescription getCommandDescription() { + default @Nullable CommandDescription getCommandDescription() { return getCommandDescription(null); } @@ -173,5 +177,6 @@ public interface Item extends Identifiable { * @param locale locale (can be null) * @return command description (can be null) */ - public @Nullable CommandDescription getCommandDescription(@Nullable Locale locale); + @Nullable + CommandDescription getCommandDescription(@Nullable Locale locale); } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/ItemRegistry.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/ItemRegistry.java index fafc3c7ac3d..86847a6f2a6 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/ItemRegistry.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/ItemRegistry.java @@ -38,7 +38,7 @@ public interface ItemRegistry extends Registry { * @return the uniquely identified item * @throws ItemNotFoundException if no item matches the input */ - public Item getItem(String name) throws ItemNotFoundException; + Item getItem(String name) throws ItemNotFoundException; /** * This method retrieves a single item from the registry. @@ -49,14 +49,14 @@ public interface ItemRegistry extends Registry { * @throws ItemNotFoundException if no item matches the input * @throws ItemNotUniqueException if multiply items match the input */ - public Item getItemByPattern(String name) throws ItemNotFoundException, ItemNotUniqueException; + Item getItemByPattern(String name) throws ItemNotFoundException, ItemNotUniqueException; /** * This method retrieves all items that are currently available in the registry * * @return a collection of all available items */ - public Collection getItems(); + Collection getItems(); /** * This method retrieves all items with the given type @@ -64,14 +64,14 @@ public interface ItemRegistry extends Registry { * @param type item type as defined by {@link ItemFactory}s * @return a collection of all items of the given type */ - public Collection getItemsOfType(String type); + Collection getItemsOfType(String type); /** * This method retrieves all items that match a given search pattern * * @return a collection of all items matching the search pattern */ - public Collection getItems(String pattern); + Collection getItems(String pattern); /** * Returns list of items which contains all of the given tags. @@ -79,7 +79,7 @@ public interface ItemRegistry extends Registry { * @param tags array of tags to be present on the returned items. * @return list of items which contains all of the given tags. */ - public Collection getItemsByTag(String... tags); + Collection getItemsByTag(String... tags); /** * Returns list of items with a certain type containing all of the given tags. @@ -88,7 +88,7 @@ public interface ItemRegistry extends Registry { * @param tags array of tags to be present on the returned items. * @return list of items which contains all of the given tags. */ - public Collection getItemsByTagAndType(String type, String... tags); + Collection getItemsByTagAndType(String type, String... tags); /** * Returns list of items which contains all of the given tags. @@ -98,12 +98,13 @@ public interface ItemRegistry extends Registry { * @return list of items which contains all of the given tags, which is * filtered by the given type filter. */ - public Collection getItemsByTag(Class typeFilter, String... tags); + Collection getItemsByTag(Class typeFilter, String... tags); /** * @see ManagedItemProvider#remove(String, boolean) */ - public @Nullable Item remove(String itemName, boolean recursive); + @Nullable + Item remove(String itemName, boolean recursive); /** * Add a hook to be informed before adding/after removing items. diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/MetadataRegistry.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/MetadataRegistry.java index d6734c41c54..5f5d098611e 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/MetadataRegistry.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/MetadataRegistry.java @@ -28,7 +28,7 @@ @NonNullByDefault public interface MetadataRegistry extends Registry { - public static final String INTERNAL_NAMESPACE_PREFIX = "_"; + static final String INTERNAL_NAMESPACE_PREFIX = "_"; /** * Determines whether the given namespace is internal. @@ -43,7 +43,7 @@ public interface MetadataRegistry extends Registry { * * @param itemname the name of the item for which the namespaces should be searched. */ - public Collection getAllNamespaces(String itemname); + Collection getAllNamespaces(String itemname); /** * Remove all metadata of a given item diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/StateChangeListener.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/StateChangeListener.java index 98dc0f2c2cc..e910f5c20f0 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/StateChangeListener.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/StateChangeListener.java @@ -34,7 +34,7 @@ public interface StateChangeListener { * @param oldState the previous state * @param newState the new state */ - public void stateChanged(Item item, State oldState, State newState); + void stateChanged(Item item, State oldState, State newState); /** * This method is called, if a state was updated, but has not changed @@ -42,5 +42,5 @@ public interface StateChangeListener { * @param item the item whose state was updated * @param state the current state, same before and after the update */ - public void stateUpdated(Item item, State state); + void stateUpdated(Item item, State state); } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/net/NetworkAddressService.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/net/NetworkAddressService.java index 9266b13a8c5..335f510dd91 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/net/NetworkAddressService.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/net/NetworkAddressService.java @@ -62,12 +62,12 @@ public interface NetworkAddressService { * * @param listener The listener */ - public void addNetworkAddressChangeListener(NetworkAddressChangeListener listener); + void addNetworkAddressChangeListener(NetworkAddressChangeListener listener); /** * Removes a {@link NetworkAddressChangeListener} so that it is no longer notified about changes. * * @param listener The listener */ - public void removeNetworkAddressChangeListener(NetworkAddressChangeListener listener); + void removeNetworkAddressChangeListener(NetworkAddressChangeListener listener); } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/scheduler/CronJob.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/scheduler/CronJob.java index 5e9eaaee749..023b4d2012d 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/scheduler/CronJob.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/scheduler/CronJob.java @@ -34,5 +34,5 @@ public interface CronJob { * @param data The data for the job * @throws Exception Exception thrown */ - public void run(Map data) throws Exception; + void run(Map data) throws Exception; } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/types/ComplexType.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/types/ComplexType.java index e685b6eefc8..9d0318cd9e4 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/types/ComplexType.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/types/ComplexType.java @@ -30,5 +30,5 @@ public interface ComplexType extends Type { * * @return all constituents with their names */ - public SortedMap getConstituents(); + SortedMap getConstituents(); } From 4349be69456bd5371a22d3ea617bc3dee8518b6e Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Thu, 20 Apr 2023 20:36:44 +0200 Subject: [PATCH 048/126] Redefine connection in addon.xsd (#3563) * Redefine addon.xml connection * Remove remains of author Signed-off-by: Jacob Laursen --- .../schema/addon-1.0.0.xsd | 23 ++++++++++++++++--- .../org/openhab/core/addon/AddonInfo.java | 16 ++++--------- .../OH-INF/addon/addon.xml | 1 + .../acmeweather.bundle/OH-INF/addon/addon.xml | 1 + 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd index 5af9bacf6bd..21142133d17 100644 --- a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd +++ b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd @@ -51,9 +51,26 @@ - - - + + + No interaction with external systems at all + + + + + Interaction with external systems, without internet access + + + + + Interaction with external systems, internet access required only for extended functionality (such as discovery) + + + + + Interaction with external systems, internet access required for normal operation + + diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java index 7094871fbc5..25c7593878b 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java @@ -46,9 +46,9 @@ public class AddonInfo implements Identifiable { private final String serviceId; private @Nullable String sourceBundle; - private AddonInfo(String id, String type, String name, String description, @Nullable String author, - @Nullable String connection, List countries, @Nullable String configDescriptionURI, - @Nullable String serviceId, @Nullable String sourceBundle) throws IllegalArgumentException { + private AddonInfo(String id, String type, String name, String description, @Nullable String connection, + List countries, @Nullable String configDescriptionURI, @Nullable String serviceId, + @Nullable String sourceBundle) throws IllegalArgumentException { // mandatory fields if (id.isBlank()) { throw new IllegalArgumentException("The ID must neither be null nor empty!"); @@ -153,7 +153,6 @@ public static class Builder { private final String type; private String name = ""; private String description = ""; - private @Nullable String author; private @Nullable String connection; private List countries = List.of(); private @Nullable String configDescriptionURI = ""; @@ -187,11 +186,6 @@ public Builder withDescription(String description) { return this; } - public Builder withAuthor(@Nullable String author) { - this.author = author; - return this; - } - public Builder withConnection(@Nullable String connection) { this.connection = connection; return this; @@ -229,8 +223,8 @@ public Builder withSourceBundle(@Nullable String sourceBundle) { * @throws IllegalArgumentException if any of the information in this builder is invalid */ public AddonInfo build() throws IllegalArgumentException { - return new AddonInfo(id, type, name, description, author, connection, countries, configDescriptionURI, - serviceId, sourceBundle); + return new AddonInfo(id, type, name, description, connection, countries, configDescriptionURI, serviceId, + sourceBundle); } } } diff --git a/itests/org.openhab.core.addon.tests/src/main/resources/test-bundle-pool/BundleInfoTest.bundle/OH-INF/addon/addon.xml b/itests/org.openhab.core.addon.tests/src/main/resources/test-bundle-pool/BundleInfoTest.bundle/OH-INF/addon/addon.xml index 0fec4bf2ffc..4d351403227 100644 --- a/itests/org.openhab.core.addon.tests/src/main/resources/test-bundle-pool/BundleInfoTest.bundle/OH-INF/addon/addon.xml +++ b/itests/org.openhab.core.addon.tests/src/main/resources/test-bundle-pool/BundleInfoTest.bundle/OH-INF/addon/addon.xml @@ -7,6 +7,7 @@ hue Binding The hue Binding integrates the Philips hue system. It allows to control hue lights. + local diff --git a/itests/org.openhab.core.addon.tests/src/main/resources/test-bundle-pool/acmeweather.bundle/OH-INF/addon/addon.xml b/itests/org.openhab.core.addon.tests/src/main/resources/test-bundle-pool/acmeweather.bundle/OH-INF/addon/addon.xml index 1fd540c81aa..543cf1ccc69 100644 --- a/itests/org.openhab.core.addon.tests/src/main/resources/test-bundle-pool/acmeweather.bundle/OH-INF/addon/addon.xml +++ b/itests/org.openhab.core.addon.tests/src/main/resources/test-bundle-pool/acmeweather.bundle/OH-INF/addon/addon.xml @@ -7,5 +7,6 @@ ACME Weather Binding The ACME Weather Binding requests the ACME Weather Service to show the current temperature, humidity and pressure. + cloud From bc922022c30b40659ae6408ad7f6a83cbc2ebe21 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Fri, 21 Apr 2023 06:20:30 +1000 Subject: [PATCH 049/126] [REST] List semantic tags (#3559) Signed-off-by: Jimmy Tanagra --- .../io/rest/core/internal/tag/TagDTO.java | 38 +++++++ .../rest/core/internal/tag/TagResource.java | 106 ++++++++++++++++++ .../core/io/rest/LocaleServiceImpl.java | 4 +- 3 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagDTO.java create mode 100644 bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagResource.java diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagDTO.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagDTO.java new file mode 100644 index 00000000000..37d68f17cec --- /dev/null +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagDTO.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.rest.core.internal.tag; + +import java.util.List; +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.semantics.SemanticTags; +import org.openhab.core.semantics.Tag; + +/** + * A DTO representing a Semantic {@link Tag}. + * + * @author Jimmy Tanagra - initial contribution + */ +@NonNullByDefault +public class TagDTO { + String name; + String label; + List synonyms; + + public TagDTO(Class tag, Locale locale) { + this.name = tag.getSimpleName(); + this.label = SemanticTags.getLabel(tag, locale); + this.synonyms = SemanticTags.getSynonyms(tag, locale); + } +} diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagResource.java new file mode 100644 index 00000000000..ef0b6f95d66 --- /dev/null +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagResource.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.rest.core.internal.tag; + +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.annotation.security.RolesAllowed; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.auth.Role; +import org.openhab.core.io.rest.JSONResponse; +import org.openhab.core.io.rest.LocaleService; +import org.openhab.core.io.rest.RESTConstants; +import org.openhab.core.io.rest.RESTResource; +import org.openhab.core.semantics.model.equipment.Equipments; +import org.openhab.core.semantics.model.location.Locations; +import org.openhab.core.semantics.model.point.Points; +import org.openhab.core.semantics.model.property.Properties; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants; +import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired; +import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; +import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; +import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; + +/** + * This class acts as a REST resource for retrieving a list of tags. + * + * @author Jimmy Tanagra - Initial contribution + */ +@Component +@JaxrsResource +@JaxrsName(TagResource.PATH_TAGS) +@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") +@JSONRequired +@Path(TagResource.PATH_TAGS) +@io.swagger.v3.oas.annotations.tags.Tag(name = TagResource.PATH_TAGS) +@NonNullByDefault +public class TagResource implements RESTResource { + + /** The URI path to this resource */ + public static final String PATH_TAGS = "tags"; + + private final Logger logger = LoggerFactory.getLogger(TagResource.class); + + private final LocaleService localeService; + + @Activate + public TagResource(final @Reference LocaleService localeService) { + this.localeService = localeService; + } + + @GET + @RolesAllowed({ Role.USER, Role.ADMIN }) + @Produces(MediaType.APPLICATION_JSON) + @Operation(operationId = "getTags", summary = "Get all available tags.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = TagDTO.class)))) }) + public Response getTags(final @Context UriInfo uriInfo, final @Context HttpHeaders httpHeaders, + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) { + + final Locale locale = localeService.getLocale(language); + + Map> tags = Map.of( // + Locations.class.getSimpleName(), Locations.stream().map(tag -> new TagDTO(tag, locale)).toList(), // + Equipments.class.getSimpleName(), Equipments.stream().map(tag -> new TagDTO(tag, locale)).toList(), // + Points.class.getSimpleName(), Points.stream().map(tag -> new TagDTO(tag, locale)).toList(), // + Properties.class.getSimpleName(), Properties.stream().map(tag -> new TagDTO(tag, locale)).toList() // + ); + + return JSONResponse.createResponse(Status.OK, tags, null); + } +} diff --git a/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/LocaleServiceImpl.java b/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/LocaleServiceImpl.java index 383a3b2fac0..385053eec81 100644 --- a/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/LocaleServiceImpl.java +++ b/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/LocaleServiceImpl.java @@ -30,7 +30,7 @@ * @author Dennis Nobel - Initial contribution * @author Markus Rathgeb - Use locale provider * @author Martin Herbst - Support of different language definition variants - * @authro Lyubomir Papazov - Add component annotation, rename the class to LocaleService and add method tryGetLocale + * @author Lyubomir Papazov - Add component annotation, rename the class to LocaleService and add method tryGetLocale */ @Component @NonNullByDefault @@ -81,7 +81,7 @@ private Locale tryGetLocale() { if (provider != null) { return provider.getLocale(); } else { - logger.error("There should ALWAYS be a local provider available, as it is provided by the core."); + logger.error("There should ALWAYS be a locale provider available, as it is provided by the core."); return Locale.US; } } From da5ac36a68903b9fb98163f7cf746fb2ab94c759 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 21 Apr 2023 20:31:16 +0100 Subject: [PATCH 050/126] Improve precision of ColorUtil (#3542) * [ColorUtil] add fine precision methods and tests * [ColorUtil] set precision to 5 Signed-off-by: Andrew Fiddian-Green --- .../java/org/openhab/core/util/ColorUtil.java | 158 +++++++++++------- .../core/library/types/HSBTypeTest.java | 13 +- .../org/openhab/core/util/ColorUtilTest.java | 110 +++++++----- 3 files changed, 168 insertions(+), 113 deletions(-) diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/util/ColorUtil.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/util/ColorUtil.java index 229e6ddf113..6fe22d063a7 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/util/ColorUtil.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/util/ColorUtil.java @@ -13,6 +13,7 @@ package org.openhab.core.util; import java.math.BigDecimal; +import java.math.MathContext; import java.math.RoundingMode; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -36,6 +37,7 @@ @NonNullByDefault public class ColorUtil { private static final Logger LOGGER = LoggerFactory.getLogger(ColorUtil.class); + private static final MathContext COLOR_MATH_CONTEXT = new MathContext(5, RoundingMode.HALF_UP); protected static final BigDecimal BIG_DECIMAL_HUNDRED = BigDecimal.valueOf(100); public static final Gamut DEFAULT_GAMUT = new Gamut(new double[] { 0.9961, 0.0001 }, new double[] { 0, 0.9961 }, new double[] { 0, 0.0001 }); @@ -50,7 +52,10 @@ private ColorUtil() { * * This function does rounding to integer valued components. It is the preferred way of doing HSB to RGB conversion. * - * See also: {@link hsbToRgbPercent(HSBType)}, {@link hsbTosRGB(HSBType)} + * See also: {@link #hsbToRgbPercent(HSBType)}, {@link #hsbTosRgb(HSBType)} + * + * @param hsb an {@link HSBType} value. + * @return array of three int with the RGB values in the range 0 to 255. */ public static int[] hsbToRgb(HSBType hsb) { final PercentType[] rgbPercent = hsbToRgbPercent(hsb); @@ -62,10 +67,13 @@ public static int[] hsbToRgb(HSBType hsb) { * Transform HSV based {@link HSBType} to * sRGB. * - * This function not round the components to integer values. Please consider consider - * using {@link hsbToRgb(HSBType)} whenever integer values are required. + * This function does not round the components. For conversion to integer values in the range 0 to 255 use + * {@link #hsbToRgb(HSBType)}. + * + * See also: {@link #hsbToRgb(HSBType)}, {@link #hsbTosRgb(HSBType)} * - * See also: {@link hsbToRgb(HSBType)}, {@link hsbTosRgb(HSBType)} + * @param hsb an {@link HSBType} value. + * @return array of three {@link PercentType} with the RGB values in the range 0 to 100 percent. */ public static PercentType[] hsbToRgbPercent(HSBType hsb) { PercentType red = null; @@ -79,6 +87,7 @@ public static PercentType[] hsbToRgbPercent(HSBType hsb) { final BigDecimal f = h.multiply(BigDecimal.valueOf(5)).divide(BigDecimal.valueOf(3), 10, RoundingMode.HALF_UP) .remainder(BigDecimal.ONE); final BigDecimal value = hsb.getBrightness().toBigDecimal(); + PercentType a = new PercentType(value.multiply(BigDecimal.ONE.subtract(s))); PercentType b = new PercentType(value.multiply(BigDecimal.ONE.subtract(s.multiply(f)))); PercentType c = new PercentType( @@ -128,9 +137,10 @@ public static PercentType[] hsbToRgbPercent(HSBType hsb) { * sRGB color model. * (Bits 24-31 are alpha, 16-23 are red, 8-15 are green, 0-7 are blue). * - * See also: {@link hsbToRgb(HSBType)}, {@link hsbToRgbPercent(HSBType)} + * See also: {@link #hsbToRgb(HSBType)}, {@link #hsbToRgbPercent(HSBType)} * - * @return the RGB value of the color in the default sRGB color model + * @param hsb an {@link HSBType} value. + * @return the RGB value of the color in the default sRGB color model. */ public static int hsbTosRgb(HSBType hsb) { final int[] rgb = hsbToRgb(hsb); @@ -145,11 +155,11 @@ public static int hsbTosRgb(HSBType hsb) { * "https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/">Hue * developerportal. * - * @param hsbType a {@link HSBType} value - * @return double array with the closest matching CIE 1931 color, x, y between 0.0000 and 1.0000. + * @param hsb an {@link HSBType} value. + * @return array of three double with the closest matching CIE 1931 x,y,Y in the range 0.0000 to 1.0000 */ - public static double[] hsbToXY(HSBType hsbType) { - return hsbToXY(hsbType, DEFAULT_GAMUT); + public static double[] hsbToXY(HSBType hsb) { + return hsbToXY(hsb, DEFAULT_GAMUT); } /** @@ -160,12 +170,12 @@ public static double[] hsbToXY(HSBType hsbType) { * "https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/">Hue * developer portal. * - * @param hsbType a {@link HSBType} value - * @param gamut the gamut supported by the light. - * @return double array with the closest matching CIE 1931 color, x, y, Y between 0.0000 and 1.0000. + * @param hsb an {@link HSBType} value. + * @param gamut the color Gamut supported by the light. + * @return array of three double with the closest matching CIE 1931 x,y,Y in the range 0.0000 to 1.0000 */ - public static double[] hsbToXY(HSBType hsbType, Gamut gamut) { - PercentType[] rgb = hsbToRgbPercent(hsbType); + public static double[] hsbToXY(HSBType hsb, Gamut gamut) { + PercentType[] rgb = hsbToRgbPercent(hsb); double r = inverseCompand(rgb[0].doubleValue() / PercentType.HUNDRED.doubleValue()); double g = inverseCompand(rgb[1].doubleValue() / PercentType.HUNDRED.doubleValue()); double b = inverseCompand(rgb[2].doubleValue() / PercentType.HUNDRED.doubleValue()); @@ -181,8 +191,10 @@ public static double[] hsbToXY(HSBType hsbType, Gamut gamut) { double[] xyY = new double[] { ((int) (q.x * 10000.0)) / 10000.0, ((int) (q.y * 10000.0)) / 10000.0, ((int) (Y * 10000.0)) / 10000.0 }; - LOGGER.trace("HSB: {} - RGB: {} - XYZ: {} {} {} - xyY: {}", hsbType, hsbType.toRGB(), X, Y, Z, xyY); - + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("HSB: {} - RGB: {} - XYZ: {} {} {} - xyY: {}", hsb, ColorUtil.hsbToRgbPercent(hsb), X, Y, Z, + xyY); + } return xyY; } @@ -190,56 +202,67 @@ public static double[] hsbToXY(HSBType hsbType, Gamut gamut) { * Transform sRGB color format to * HSV based {@link HSBType}. * - * Note: Conversion result is rounded and HSBType is created with integer valued components. - * - * @param rgb int array of length 3, all values are constrained to 0-255 + * @param rgb array of three int with the RGB values in the range 0 to 255. * @return the corresponding {@link HSBType}. - * @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range + * @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range. */ public static HSBType rgbToHsb(int[] rgb) throws IllegalArgumentException { if (rgb.length != 3 || !inByteRange(rgb[0]) || !inByteRange(rgb[1]) || !inByteRange(rgb[2])) { throw new IllegalArgumentException("RGB array only allows values between 0 and 255"); } - final int r = rgb[0]; - final int g = rgb[1]; - final int b = rgb[2]; + return rgbToHsb(new PercentType[] { convertByteToColorPercent(rgb[0]), convertByteToColorPercent(rgb[1]), + convertByteToColorPercent(rgb[2]) }); + } - int max = (r > g) ? r : g; - if (b > max) { - max = b; + /** + * Transform sRGB color format to + * HSV based {@link HSBType}. + * + * @param rgb array of three {@link PercentType] with the RGB values in the range 0 to 100 percent. + * @return the corresponding {@link HSBType}. + * @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range. + */ + public static HSBType rgbToHsb(PercentType[] rgb) throws IllegalArgumentException { + if (rgb.length != 3) { + throw new IllegalArgumentException("RGB array needs exactly three values!"); } - int min = (r < g) ? r : g; - if (b < min) { - min = b; + + BigDecimal r = rgb[0].toBigDecimal(); + BigDecimal g = rgb[1].toBigDecimal(); + BigDecimal b = rgb[2].toBigDecimal(); + + BigDecimal max = r.max(g).max(b); + BigDecimal min = r.min(g).min(b); + BigDecimal span = max.subtract(min); + + if (max.compareTo(BigDecimal.ZERO) == 0) { // all values are 0, return black + return new HSBType(); + } else if (span.compareTo(BigDecimal.ZERO) == 0) { // all values are equal, return dimmed white + return new HSBType(new DecimalType(), new PercentType(), new PercentType(max)); } - float tmpHue; - final float tmpBrightness = max / 2.55f; - final float tmpSaturation = (max != 0 ? ((float) (max - min)) / ((float) max) : 0) * 100.0f; - // smallest possible saturation: 0 (max=0 or max-min=0), other value closest to 0 is 100/255 (max=255, min=254) - // -> avoid float comparision to 0 - // if (tmpSaturation == 0) { - if (max == 0 || (max - min) == 0) { - tmpHue = 0; + + PercentType saturation = new PercentType(span.divide(max, COLOR_MATH_CONTEXT).multiply(BIG_DECIMAL_HUNDRED)); + PercentType brightness = new PercentType(max); + + BigDecimal scale = span.divide(BigDecimal.valueOf(60), COLOR_MATH_CONTEXT); + + BigDecimal redAngle = max.subtract(r).divide(scale, COLOR_MATH_CONTEXT); + BigDecimal greenAngle = max.subtract(g).divide(scale, COLOR_MATH_CONTEXT); + BigDecimal blueAngle = max.subtract(b).divide(scale, COLOR_MATH_CONTEXT); + + BigDecimal hue; + if (r.compareTo(max) == 0) { + hue = blueAngle.subtract(greenAngle); + } else if (g.compareTo(max) == 0) { + hue = BigDecimal.valueOf(120).add(redAngle).subtract(blueAngle); } else { - float red = ((float) (max - r)) / ((float) (max - min)); - float green = ((float) (max - g)) / ((float) (max - min)); - float blue = ((float) (max - b)) / ((float) (max - min)); - if (r == max) { - tmpHue = blue - green; - } else if (g == max) { - tmpHue = 2.0f + red - blue; - } else { - tmpHue = 4.0f + green - red; - } - tmpHue = tmpHue / 6.0f * 360; - if (tmpHue < 0) { - tmpHue = tmpHue + 360.0f; - } + hue = BigDecimal.valueOf(240).add(greenAngle).subtract(redAngle); + } + if (hue.compareTo(BigDecimal.ZERO) < 0) { + hue = hue.add(BigDecimal.valueOf(360)); } - // adding 0.5 and casting to int approximates rounding - return new HSBType(new DecimalType((int) (tmpHue + .5f)), new PercentType((int) (tmpSaturation + .5f)), - new PercentType((int) (tmpBrightness + .5f))); + return new HSBType(new DecimalType(hue), saturation, brightness); } /** @@ -250,9 +273,9 @@ public static HSBType rgbToHsb(int[] rgb) throws IllegalArgumentException { * "https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/">Hue * developer portal. * - * @param xy the CIE 1931 xy color, x,y between 0.0000 and 1.0000. + * @param xy array of double with CIE 1931 x,y[,Y] in the range 0.0000 to 1.0000 Y value is optional. * @return the corresponding {@link HSBType}. - * @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range + * @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range. */ public static HSBType xyToHsb(double[] xy) throws IllegalArgumentException { return xyToHsb(xy, DEFAULT_GAMUT); @@ -266,15 +289,15 @@ public static HSBType xyToHsb(double[] xy) throws IllegalArgumentException { * "https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/">Hue * developer portal. * - * @param xy the CIE 1931 xy color, x,y[,Y] between 0.0000 and 1.0000. Y value is optional. - * @param gamut the gamut supported by the light. + * @param xy array of double with CIE 1931 x,y[,Y] in the range 0.0000 to 1.0000 Y value is optional. + * @param gamut the color Gamut supported by the light. * @return the corresponding {@link HSBType}. * @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range */ public static HSBType xyToHsb(double[] xy, Gamut gamut) throws IllegalArgumentException { if (xy.length < 2 || xy.length > 3 || !inRange(xy[0]) || !inRange(xy[1]) || (xy.length == 3 && !inRange(xy[2]))) { - throw new IllegalArgumentException("xy array only allowes two or three values between 0.0 and 1.0."); + throw new IllegalArgumentException("xy array only allows two or three values between 0.0 and 1.0."); } Point p = gamut.closest(new Point(xy[0], xy[1])); double x = p.x; @@ -315,11 +338,10 @@ public static HSBType xyToHsb(double[] xy, Gamut gamut) throws IllegalArgumentEx b /= max; } - HSBType hsb = rgbToHsb( - new int[] { (int) Math.round(255.0 * r), (int) Math.round(255.0 * g), (int) Math.round(255.0 * b) }); - LOGGER.trace("xy: {} - XYZ: {} {} {} - RGB: {} {} {} - HSB: {} ", xy, X, Y, Z, r, g, b, hsb); + LOGGER.trace("xy: {} - XYZ: {} {} {} - RGB: {} {} {}", xy, X, Y, Z, r, g, b); - return hsb; + return rgbToHsb(new PercentType[] { convertDoubleToColorPercent(r), convertDoubleToColorPercent(g), + convertDoubleToColorPercent(b) }); } /** @@ -466,4 +488,12 @@ private static int convertColorPercentToByte(PercentType percent) { return percent.toBigDecimal().multiply(BigDecimal.valueOf(255)) .divide(BIG_DECIMAL_HUNDRED, 0, RoundingMode.HALF_UP).intValue(); } + + private static PercentType convertByteToColorPercent(int b) { + return new PercentType(new BigDecimal(b).divide(new BigDecimal("2.55"), COLOR_MATH_CONTEXT)); + } + + private static PercentType convertDoubleToColorPercent(double d) { + return new PercentType(new BigDecimal(d).multiply(BIG_DECIMAL_HUNDRED, COLOR_MATH_CONTEXT)); + } } diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/HSBTypeTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/HSBTypeTest.java index fc2df6716fa..43ce328e0ae 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/HSBTypeTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/HSBTypeTest.java @@ -70,10 +70,11 @@ private int convertPercentToByte(PercentType percent) { private void compareHsbToRgbValues(String hsbValues, int red, int green, int blue) { HSBType hsb = new HSBType(hsbValues); + HSBType hsbRgb = HSBType.fromRGB(red, green, blue); - assertEquals(red, convertPercentToByte(hsb.getRed())); - assertEquals(green, convertPercentToByte(hsb.getGreen())); - assertEquals(blue, convertPercentToByte(hsb.getBlue())); + assertEquals(hsb.getHue().doubleValue(), hsbRgb.getHue().doubleValue(), 0.5); + assertEquals(hsb.getSaturation().doubleValue(), hsbRgb.getSaturation().doubleValue(), 0.5); + assertEquals(hsb.getBrightness().doubleValue(), hsbRgb.getBrightness().doubleValue(), 0.5); } @Test @@ -93,9 +94,9 @@ private void compareRgbToHsbValues(String hsbValues, int red, int green, int blu HSBType hsb = new HSBType(hsbValues); HSBType hsbRgb = HSBType.fromRGB(red, green, blue); - assertEquals(hsb.getHue(), hsbRgb.getHue()); - assertEquals(hsb.getSaturation(), hsbRgb.getSaturation()); - assertEquals(hsb.getBrightness(), hsbRgb.getBrightness()); + assertEquals(hsb.getHue().doubleValue(), hsbRgb.getHue().doubleValue(), 0.5); + assertEquals(hsb.getSaturation().doubleValue(), hsbRgb.getSaturation().doubleValue(), 0.5); + assertEquals(hsb.getBrightness().doubleValue(), hsbRgb.getBrightness().doubleValue(), 0.5); } @Test diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/util/ColorUtilTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/util/ColorUtilTest.java index 297f9cd4bab..21acc54d127 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/util/ColorUtilTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/util/ColorUtilTest.java @@ -13,9 +13,7 @@ package org.openhab.core.util; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.lessThan; -import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.hamcrest.Matchers.*; import static org.junit.jupiter.api.Assertions.*; import java.util.stream.Stream; @@ -32,6 +30,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.PercentType; +import org.openhab.core.util.ColorUtil.Gamut; /** * The {@link ColorUtilTest} is a test class for the color conversion @@ -52,8 +51,10 @@ public void inversionTest(HSBType hsb) { deltaHue = deltaHue > 180.0 ? Math.abs(deltaHue - 360) : deltaHue; double deltaSat = Math.abs(hsb.getSaturation().doubleValue() - hsb2.getSaturation().doubleValue()); double deltaBri = Math.abs(hsb.getBrightness().doubleValue() - hsb2.getBrightness().doubleValue()); - - assertThat(deltaHue, is(lessThan(5.0))); + // hue is meaningless when saturation is zero + if (hsb.getSaturation().doubleValue() > 0) { + assertThat(deltaHue, is(lessThan(5.0))); + } assertThat(deltaSat, is(lessThanOrEqualTo(1.0))); assertThat(deltaBri, is(lessThanOrEqualTo(1.0))); } @@ -75,12 +76,12 @@ public void testConversionToXY() { // test RGB -> HSB -> RGB conversion for different values, including the ones known to cause rounding error @ParameterizedTest @ArgumentsSource(RgbValueProvider.class) - public void testConversionRgbToHsbToRgb(int[] rgb, int maxSquaredSum) { + public void testConversionRgbToHsbToRgb(int[] rgb) { HSBType hsb = ColorUtil.rgbToHsb(rgb); Assertions.assertNotNull(hsb); final int[] convertedRgb = ColorUtil.hsbToRgb(hsb); - assertRgbEquals(rgb, convertedRgb, maxSquaredSum); + assertRgbEquals(rgb, convertedRgb); } @ParameterizedTest @@ -90,7 +91,7 @@ public void testConversionHsbToRgb(int[] hsb, int[] rgb) { final HSBType hsbType = new HSBType(hsbString); final int[] converted = ColorUtil.hsbToRgb(hsbType); - assertRgbEquals(rgb, converted, 0); + assertRgbEquals(rgb, converted); } @ParameterizedTest @@ -99,7 +100,7 @@ public void testConversionRgbToRgb(int[] hsb, int[] rgb) { final HSBType hsbType = ColorUtil.rgbToHsb(rgb); final int[] rgbConverted = ColorUtil.hsbToRgb(hsbType); - assertRgbEquals(rgb, rgbConverted, 0); + assertRgbEquals(rgb, rgbConverted); } @ParameterizedTest @@ -113,11 +114,47 @@ public void testConversionRgbToHsb(int[] hsb, int[] rgb) { assertTrue(hsbType.closeTo(new HSBType(expected), 0.01)); } + private void xyToXY(double[] xy, Gamut gamut) { + assertTrue(xy.length > 1); + HSBType hsb = ColorUtil.xyToHsb(xy, gamut); + double[] xy2 = ColorUtil.hsbToXY(hsb, gamut); + assertTrue(xy2.length > 1); + for (int i = 0; i < xy.length; i++) { + assertEquals(xy[i], xy2[i], 0.02); + } + } + + /** + * Test XY -> RGB -> HSB - RGB - XY round trips. + * Use ColorUtil fine precision methods for conversions. + * Test on Hue standard Gamuts 'A', 'B', and 'C'. + */ + @Test + public void testXyHsbRoundTrips() { + Gamut[] gamuts = new Gamut[] { + new Gamut(new double[] { 0.704, 0.296 }, new double[] { 0.2151, 0.7106 }, new double[] { 0.138, 0.08 }), + new Gamut(new double[] { 0.675, 0.322 }, new double[] { 0.409, 0.518 }, new double[] { 0.167, 0.04 }), + new Gamut(new double[] { 0.6915, 0.3038 }, new double[] { 0.17, 0.7 }, new double[] { 0.1532, 0.0475 }) // + }; + for (Gamut g : gamuts) { + xyToXY(g.r(), g); + xyToXY(g.g(), g); + xyToXY(g.b(), g); + xyToXY(new double[] { (g.r()[0] + g.g()[0]) / 2f, (g.r()[1] + g.g()[1]) / 2f }, g); + xyToXY(new double[] { (g.g()[0] + g.b()[0]) / 2f, (g.g()[1] + g.b()[1]) / 2f }, g); + xyToXY(new double[] { (g.b()[0] + g.r()[0]) / 2f, (g.b()[1] + g.r()[1]) / 2f }, g); + xyToXY(new double[] { (g.r()[0] + g.g()[0] + g.b()[0]) / 3f, (g.r()[1] + g.g()[1] + g.b()[1]) / 3f }, g); + xyToXY(ColorUtil.hsbToXY(HSBType.WHITE), g); + } + } + /* Providers for parameterized tests */ private static Stream colors() { - return Stream.of(HSBType.BLACK, HSBType.BLUE, HSBType.GREEN, HSBType.RED, HSBType.WHITE, - ColorUtil.rgbToHsb(new int[] { 127, 94, 19 })).map(Arguments::of); + return Stream + .of(HSBType.BLACK, HSBType.BLUE, HSBType.GREEN, HSBType.RED, HSBType.WHITE, + ColorUtil.rgbToHsb(new int[] { 127, 94, 19 }), new HSBType("0,0.1,0"), new HSBType("0,0.1,100")) + .map(Arguments::of); } private static Stream invalids() { @@ -152,55 +189,42 @@ public Stream provideArguments(@Nullable ExtensionContext c } /* - * Return a stream RGB values together with allowed deviation (sum of squared differences). - * Differences in conversion are due to rounding errors as HSBType is created with integer numbers. + * Return a stream RGB values. */ - static class RgbValueProvider implements ArgumentsProvider { @Override public Stream provideArguments(@Nullable ExtensionContext context) throws Exception { - return Stream.of(Arguments.of(new int[] { 0, 0, 0 }, 0), Arguments.of(new int[] { 255, 255, 255 }, 0), - Arguments.of(new int[] { 255, 0, 0 }, 0), Arguments.of(new int[] { 0, 255, 0 }, 0), - Arguments.of(new int[] { 0, 0, 255 }, 0), Arguments.of(new int[] { 255, 255, 0 }, 0), - Arguments.of(new int[] { 255, 0, 255 }, 0), Arguments.of(new int[] { 0, 255, 255 }, 0), - Arguments.of(new int[] { 191, 191, 191 }, 0), Arguments.of(new int[] { 128, 128, 128 }, 0), - Arguments.of(new int[] { 128, 0, 0 }, 0), Arguments.of(new int[] { 128, 128, 0 }, 0), - Arguments.of(new int[] { 0, 128, 0 }, 0), Arguments.of(new int[] { 128, 0, 128 }, 0), - Arguments.of(new int[] { 0, 128, 128 }, 0), Arguments.of(new int[] { 0, 0, 128 }, 0), - Arguments.of(new int[] { 0, 132, 255 }, 0), Arguments.of(new int[] { 1, 131, 254 }, 3), - Arguments.of(new int[] { 2, 130, 253 }, 6), Arguments.of(new int[] { 3, 129, 252 }, 4), - Arguments.of(new int[] { 4, 128, 251 }, 3), Arguments.of(new int[] { 5, 127, 250 }, 0)); + return Stream.of(Arguments.of(new int[] { 0, 0, 0 }), Arguments.of(new int[] { 255, 255, 255 }), + Arguments.of(new int[] { 255, 0, 0 }), Arguments.of(new int[] { 0, 255, 0 }), + Arguments.of(new int[] { 0, 0, 255 }), Arguments.of(new int[] { 255, 255, 0 }), + Arguments.of(new int[] { 255, 0, 255 }), Arguments.of(new int[] { 0, 255, 255 }), + Arguments.of(new int[] { 191, 191, 191 }), Arguments.of(new int[] { 128, 128, 128 }), + Arguments.of(new int[] { 128, 0, 0 }), Arguments.of(new int[] { 128, 128, 0 }), + Arguments.of(new int[] { 0, 128, 0 }), Arguments.of(new int[] { 128, 0, 128 }), + Arguments.of(new int[] { 0, 128, 128 }), Arguments.of(new int[] { 0, 0, 128 }), + Arguments.of(new int[] { 0, 132, 255 }), Arguments.of(new int[] { 1, 131, 254 }), + Arguments.of(new int[] { 2, 130, 253 }), Arguments.of(new int[] { 3, 129, 252 }), + Arguments.of(new int[] { 4, 128, 251 }), Arguments.of(new int[] { 5, 127, 250 })); } } /* Helper functions */ /** - * Helper method for checking if expected and actual RGB color parameters (int[3], 0..255) lie within a given - * percentage of each other. This method is required in order to eliminate integer rounding artifacts in JUnit tests - * when comparing RGB values. Asserts that the color parameters of expected and actual do not have a squared sum - * of differences which exceeds maxSquaredSum. + * Helper method for checking if expected and actual RGB color parameters (int[3], 0..255) match. * * When the test fails, both colors are printed. * * @param expected an HSBType containing the expected color. * @param actual an HSBType containing the actual color. - * @param maxSquaredSum the maximum allowed squared sum of differences. */ - private void assertRgbEquals(final int[] expected, final int[] actual, int maxSquaredSum) { - int squaredSum = 0; + private void assertRgbEquals(final int[] expected, final int[] actual) { if (expected[0] != actual[0] || expected[1] != actual[1] || expected[2] != actual[2]) { - // only proceed if both RGB colors are not idential - for (int i = 0; i < 3; i++) { - int diff = expected[i] - actual[i]; - squaredSum = squaredSum + diff * diff; - } - if (squaredSum > maxSquaredSum) { - // deviation too high, just prepare readable string compare and let it fail - final String expectedS = expected[0] + ", " + expected[1] + ", " + expected[2]; - final String actualS = actual[0] + ", " + actual[1] + ", " + actual[2]; - assertEquals(expectedS, actualS); - } + // only proceed if both RGB colors are not idential, + // just prepare readable string compare and let it fail + final String expectedS = expected[0] + ", " + expected[1] + ", " + expected[2]; + final String actualS = actual[0] + ", " + actual[1] + ", " + actual[2]; + assertEquals(expectedS, actualS); } } } From 7cf090140cb06ce6f7f2c85888a2cca9b73eec2c Mon Sep 17 00:00:00 2001 From: GiviMAD Date: Fri, 21 Apr 2023 23:05:09 +0200 Subject: [PATCH 051/126] [voice] Remove deprecated dialog methods (#3567) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miguel Álvarez --- .../org/openhab/core/voice/VoiceManager.java | 107 ------------------ .../core/voice/internal/VoiceManagerImpl.java | 92 --------------- .../voice/internal/VoiceManagerImplTest.java | 58 ++++++---- 3 files changed, 37 insertions(+), 220 deletions(-) diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/VoiceManager.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/VoiceManager.java index 9abb3fc48e8..86ba0b8c04e 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/VoiceManager.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/VoiceManager.java @@ -14,12 +14,10 @@ import java.util.Collection; import java.util.List; -import java.util.Locale; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.audio.AudioSink; import org.openhab.core.audio.AudioSource; import org.openhab.core.library.types.PercentType; import org.openhab.core.voice.text.HumanLanguageInterpreter; @@ -137,73 +135,6 @@ public interface VoiceManager { @Nullable DialogContext getLastDialogContext(); - /** - * Starts an infinite dialog sequence using all default services: keyword spotting on the default audio source, - * audio source listening to retrieve a question or a command (default Speech to Text service), interpretation and - * handling of the command, and finally playback of the answer on the default audio sink (default Text to Speech - * service). - * - * Only one dialog can be started for the default audio source. - * - * @throws IllegalStateException if required services are not all available or the default locale is not supported - * by all these services or a dialog is already started for the default audio source - */ - @Deprecated - void startDialog() throws IllegalStateException; - - /** - * Starts an infinite dialog sequence: keyword spotting on the audio source, audio source listening to retrieve - * a question or a command (Speech to Text service), interpretation and handling of the command, and finally - * playback of the answer on the audio sink (Text to Speech service). - * - * Only one dialog can be started for an audio source. - * - * @param ks the keyword spotting service to use or null to use the default service - * @param stt the speech-to-text service to use or null to use the default service - * @param tts the text-to-speech service to use or null to use the default service - * @param hli the human language text interpreter to use or null to use the default service - * @param source the audio source to use or null to use the default source - * @param sink the audio sink to use or null to use the default sink - * @param locale the locale to use or null to use the default locale - * @param keyword the keyword to use during keyword spotting or null to use the default keyword - * @param listeningItem the item to switch ON while listening to a question - * @throws IllegalStateException if required services are not all available or the provided locale is not supported - * by all these services or a dialog is already started for this audio source - */ - @Deprecated - void startDialog(@Nullable KSService ks, @Nullable STTService stt, @Nullable TTSService tts, - @Nullable HumanLanguageInterpreter hli, @Nullable AudioSource source, @Nullable AudioSink sink, - @Nullable Locale locale, @Nullable String keyword, @Nullable String listeningItem) - throws IllegalStateException; - - /** - * Starts an infinite dialog sequence: keyword spotting on the audio source, audio source listening to retrieve - * a question or a command (Speech to Text service), interpretation and handling of the command, and finally - * playback of the answer on the audio sink (Text to Speech service). - * - * Only one dialog can be started for an audio source. - * - * @param ks the keyword spotting service to use or null to use the default service - * @param stt the speech-to-text service to use or null to use the default service - * @param tts the text-to-speech service to use or null to use the default service - * @param voice the voice to use or null to use the default voice or any voice provided by the text-to-speech - * service matching the locale - * @param hlis list of human language text interpreters to use, they are executed in order until the first - * successful response, or empty to use the default service - * @param source the audio source to use or null to use the default source - * @param sink the audio sink to use or null to use the default sink - * @param locale the locale to use or null to use the default locale - * @param keyword the keyword to use during keyword spotting or null to use the default keyword - * @param listeningItem the item to switch ON while listening to a question - * @throws IllegalStateException if required services are not all available or the provided locale is not supported - * by all these services or a dialog is already started for this audio source - */ - @Deprecated - void startDialog(@Nullable KSService ks, @Nullable STTService stt, @Nullable TTSService tts, @Nullable Voice voice, - List hlis, @Nullable AudioSource source, @Nullable AudioSink sink, - @Nullable Locale locale, @Nullable String keyword, @Nullable String listeningItem) - throws IllegalStateException; - /** * Starts an infinite dialog sequence: keyword spotting on the audio source, audio source listening to retrieve * a question or a command (Speech to Text service), interpretation and handling of the command, and finally @@ -233,44 +164,6 @@ void startDialog(@Nullable KSService ks, @Nullable STTService stt, @Nullable TTS */ void stopDialog(DialogContext context) throws IllegalStateException; - /** - * Executes a simple dialog sequence without keyword spotting using all default services: default audio source - * listening to retrieve a question or a command (default Speech to Text service), interpretation and handling of - * the command, and finally playback of the answer on the default audio sink (default Text to Speech service). - * - * Only possible if no dialog processor is already started for the default audio source. - * - * @throws IllegalStateException if required services are not all available or the provided default locale is not - * supported by all these services or a dialog is already started for the default audio source - */ - @Deprecated - void listenAndAnswer() throws IllegalStateException; - - /** - * Executes a simple dialog sequence without keyword spotting: audio source listening to retrieve a question or a - * command (Speech to Text service), interpretation and handling of the command, and finally playback of the - * answer on the audio sink (Text to Speech service). - * - * Only possible if no dialog processor is already started for the audio source. - * - * @param stt the speech-to-text service to use or null to use the default service - * @param tts the text-to-speech service to use or null to use the default service - * @param voice the voice to use or null to use the default voice or any voice provided by the text-to-speech - * service matching the locale - * @param hlis list of human language text interpreters to use, they are executed in order until the first - * successful response, or empty to use the default service - * @param source the audio source to use or null to use the default source - * @param sink the audio sink to use or null to use the default sink - * @param locale the locale to use or null to use the default locale - * @param listeningItem the item to switch ON while listening to a question - * @throws IllegalStateException if required services are not all available or the provided locale is not supported - * by all these services or a dialog is already started for this audio source - */ - @Deprecated - void listenAndAnswer(@Nullable STTService stt, @Nullable TTSService tts, @Nullable Voice voice, - List hlis, @Nullable AudioSource source, @Nullable AudioSink sink, - @Nullable Locale locale, @Nullable String listeningItem) throws IllegalStateException; - /** * Executes a simple dialog sequence without keyword spotting: audio source listening to retrieve a question or a * command (Speech to Text service), interpretation and handling of the command, and finally playback of the diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceManagerImpl.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceManagerImpl.java index 105eb6e7d75..f08df2250ab 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceManagerImpl.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceManagerImpl.java @@ -530,62 +530,6 @@ public DialogContext.Builder getDialogContextBuilder() { return lastDialogContext; } - @Override - @Deprecated - public void startDialog() throws IllegalStateException { - startDialog(null, null, null, null, List.of(), null, null, null, this.keyword, this.listeningItem); - } - - @Override - @Deprecated - public void startDialog(@Nullable KSService ks, @Nullable STTService stt, @Nullable TTSService tts, - @Nullable HumanLanguageInterpreter hli, @Nullable AudioSource source, @Nullable AudioSink sink, - @Nullable Locale locale, @Nullable String keyword, @Nullable String listeningItem) - throws IllegalStateException { - startDialog(ks, stt, tts, null, hli == null ? List.of() : List.of(hli), source, sink, locale, keyword, - listeningItem); - } - - @Override - @Deprecated - public void startDialog(@Nullable KSService ks, @Nullable STTService stt, @Nullable TTSService tts, - @Nullable Voice voice, List hlis, @Nullable AudioSource source, - @Nullable AudioSink sink, @Nullable Locale locale, @Nullable String keyword, @Nullable String listeningItem) - throws IllegalStateException { - var builder = getDialogContextBuilder(); - if (ks != null) { - builder.withKS(ks); - } - if (keyword != null) { - builder.withKeyword(keyword); - } - if (stt != null) { - builder.withSTT(stt); - } - if (tts != null) { - builder.withTTS(tts); - } - if (voice != null) { - builder.withVoice(voice); - } - if (!hlis.isEmpty()) { - builder.withHLIs(hlis); - } - if (source != null) { - builder.withSource(source); - } - if (sink != null) { - builder.withSink(sink); - } - if (locale != null) { - builder.withLocale(locale); - } - if (listeningItem != null) { - builder.withListeningItem(listeningItem); - } - startDialog(builder.build()); - } - @Override public void startDialog(DialogContext context) throws IllegalStateException { var ksService = context.ks(); @@ -645,42 +589,6 @@ public void stopDialog(DialogContext context) throws IllegalStateException { stopDialog(context.source()); } - @Override - @Deprecated - public void listenAndAnswer() throws IllegalStateException { - listenAndAnswer(null, null, null, List.of(), null, null, null, null); - } - - @Override - @Deprecated - public void listenAndAnswer(@Nullable STTService stt, @Nullable TTSService tts, @Nullable Voice voice, - List hlis, @Nullable AudioSource source, @Nullable AudioSink sink, - @Nullable Locale locale, @Nullable String listeningItem) throws IllegalStateException { - var builder = getDialogContextBuilder(); - if (stt != null) { - builder.withSTT(stt); - } - if (tts != null) { - builder.withTTS(tts); - } - if (!hlis.isEmpty()) { - builder.withHLIs(hlis); - } - if (source != null) { - builder.withSource(source); - } - if (sink != null) { - builder.withSink(sink); - } - if (locale != null) { - builder.withLocale(locale); - } - if (listeningItem != null) { - builder.withListeningItem(listeningItem); - } - listenAndAnswer(builder.build()); - } - @Override public void listenAndAnswer(DialogContext context) throws IllegalStateException { Bundle b = bundle; diff --git a/itests/org.openhab.core.voice.tests/src/main/java/org/openhab/core/voice/internal/VoiceManagerImplTest.java b/itests/org.openhab.core.voice.tests/src/main/java/org/openhab/core/voice/internal/VoiceManagerImplTest.java index 89ce42a9786..dcf6b21aee2 100644 --- a/itests/org.openhab.core.voice.tests/src/main/java/org/openhab/core/voice/internal/VoiceManagerImplTest.java +++ b/itests/org.openhab.core.voice.tests/src/main/java/org/openhab/core/voice/internal/VoiceManagerImplTest.java @@ -38,6 +38,7 @@ import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.test.java.JavaOSGiTest; +import org.openhab.core.voice.DialogContext; import org.openhab.core.voice.DialogRegistration; import org.openhab.core.voice.Voice; import org.openhab.core.voice.VoiceManager; @@ -200,8 +201,10 @@ public void verifyThatADialogIsNotStartedWhenAnyOfTheRequiredServiceIsNull() { ksService = new KSServiceStub(); hliStub = new HumanLanguageInterpreterStub(); - assertThrows(IllegalStateException.class, () -> voiceManager.startDialog(ksService, sttService, null, null, - List.of(hliStub), source, sink, Locale.ENGLISH, "word", null)); + assertThrows(IllegalStateException.class, + () -> voiceManager.startDialog(voiceManager.getDialogContextBuilder().withSource(source).withSink(sink) + .withKS(ksService).withSTT(sttService).withTTS(null).withHLI(hliStub).withLocale(Locale.ENGLISH) + .withKeyword("word").build())); assertFalse(ksService.isWordSpotted()); assertFalse(sink.getIsStreamProcessed()); @@ -218,8 +221,10 @@ public void verifyThatADialogIsNotStartedWhenLocaleIsNotSupported() { registerService(ttsService); registerService(hliStub); - assertThrows(IllegalStateException.class, () -> voiceManager.startDialog(ksService, sttService, ttsService, - null, List.of(hliStub), source, sink, Locale.FRENCH, "mot", null)); + assertThrows(IllegalStateException.class, + () -> voiceManager.startDialog(voiceManager.getDialogContextBuilder().withSource(source).withSink(sink) + .withKS(ksService).withSTT(sttService).withTTS(ttsService).withHLI(hliStub) + .withLocale(Locale.FRENCH).withKeyword("mot").build())); assertFalse(ksService.isWordSpotted()); assertFalse(sink.getIsStreamProcessed()); @@ -236,8 +241,9 @@ public void startDialogWhenAllOfTheRequiredServicesAreAvailable() { registerService(ttsService); registerService(hliStub); - voiceManager.startDialog(ksService, sttService, ttsService, null, List.of(hliStub), source, sink, - Locale.ENGLISH, "word", null); + voiceManager.startDialog(voiceManager.getDialogContextBuilder().withSource(source).withSink(sink) + .withKS(ksService).withSTT(sttService).withTTS(ttsService).withHLI(hliStub).withLocale(Locale.ENGLISH) + .withKeyword("word").build()); assertTrue(ksService.isWordSpotted()); assertTrue(sttService.isRecognized()); @@ -264,8 +270,9 @@ public void startDialogAndVerifyThatAKSExceptionIsProperlyHandled() { ksService.setExceptionExpected(true); - voiceManager.startDialog(ksService, sttService, ttsService, null, List.of(hliStub), source, sink, - Locale.ENGLISH, "", null); + voiceManager.startDialog(voiceManager.getDialogContextBuilder().withSource(source).withSink(sink) + .withKS(ksService).withSTT(sttService).withTTS(ttsService).withHLI(hliStub).withLocale(Locale.ENGLISH) + .withKeyword("word").build()); assertFalse(ksService.isWordSpotted()); assertFalse(sttService.isRecognized()); @@ -290,8 +297,9 @@ public void startDialogAndVerifyThatAKSErrorIsProperlyHandled() { ksService.setErrorExpected(true); - voiceManager.startDialog(ksService, sttService, ttsService, null, List.of(hliStub), source, sink, - Locale.ENGLISH, "word", null); + voiceManager.startDialog(voiceManager.getDialogContextBuilder().withSource(source).withSink(sink) + .withKS(ksService).withSTT(sttService).withTTS(ttsService).withHLI(hliStub).withLocale(Locale.ENGLISH) + .withKeyword("word").build()); assertFalse(ksService.isWordSpotted()); assertFalse(sttService.isRecognized()); @@ -317,8 +325,9 @@ public void startDialogAndVerifyThatASTTExceptionIsProperlyHandled() { sttService.setExceptionExpected(true); - voiceManager.startDialog(ksService, sttService, ttsService, null, List.of(hliStub), source, sink, - Locale.ENGLISH, "word", null); + voiceManager.startDialog(voiceManager.getDialogContextBuilder().withSource(source).withSink(sink) + .withKS(ksService).withSTT(sttService).withTTS(ttsService).withHLI(hliStub).withLocale(Locale.ENGLISH) + .withKeyword("word").build()); assertTrue(ksService.isWordSpotted()); assertFalse(sttService.isRecognized()); @@ -343,8 +352,9 @@ public void startDialogAndVerifyThatASpeechRecognitionErrorIsProperlyHandled() { sttService.setErrorExpected(true); - voiceManager.startDialog(ksService, sttService, ttsService, null, List.of(hliStub), source, sink, - Locale.ENGLISH, "word", null); + voiceManager.startDialog(voiceManager.getDialogContextBuilder().withSource(source).withSink(sink) + .withKS(ksService).withSTT(sttService).withTTS(ttsService).withHLI(hliStub).withLocale(Locale.ENGLISH) + .withKeyword("word").build()); assertTrue(ksService.isWordSpotted()); assertFalse(sttService.isRecognized()); @@ -369,8 +379,9 @@ public void startDialogAndVerifyThatAnInterpretationExceptionIsProperlyHandled() hliStub.setExceptionExpected(true); - voiceManager.startDialog(ksService, sttService, ttsService, null, List.of(hliStub), source, sink, - Locale.ENGLISH, "word", null); + voiceManager.startDialog(voiceManager.getDialogContextBuilder().withSource(source).withSink(sink) + .withKS(ksService).withSTT(sttService).withTTS(ttsService).withHLI(hliStub).withLocale(Locale.ENGLISH) + .withKeyword("word").build()); assertTrue(ksService.isWordSpotted()); assertTrue(sttService.isRecognized()); @@ -407,8 +418,10 @@ public void startDialogWithoutPassingAnyParameters() throws Exception { // Wait some time to be sure that the configuration will be updated Thread.sleep(2000); - voiceManager.startDialog(); + DialogContext context = voiceManager.getDialogContextBuilder().build(); + voiceManager.startDialog(context); + assertThat(voiceManager.getLastDialogContext(), is(context)); assertTrue(ksService.isWordSpotted()); assertTrue(sttService.isRecognized()); assertThat(hliStub.getQuestion(), is("Recognized text")); @@ -432,13 +445,16 @@ public void verifyThatOnlyOneDialogPerSourceIsPossible() { registerService(ttsService); registerService(hliStub); - voiceManager.startDialog(ksService, sttService, ttsService, null, List.of(hliStub), source, sink, - Locale.ENGLISH, "word", null); + voiceManager.startDialog(voiceManager.getDialogContextBuilder().withSource(source).withSink(sink) + .withKS(ksService).withSTT(sttService).withTTS(ttsService).withHLI(hliStub).withLocale(Locale.ENGLISH) + .withKeyword("word").build()); assertTrue(ksService.isWordSpotted()); - assertThrows(IllegalStateException.class, () -> voiceManager.startDialog(ksService, sttService, ttsService, - null, List.of(hliStub), source, sink, Locale.ENGLISH, "word", null)); + assertThrows(IllegalStateException.class, + () -> voiceManager.startDialog(voiceManager.getDialogContextBuilder().withSource(source).withSink(sink) + .withKS(ksService).withSTT(sttService).withTTS(ttsService).withHLI(hliStub) + .withLocale(Locale.ENGLISH).withKeyword("word").build())); voiceManager.stopDialog(source); From bff92e0349724c7b61775e780d54c2115eade506 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Sat, 22 Apr 2023 16:18:49 +1000 Subject: [PATCH 052/126] Improve log message to identify profile's link (#3568) --- .../automation/module/script/profile/ScriptProfile.java | 3 ++- .../module/script/profile/ScriptProfileTest.java | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfile.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfile.java index cb2634afc67..e9e720bcb5f 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfile.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfile.java @@ -76,7 +76,8 @@ public ScriptProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback, Pr if (toItemScript.isBlank() && toHandlerScript.isBlank()) { logger.error( - "Neither 'toItem' nor 'toHandler' script defined. Profile will discard all states and commands."); + "Neither 'toItemScript' nor 'toHandlerScript' defined in link '{}'. Profile will discard all states and commands.", + callback.getItemChannelLink()); isConfigured = false; return; } diff --git a/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/profile/ScriptProfileTest.java b/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/profile/ScriptProfileTest.java index 11111ec4499..d4268e65fe8 100644 --- a/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/profile/ScriptProfileTest.java +++ b/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/profile/ScriptProfileTest.java @@ -40,6 +40,8 @@ import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; import org.openhab.core.test.java.JavaTest; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.link.ItemChannelLink; import org.openhab.core.thing.profiles.ProfileCallback; import org.openhab.core.thing.profiles.ProfileContext; import org.openhab.core.thing.profiles.ProfileTypeUID; @@ -70,6 +72,9 @@ public void setUp() throws TransformationException { public void testScriptNotExecutedAndNoValueForwardedToCallbackIfNoScriptDefined() throws TransformationException { ProfileContext profileContext = ProfileContextBuilder.create().build(); + ItemChannelLink link = new ItemChannelLink("DummyItem", new ChannelUID("foo:bar:baz:qux")); + when(profileCallback.getItemChannelLink()).thenReturn(link); + setupInterceptedLogger(ScriptProfile.class, LogLevel.ERROR); ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext, @@ -85,7 +90,8 @@ public void testScriptNotExecutedAndNoValueForwardedToCallbackIfNoScriptDefined( verify(profileCallback, never()).sendCommand(any()); assertLogMessage(ScriptProfile.class, LogLevel.ERROR, - "Neither 'toItem' nor 'toHandler' script defined. Profile will discard all states and commands."); + "Neither 'toItemScript' nor 'toHandlerScript' defined in link '" + link.toString() + + "'. Profile will discard all states and commands."); } @Test From ac0f512178e5a5949032f16aaec7fd3896678574 Mon Sep 17 00:00:00 2001 From: Kai Kreuzer Date: Mon, 24 Apr 2023 20:14:20 +0200 Subject: [PATCH 053/126] Consider auto-update veto from channel types (#3575) Signed-off-by: Kai Kreuzer --- .../core/thing/type/AutoUpdatePolicy.java | 6 +- .../openhab/core/thing/ThingChannelsTest.java | 16 ++- .../thing/internal/AutoUpdateManagerTest.java | 107 ++++++++++++++++++ .../CommunicationManagerOSGiTest.java | 3 - 4 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/AutoUpdateManagerTest.java diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/type/AutoUpdatePolicy.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/type/AutoUpdatePolicy.java index 4ecb759b570..085eceeba84 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/type/AutoUpdatePolicy.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/type/AutoUpdatePolicy.java @@ -46,12 +46,12 @@ public enum AutoUpdatePolicy { * Parses the input string into an {@link AutoUpdatePolicy}. * * @param input the input string - * @return the parsed AutoUpdatePolicy + * @return the parsed AutoUpdatePolicy or null if the input was null * @throws IllegalArgumentException if the input couldn't be parsed. */ - public static AutoUpdatePolicy parse(@Nullable String input) { + public static @Nullable AutoUpdatePolicy parse(@Nullable String input) { if (input == null) { - return DEFAULT; + return null; } for (AutoUpdatePolicy value : values()) { diff --git a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/ThingChannelsTest.java b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/ThingChannelsTest.java index 8f09da9de0d..675d063ff18 100644 --- a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/ThingChannelsTest.java +++ b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/ThingChannelsTest.java @@ -32,22 +32,22 @@ @NonNullByDefault public class ThingChannelsTest extends JavaOSGiTest { + private static final ThingTypeUID THING_TYPE_UID = new ThingTypeUID("bindingId", "thingTypeId"); + private static final ThingUID THING_UID = new ThingUID(THING_TYPE_UID, "thingLabel"); + private static final List CHANNEL_IDS = List.of("polarBear", "alligator", "hippopotamus", "aardvark", "whiteRabbit", "redHerring", "orangutan", "kangaroo", "rubberDuck", "timorousBeastie"); @Test public void testThingChannelOrder() { - ThingTypeUID thingTypeUID = new ThingTypeUID("bindingId", "thingTypeId"); - ThingUID thingUID = new ThingUID(thingTypeUID, "thingLabel"); - // create and fill the list of origin channels List originChannels = new ArrayList<>(); CHANNEL_IDS.forEach(channelId -> originChannels - .add(ChannelBuilder.create(new ChannelUID(thingUID, channelId), null).build())); + .add(ChannelBuilder.create(new ChannelUID(THING_UID, channelId), null).build())); assertEquals(CHANNEL_IDS.size(), originChannels.size()); // build a thing with the origin channels - Thing thing = ThingBuilder.create(thingTypeUID, thingUID).withChannels(originChannels).build(); + Thing thing = ThingBuilder.create(THING_TYPE_UID, THING_UID).withChannels(originChannels).build(); List resultChannels; @@ -65,4 +65,10 @@ public void testThingChannelOrder() { assertTrue(CHANNEL_IDS.get(i).equals(resultChannels.get(i).getUID().getId())); } } + + @Test + public void testAutoUpdatePolicyNotSetOnNewChannels() { + Channel channel = ChannelBuilder.create(new ChannelUID(THING_UID, CHANNEL_IDS.get(0)), null).build(); + assertNull(channel.getAutoUpdatePolicy()); + } } diff --git a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/AutoUpdateManagerTest.java b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/AutoUpdateManagerTest.java new file mode 100644 index 00000000000..7286855c902 --- /dev/null +++ b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/AutoUpdateManagerTest.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.internal; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openhab.core.events.Event; +import org.openhab.core.events.EventPublisher; +import org.openhab.core.items.Item; +import org.openhab.core.items.MetadataRegistry; +import org.openhab.core.items.events.ItemEventFactory; +import org.openhab.core.library.CoreItemFactory; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.test.java.JavaTest; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingRegistry; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.link.ItemChannelLink; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; +import org.openhab.core.thing.type.AutoUpdatePolicy; +import org.openhab.core.thing.type.ChannelTypeBuilder; +import org.openhab.core.thing.type.ChannelTypeRegistry; +import org.openhab.core.thing.type.ChannelTypeUID; + +/** + * Tests for {@link AutoUpdateManager}. + * + * @author Kai Kreuzer - Initial contribution + */ +@NonNullByDefault +public class AutoUpdateManagerTest extends JavaTest { + + private static final ChannelTypeUID CHANNEL_TYPE_UID = new ChannelTypeUID("binding:channelType"); + private static final ChannelUID CHANNEL_UID = new ChannelUID("binding:thingtype1:thing1:channel1"); + private static final String ITEM_NAME = "TestItem"; + + private @NonNullByDefault({}) ChannelTypeRegistry channelTypeRegistry; + private @NonNullByDefault({}) ThingRegistry thingRegistry; + private @NonNullByDefault({}) MetadataRegistry metadataRegistry; + private @NonNullByDefault({}) EventPublisher eventPublisher; + private @NonNullByDefault({}) ItemChannelLinkRegistry itemChannelLinkRegistry; + private @NonNullByDefault({}) AutoUpdateManager autoUpdateManager; + private @NonNullByDefault({}) Item item; + private @NonNullByDefault({}) Thing thing; + + @BeforeEach + public void setup() { + channelTypeRegistry = mock(ChannelTypeRegistry.class); + eventPublisher = mock(EventPublisher.class); + itemChannelLinkRegistry = mock(ItemChannelLinkRegistry.class); + assertNotNull(itemChannelLinkRegistry); + + thingRegistry = mock(ThingRegistry.class); + thing = mock(Thing.class); + metadataRegistry = mock(MetadataRegistry.class); + + Channel channel = ChannelBuilder.create(CHANNEL_UID).withType(CHANNEL_TYPE_UID).build(); + + autoUpdateManager = new AutoUpdateManager(Collections.emptyMap(), channelTypeRegistry, eventPublisher, + itemChannelLinkRegistry, metadataRegistry, thingRegistry); + + item = mock(Item.class); + when(item.getName()).thenReturn(ITEM_NAME); + when(item.getAcceptedDataTypes()).thenReturn(List.of(OnOffType.class)); + when(itemChannelLinkRegistry.getLinks(any(String.class))) + .thenReturn(Set.of(new ItemChannelLink(ITEM_NAME, CHANNEL_UID))); + when(thingRegistry.get(any(ThingUID.class))).thenReturn(thing); + when(thing.getStatus()).thenReturn(ThingStatus.ONLINE); + when(thing.getHandler()).thenReturn(mock(ThingHandler.class)); + when(thing.getChannel(any(String.class))).thenReturn(channel); + } + + @Test + public void testAutoUpdateVetoFromChannelType() { + when(channelTypeRegistry.getChannelType(any(ChannelTypeUID.class))) + .thenReturn(ChannelTypeBuilder.state(CHANNEL_TYPE_UID, "label", CoreItemFactory.SWITCH).withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build()); + + autoUpdateManager.receiveCommand(ItemEventFactory.createCommandEvent(ITEM_NAME, OnOffType.ON), item); + + // No event should have been sent + verify(eventPublisher, never()).post(any(Event.class)); + } +} diff --git a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/CommunicationManagerOSGiTest.java b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/CommunicationManagerOSGiTest.java index faf25f40bd8..c4e2dcdac05 100644 --- a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/CommunicationManagerOSGiTest.java +++ b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/CommunicationManagerOSGiTest.java @@ -164,9 +164,6 @@ public void beforeEach() { SystemProfileFactory profileFactory = getService(ProfileTypeProvider.class, SystemProfileFactory.class); assertNotNull(profileFactory); - if (profileFactory == null) { - throw new IllegalStateException("thing is null"); - } manager = new CommunicationManager(autoUpdateManagerMock, channelTypeRegistryMock, profileFactory, iclRegistry, itemRegistryMock, itemStateConverterMock, eventPublisherMock, safeCaller, thingRegistryMock); From 33233982b3df2cb8d6bae0bddfb19ce208019959 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Sun, 30 Apr 2023 18:37:48 +0200 Subject: [PATCH 054/126] Code cleanup: Use Java 17 features (#3580) * Code cleanup: Use Java 17 features Signed-off-by: Holger Friedrich --- .../core/addon/xml/test/AddonInfoTest.java | 10 ++++-- .../test/internal/AbstractTestAgent.java | 16 ++++------ .../internal/module/RunRuleModuleTest.java | 8 ++--- .../timer/internal/RuntimeRuleTest.java | 5 +-- .../TimeOfDayConditionHandlerTest.java | 9 ++---- .../internal/TestModuleTypeProvider.java | 12 +++---- .../core/xml/ConfigDescriptionI18nTest.java | 3 +- .../core/xml/ConfigDescriptionsTest.java | 6 ++-- .../DiscoveryServiceRegistryOSGiTest.java | 2 +- .../discovery/internal/InboxOSGiTest.java | 10 +++--- .../internal/SysFsUsbSerialScannerTest.java | 4 +-- .../test/hue/GenericThingProviderTest.java | 24 +++++++------- .../test/hue/GenericThingProviderTest4.java | 4 +-- .../hue/TestHueThingHandlerFactory.java | 4 +-- .../internal/items/ItemUpdaterOSGiTest.java | 3 +- .../openhab/core/items/GroupItemOSGiTest.java | 32 +++++++++---------- .../core/items/ItemRegistryImplTest.java | 8 ++--- .../binding/BindingBaseClassesOSGiTest.java | 2 +- .../thing/binding/firmware/FirmwareTest.java | 2 +- .../internal/ChannelLinkNotifierOSGiTest.java | 14 ++++---- .../firmware/FirmwareUpdateServiceTest.java | 22 ++++++------- .../core/voice/internal/TTSServiceStub.java | 2 +- .../tools/i18n/plugin/BundleInfoReader.java | 9 ++---- 23 files changed, 99 insertions(+), 112 deletions(-) diff --git a/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java b/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java index 0778f76f369..1531c2ab4b8 100644 --- a/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java +++ b/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java @@ -29,6 +29,8 @@ import org.openhab.core.config.core.ConfigDescription; import org.openhab.core.config.core.ConfigDescriptionParameter; import org.openhab.core.config.core.ConfigDescriptionRegistry; +import org.openhab.core.config.core.FilterCriteria; +import org.openhab.core.config.core.ParameterOption; import org.openhab.core.test.java.JavaOSGiTest; /** @@ -82,14 +84,16 @@ public void assertThatConfigWithOptionsAndFilterAreProperlyRead() throws Excepti ConfigDescriptionParameter listParameter = parameters.stream().filter(p -> "list".equals(p.getName())) .findFirst().get(); assertThat(listParameter, is(notNullValue())); - assertThat(listParameter.getOptions().stream().map(p -> p.toString()).collect(Collectors.joining(", ")), is( - "ParameterOption [value=\"key1\", label=\"label1\"], ParameterOption [value=\"key2\", label=\"label2\"]")); + assertThat( + listParameter.getOptions().stream().map(ParameterOption::toString) + .collect(Collectors.joining(", ")), + is("ParameterOption [value=\"key1\", label=\"label1\"], ParameterOption [value=\"key2\", label=\"label2\"]")); ConfigDescriptionParameter lightParameter = parameters.stream() .filter(p -> "color-alarming-light".equals(p.getName())).findFirst().get(); assertThat(lightParameter, is(notNullValue())); assertThat( - lightParameter.getFilterCriteria().stream().map(p -> p.toString()) + lightParameter.getFilterCriteria().stream().map(FilterCriteria::toString) .collect(Collectors.joining(", ")), is("FilterCriteria [name=\"tags\", value=\"alarm, light\"], FilterCriteria [name=\"type\", value=\"color\"], FilterCriteria [name=\"binding-id\", value=\"hue\"]")); }); diff --git a/itests/org.openhab.core.auth.oauth2client.tests/src/main/java/org/openhab/core/auth/oauth2client/test/internal/AbstractTestAgent.java b/itests/org.openhab.core.auth.oauth2client.tests/src/main/java/org/openhab/core/auth/oauth2client/test/internal/AbstractTestAgent.java index 54d1be91f17..c8a586673ce 100644 --- a/itests/org.openhab.core.auth.oauth2client.tests/src/main/java/org/openhab/core/auth/oauth2client/test/internal/AbstractTestAgent.java +++ b/itests/org.openhab.core.auth.oauth2client.tests/src/main/java/org/openhab/core/auth/oauth2client/test/internal/AbstractTestAgent.java @@ -65,11 +65,10 @@ private static String getProperty(Map properties, String propKey if (obj == null) { return ""; } - if (obj instanceof String) { - return (String) obj; + if (obj instanceof String string) { + return string; } - if (obj instanceof String[]) { - String[] strArr = (String[]) obj; + if (obj instanceof String[] strArr) { if (strArr.length >= 1) { return strArr[0]; } else { @@ -141,22 +140,19 @@ public AccessTokenResponse testGetAccessTokenByAuthorizationCode(String code) @Override public AccessTokenResponse testGetCachedAccessToken() throws OAuthException, IOException, OAuthResponseException { logger.debug("test getCachedAccessToken"); - AccessTokenResponse oldRefreshedToken = oauthClientService.getAccessTokenResponse(); - return oldRefreshedToken; + return oauthClientService.getAccessTokenResponse(); } @Override public AccessTokenResponse testRefreshToken() throws OAuthException, IOException, OAuthResponseException { logger.debug("test RefreshToken"); - AccessTokenResponse newRefreshedToken = oauthClientService.refreshToken(); - return newRefreshedToken; + return oauthClientService.refreshToken(); } @Override public String testGetAuthorizationUrl(String state) throws OAuthException { logger.debug("test getAuthorizationUrl {}", state); - String authorizationURL = oauthClientService.getAuthorizationUrl(redirectUri, scope, state); - return authorizationURL; + return oauthClientService.getAuthorizationUrl(redirectUri, scope, state); } @Override diff --git a/itests/org.openhab.core.automation.module.core.tests/src/main/java/org/openhab/core/automation/internal/module/RunRuleModuleTest.java b/itests/org.openhab.core.automation.module.core.tests/src/main/java/org/openhab/core/automation/internal/module/RunRuleModuleTest.java index 3ba71f090e0..e3c68f6d6c9 100644 --- a/itests/org.openhab.core.automation.module.core.tests/src/main/java/org/openhab/core/automation/internal/module/RunRuleModuleTest.java +++ b/itests/org.openhab.core.automation.module.core.tests/src/main/java/org/openhab/core/automation/internal/module/RunRuleModuleTest.java @@ -99,7 +99,7 @@ private Rule createSceneRule() { final Configuration sceneRuleAction3Config = new Configuration( Map.ofEntries(entry("itemName", "switch3"), entry("command", "ON"))); - final Rule sceneRule = RuleBuilder.create("exampleSceneRule").withActions( + return RuleBuilder.create("exampleSceneRule").withActions( ModuleBuilder.createAction().withId("sceneItemPostCommandAction1").withTypeUID("core.ItemCommandAction") .withConfiguration(sceneRuleAction1Config).build(), ModuleBuilder.createAction().withId("sceneItemPostCommandAction2").withTypeUID("core.ItemCommandAction") @@ -107,8 +107,6 @@ private Rule createSceneRule() { ModuleBuilder.createAction().withId("sceneItemPostCommandAction3").withTypeUID("core.ItemCommandAction") .withConfiguration(sceneRuleAction3Config).build()) .withName("Example Scene").build(); - - return sceneRule; } private Rule createOuterRule() { @@ -120,14 +118,12 @@ private Rule createOuterRule() { final Configuration outerRuleActionConfig = new Configuration(Map.of("ruleUIDs", ruleUIDs)); - final Rule outerRule = RuleBuilder.create("sceneActivationRule") + return RuleBuilder.create("sceneActivationRule") .withTriggers(ModuleBuilder.createTrigger().withId("ItemStateChangeTrigger2") .withTypeUID("core.GenericEventTrigger").withConfiguration(outerRuleTriggerConfig).build()) .withActions(ModuleBuilder.createAction().withId("RunRuleAction1").withTypeUID("core.RunRuleAction") .withConfiguration(outerRuleActionConfig).build()) .withName("scene activator").build(); - - return outerRule; } @Test diff --git a/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/RuntimeRuleTest.java b/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/RuntimeRuleTest.java index 1df494a5ceb..1077c801b41 100644 --- a/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/RuntimeRuleTest.java +++ b/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/RuntimeRuleTest.java @@ -105,8 +105,6 @@ public void checkDisableAndEnableOfTimerTriggeredRule() { */ logger.info("Create rule"); String testExpression = "* * * * * ?"; - - ; Configuration triggerConfig = new Configuration(Map.of("cronExpression", testExpression)); List triggers = List.of(ModuleBuilder.createTrigger().withId("MyTimerTrigger") .withTypeUID(GenericCronTriggerHandler.MODULE_TYPE_ID).withConfiguration(triggerConfig).build()); @@ -136,8 +134,7 @@ public void checkDisableAndEnableOfTimerTriggeredRule() { final RuleStatusInfo ruleStatus = ruleEngine.getStatusInfo(rule.getUID()); logger.info("Rule status (should be IDLE or RUNNING): {}", ruleStatus); boolean allFine; - if (RuleStatus.IDLE.equals(ruleStatus.getStatus()) - || RuleStatus.RUNNING.equals(ruleStatus.getStatus())) { + if (RuleStatus.IDLE == ruleStatus.getStatus() || RuleStatus.RUNNING == ruleStatus.getStatus()) { allFine = true; } else { allFine = false; diff --git a/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/TimeOfDayConditionHandlerTest.java b/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/TimeOfDayConditionHandlerTest.java index 2b670aab489..3bbdea8a183 100644 --- a/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/TimeOfDayConditionHandlerTest.java +++ b/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/TimeOfDayConditionHandlerTest.java @@ -74,23 +74,20 @@ public void assertThatConditionWorks() { } private TimeOfDayConditionHandler getTimeOfDayConditionHandler(String startTime, String endTime) { - TimeOfDayConditionHandler handler = new TimeOfDayConditionHandler(getTimeCondition(startTime, endTime)); - return handler; + return new TimeOfDayConditionHandler(getTimeCondition(startTime, endTime)); } private Condition getTimeCondition(String startTime, String endTime) { Configuration timeConfig = getTimeConfiguration(startTime, endTime); - Condition condition = ModuleBuilder.createCondition().withId("testTimeOfDayCondition") + return ModuleBuilder.createCondition().withId("testTimeOfDayCondition") .withTypeUID(TimeOfDayConditionHandler.MODULE_TYPE_ID).withConfiguration(timeConfig).build(); - return condition; } private Configuration getTimeConfiguration(String startTime, String endTime) { Map timeMap = new HashMap<>(); timeMap.put("startTime", startTime); timeMap.put("endTime", endTime); - Configuration timeConfig = new Configuration(timeMap); - return timeConfig; + return new Configuration(timeMap); } @Override diff --git a/itests/org.openhab.core.automation.tests/src/main/java/org/openhab/core/automation/internal/TestModuleTypeProvider.java b/itests/org.openhab.core.automation.tests/src/main/java/org/openhab/core/automation/internal/TestModuleTypeProvider.java index 37011e6c4ac..5ba47dcf1da 100644 --- a/itests/org.openhab.core.automation.tests/src/main/java/org/openhab/core/automation/internal/TestModuleTypeProvider.java +++ b/itests/org.openhab.core.automation.tests/src/main/java/org/openhab/core/automation/internal/TestModuleTypeProvider.java @@ -55,17 +55,16 @@ private TriggerType createTriggerType() { outputs.add(createOutput("out1", Set.of("tagA"))); outputs.add(createOutput("out2", Set.of("tagB", "tagC"))); outputs.add(createOutput("out3", Set.of("tagA", "tagB", "tagC"))); - TriggerType t = new TriggerType(TRIGGER_TYPE, null, outputs); - return t; + return new TriggerType(TRIGGER_TYPE, null, outputs); } private ConditionType createConditionType() { List inputs = new ArrayList<>(3); inputs.add(createInput("in0", Set.of("tagE"))); // no connection, missing condition tag inputs.add(createInput("in1", Set.of("tagA"))); // conflict in2 -> out1 or in2 -> out3 - inputs.add(createInput("in2", Set.of("tagA", "tagB"))); // in2 -> out3 - ConditionType t = new ConditionType(CONDITION_TYPE, null, inputs); - return t; + inputs.add(createInput("in2", Set.of("tagA", "tagB"))); + // in2 -> out3 + return new ConditionType(CONDITION_TYPE, null, inputs); } private ActionType createActionType() { @@ -78,8 +77,7 @@ private ActionType createActionType() { List outputs = new ArrayList<>(3); outputs.add(createOutput("out4", Set.of("tagD"))); outputs.add(createOutput("out5", Set.of("tagD", "tagE"))); - ActionType t = new ActionType(ACTION_TYPE, null, inputs, outputs); - return t; + return new ActionType(ACTION_TYPE, null, inputs, outputs); } private Output createOutput(String name, Set tags) { diff --git a/itests/org.openhab.core.config.core.tests/src/main/java/org/openhab/core/config/core/xml/ConfigDescriptionI18nTest.java b/itests/org.openhab.core.config.core.tests/src/main/java/org/openhab/core/config/core/xml/ConfigDescriptionI18nTest.java index caf3d4d990b..dd973d1c41a 100644 --- a/itests/org.openhab.core.config.core.tests/src/main/java/org/openhab/core/config/core/xml/ConfigDescriptionI18nTest.java +++ b/itests/org.openhab.core.config.core.tests/src/main/java/org/openhab/core/config/core/xml/ConfigDescriptionI18nTest.java @@ -30,6 +30,7 @@ import org.openhab.core.config.core.ConfigDescriptionParameter; import org.openhab.core.config.core.ConfigDescriptionParameterGroup; import org.openhab.core.config.core.ConfigDescriptionProvider; +import org.openhab.core.config.core.ParameterOption; import org.openhab.core.test.BundleCloseable; import org.openhab.core.test.SyntheticBundleInstaller; import org.openhab.core.test.java.JavaOSGiTest; @@ -101,7 +102,7 @@ private static String asString(ConfigDescription description) { sb.append(String.format("refresh.description = %s\n", refresh.getDescription())); sb.append(String.format("question.pattern = %s\n", question.getPattern())); sb.append(String.format("question.options = %s\n", - question.getOptions().stream().map(o -> o.getLabel()).collect(Collectors.joining(", ")))); + question.getOptions().stream().map(ParameterOption::getLabel).collect(Collectors.joining(", ")))); sb.append(String.format("group.label = %s\n", group.getLabel())); sb.append(String.format("group.description = %s", group.getDescription())); diff --git a/itests/org.openhab.core.config.core.tests/src/main/java/org/openhab/core/config/core/xml/ConfigDescriptionsTest.java b/itests/org.openhab.core.config.core.tests/src/main/java/org/openhab/core/config/core/xml/ConfigDescriptionsTest.java index a1193063edf..63d9f595109 100644 --- a/itests/org.openhab.core.config.core.tests/src/main/java/org/openhab/core/config/core/xml/ConfigDescriptionsTest.java +++ b/itests/org.openhab.core.config.core.tests/src/main/java/org/openhab/core/config/core/xml/ConfigDescriptionsTest.java @@ -33,6 +33,8 @@ import org.openhab.core.config.core.ConfigDescriptionParameter.Type; import org.openhab.core.config.core.ConfigDescriptionParameterGroup; import org.openhab.core.config.core.ConfigDescriptionRegistry; +import org.openhab.core.config.core.FilterCriteria; +import org.openhab.core.config.core.ParameterOption; import org.openhab.core.test.BundleCloseable; import org.openhab.core.test.SyntheticBundleInstaller; import org.openhab.core.test.java.JavaOSGiTest; @@ -119,7 +121,7 @@ public void assertThatConfigDescriptionsAreLoadedProperly() throws Exception { assertThat(colorItemParameter.getContext(), is("item")); assertThat(colorItemParameter.getFilterCriteria(), is(notNullValue())); assertThat( - colorItemParameter.getFilterCriteria().stream().map(c -> c.toString()) + colorItemParameter.getFilterCriteria().stream().map(FilterCriteria::toString) .collect(Collectors.joining(", ")), is("FilterCriteria [name=\"tags\", value=\"alarm, light\"], FilterCriteria [name=\"type\", value=\"color\"], FilterCriteria [name=\"binding-id\", value=\"hue\"]")); @@ -136,7 +138,7 @@ public void assertThatConfigDescriptionsAreLoadedProperly() throws Exception { assertThat(listParameter1.isVerifyable(), is(false)); assertThat(listParameter1.getLimitToOptions(), is(true)); assertThat(listParameter1.getMultipleLimit(), is(nullValue())); - assertThat(listParameter1.getOptions().stream().map(o -> o.toString()).collect(joining(", ")), is( + assertThat(listParameter1.getOptions().stream().map(ParameterOption::toString).collect(joining(", ")), is( "ParameterOption [value=\"key1\", label=\"label1\"], ParameterOption [value=\"key2\", label=\"label2\"]")); ConfigDescriptionParameter listParameter2 = findParameter(englishDescription, "list2"); diff --git a/itests/org.openhab.core.config.discovery.tests/src/main/java/org/openhab/core/config/discovery/DiscoveryServiceRegistryOSGiTest.java b/itests/org.openhab.core.config.discovery.tests/src/main/java/org/openhab/core/config/discovery/DiscoveryServiceRegistryOSGiTest.java index 3aeb235d832..011da7699e8 100644 --- a/itests/org.openhab.core.config.discovery.tests/src/main/java/org/openhab/core/config/discovery/DiscoveryServiceRegistryOSGiTest.java +++ b/itests/org.openhab.core.config.discovery.tests/src/main/java/org/openhab/core/config/discovery/DiscoveryServiceRegistryOSGiTest.java @@ -359,7 +359,7 @@ public void testStartScanTwoDiscoveryServices() { discoveryServiceRegistry.addDiscoveryListener(discoveryListenerMock); discoveryServiceRegistry.startScan(new ThingTypeUID(ANY_BINDING_ID_1, ANY_THING_TYPE_1), mockScanListener1); - waitForAssert(() -> mockScanListener1.onFinished()); + waitForAssert(mockScanListener1::onFinished); verify(discoveryListenerMock, times(2)).thingDiscovered(any(), any()); } diff --git a/itests/org.openhab.core.config.discovery.tests/src/main/java/org/openhab/core/config/discovery/internal/InboxOSGiTest.java b/itests/org.openhab.core.config.discovery.tests/src/main/java/org/openhab/core/config/discovery/internal/InboxOSGiTest.java index 983c8d0a47e..5646ab4264c 100644 --- a/itests/org.openhab.core.config.discovery.tests/src/main/java/org/openhab/core/config/discovery/internal/InboxOSGiTest.java +++ b/itests/org.openhab.core.config.discovery.tests/src/main/java/org/openhab/core/config/discovery/internal/InboxOSGiTest.java @@ -219,8 +219,8 @@ public void cleanUp() { EventSubscriber inboxEventSubscriber = new EventSubscriber() { @Override public void receive(Event event) { - if (event instanceof InboxRemovedEvent) { - removedInboxThingUIDs.add(((InboxRemovedEvent) event).getDiscoveryResult().thingUID); + if (event instanceof InboxRemovedEvent removedEvent) { + removedInboxThingUIDs.add(removedEvent.getDiscoveryResult().thingUID); } } @@ -235,7 +235,7 @@ public Set getSubscribedEventTypes() { registry.remove(BRIDGE_THING_UID); managedThingProvider.getAll().forEach(thing -> managedThingProvider.remove(thing.getUID())); - inboxListeners.forEach(listener -> inbox.removeInboxListener(listener)); + inboxListeners.forEach(inbox::removeInboxListener); inbox.getAll().stream().forEach(discoveryResult -> inbox.remove(discoveryResult.getThingUID())); discoveryResults.clear(); @@ -1053,7 +1053,7 @@ public void assertThatResultWithMissingThingTypeNotAdded() throws ExecutionExcep CompletableFuture future = inbox.add(discoveryResult); - waitForAssert(() -> future.isDone(), 30, 5); + waitForAssert(future::isDone, 30, 5); assertThat(future.get(), is(false)); } @@ -1071,7 +1071,7 @@ public void assertThatResultWithLaterAddedThingTypeIsAdded() throws ExecutionExc dummyThingTypeProvider.add(thingTypeUID, ThingTypeBuilder.instance(thingTypeUID, "label").build()); - waitForAssert(() -> future.isDone(), 30, 5); + waitForAssert(future::isDone, 30, 5); assertThat(future.get(), is(true)); } diff --git a/itests/org.openhab.core.config.discovery.usbserial.linuxsysfs.tests/src/main/java/org/openhab/core/config/discovery/usbserial/linuxsysfs/internal/SysFsUsbSerialScannerTest.java b/itests/org.openhab.core.config.discovery.usbserial.linuxsysfs.tests/src/main/java/org/openhab/core/config/discovery/usbserial/linuxsysfs/internal/SysFsUsbSerialScannerTest.java index 8b73f376206..92ec4d60e1c 100644 --- a/itests/org.openhab.core.config.discovery.usbserial.linuxsysfs.tests/src/main/java/org/openhab/core/config/discovery/usbserial/linuxsysfs/internal/SysFsUsbSerialScannerTest.java +++ b/itests/org.openhab.core.config.discovery.usbserial.linuxsysfs.tests/src/main/java/org/openhab/core/config/discovery/usbserial/linuxsysfs/internal/SysFsUsbSerialScannerTest.java @@ -88,7 +88,7 @@ public void setup() throws IOException { @Test public void testIOExceptionIfSysfsTtyDoesNotExist() throws IOException { delete(sysfsTtyPath); - assertThrows(IOException.class, () -> scanner.scan()); + assertThrows(IOException.class, scanner::scan); } @Test @@ -246,6 +246,6 @@ private enum DeviceCreationOption { NO_VENDOR_ID, NO_PRODUCT_ID, NO_INTERFACE_NUMBER, - NON_USB_DEVICE; + NON_USB_DEVICE } } diff --git a/itests/org.openhab.core.model.thing.tests/src/main/java/org/openhab/core/model/thing/test/hue/GenericThingProviderTest.java b/itests/org.openhab.core.model.thing.tests/src/main/java/org/openhab/core/model/thing/test/hue/GenericThingProviderTest.java index a84cda47e23..b57c389917f 100644 --- a/itests/org.openhab.core.model.thing.tests/src/main/java/org/openhab/core/model/thing/test/hue/GenericThingProviderTest.java +++ b/itests/org.openhab.core.model.thing.tests/src/main/java/org/openhab/core/model/thing/test/hue/GenericThingProviderTest.java @@ -271,7 +271,7 @@ public void assertThatBridgeUIDcanBbeSet() { assertThat(actualThings.size(), is(2)); Thing thing = actualThings.stream().filter(t -> !(t instanceof Bridge)).findFirst().get(); - Bridge bridge = (Bridge) actualThings.stream().filter(t -> t instanceof Bridge).findFirst().get(); + Bridge bridge = (Bridge) actualThings.stream().filter(Bridge.class::isInstance).findFirst().get(); assertThat(thing.getBridgeUID().toString(), is("hue:bridge:bridge1")); assertThat(bridge.getThings().contains(thing), is(true)); @@ -302,22 +302,22 @@ public void assertThatChannelDefinitionsCanBeReferenced() { assertThat(actualThings.size(), is(4)); - actualThings.stream().filter(t -> "bulb_default".equals(t.getUID().getId().toString())).findFirst().get(); + actualThings.stream().filter(t -> "bulb_default".equals(t.getUID().getId())).findFirst().get(); - Thing thingDefault = actualThings.stream().filter(t -> "bulb_default".equals(t.getUID().getId().toString())) - .findFirst().get(); + Thing thingDefault = actualThings.stream().filter(t -> "bulb_default".equals(t.getUID().getId())).findFirst() + .get(); assertThat(thingDefault.getChannels().size(), is(2)); - Thing thingCustom = actualThings.stream().filter(t -> "bulb_custom".equals(t.getUID().getId().toString())) - .findFirst().get(); + Thing thingCustom = actualThings.stream().filter(t -> "bulb_custom".equals(t.getUID().getId())).findFirst() + .get(); assertThat(thingCustom.getChannels().size(), is(4)); assertThat(thingCustom.getChannel("manual").getChannelTypeUID(), is(equalTo(new ChannelTypeUID("hue", "color")))); assertThat(thingCustom.getChannel("manual").getLabel(), is("colorLabel")); // default from thing type assertThat(thingCustom.getChannel("manualWithLabel").getLabel(), is("With Label")); // manual overrides default - Thing thingBroken = actualThings.stream().filter(t -> "bulb_broken".equals(t.getUID().getId().toString())) - .findFirst().get(); + Thing thingBroken = actualThings.stream().filter(t -> "bulb_broken".equals(t.getUID().getId())).findFirst() + .get(); assertThat(thingBroken.getChannels().size(), is(4)); assertThat(thingBroken.getChannel("manual").getChannelTypeUID(), is(equalTo(new ChannelTypeUID("hue", "broken")))); @@ -343,8 +343,8 @@ public void assertThatChannelDefinitionsWithDimensionAreParsed() { assertThat(actualThings.size(), is(1)); - Thing thingDefault = actualThings.stream().filter(t -> "sensor_custom".equals(t.getUID().getId().toString())) - .findFirst().get(); + Thing thingDefault = actualThings.stream().filter(t -> "sensor_custom".equals(t.getUID().getId())).findFirst() + .get(); assertThat(thingDefault.getChannels().size(), is(2)); assertThat(thingDefault.getChannel("sensor1").getAcceptedItemType(), is("Number:Temperature")); @@ -363,8 +363,8 @@ public void assertThatConfigParameterListsAreParsed() { assertThat(actualThings.size(), is(1)); - Thing thingDefault = actualThings.stream().filter(t -> "sensor_custom".equals(t.getUID().getId().toString())) - .findFirst().get(); + Thing thingDefault = actualThings.stream().filter(t -> "sensor_custom".equals(t.getUID().getId())).findFirst() + .get(); @SuppressWarnings("unchecked") Collection valueCollection = (Collection) thingDefault.getConfiguration().get("config"); diff --git a/itests/org.openhab.core.model.thing.tests/src/main/java/org/openhab/core/model/thing/test/hue/GenericThingProviderTest4.java b/itests/org.openhab.core.model.thing.tests/src/main/java/org/openhab/core/model/thing/test/hue/GenericThingProviderTest4.java index 2c41f773293..55229369cf2 100644 --- a/itests/org.openhab.core.model.thing.tests/src/main/java/org/openhab/core/model/thing/test/hue/GenericThingProviderTest4.java +++ b/itests/org.openhab.core.model.thing.tests/src/main/java/org/openhab/core/model/thing/test/hue/GenericThingProviderTest4.java @@ -120,8 +120,8 @@ public void setUp() { hueThingHandlerFactory = new TestHueThingHandlerFactoryX(componentContextMock) { @Override protected @Nullable ThingHandler createHandler(final Thing thing) { - if (thing instanceof Bridge) { - return new TestBridgeHandler((Bridge) thing); + if (thing instanceof Bridge bridge) { + return new TestBridgeHandler(bridge); } else { return new BaseThingHandler(thing) { @Override diff --git a/itests/org.openhab.core.model.thing.testsupport/src/main/java/org/openhab/core/model/thing/testsupport/hue/TestHueThingHandlerFactory.java b/itests/org.openhab.core.model.thing.testsupport/src/main/java/org/openhab/core/model/thing/testsupport/hue/TestHueThingHandlerFactory.java index c9f4e324c83..fce64d94361 100644 --- a/itests/org.openhab.core.model.thing.testsupport/src/main/java/org/openhab/core/model/thing/testsupport/hue/TestHueThingHandlerFactory.java +++ b/itests/org.openhab.core.model.thing.testsupport/src/main/java/org/openhab/core/model/thing/testsupport/hue/TestHueThingHandlerFactory.java @@ -110,8 +110,8 @@ private ThingUID getLightUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thing @Override protected @Nullable ThingHandler createHandler(Thing thing) { - if (thing instanceof Bridge) { - return new BaseBridgeHandler((Bridge) thing) { + if (thing instanceof Bridge bridge) { + return new BaseBridgeHandler(bridge) { @Override public void handleCommand(ChannelUID channelUID, Command command) { } diff --git a/itests/org.openhab.core.tests/src/main/java/org/openhab/core/internal/items/ItemUpdaterOSGiTest.java b/itests/org.openhab.core.tests/src/main/java/org/openhab/core/internal/items/ItemUpdaterOSGiTest.java index 56169bf46a3..e6e06fb3006 100644 --- a/itests/org.openhab.core.tests/src/main/java/org/openhab/core/internal/items/ItemUpdaterOSGiTest.java +++ b/itests/org.openhab.core.tests/src/main/java/org/openhab/core/internal/items/ItemUpdaterOSGiTest.java @@ -14,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.*; +import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; @@ -43,7 +44,7 @@ public class ItemUpdaterOSGiTest extends JavaOSGiTest { private @NonNullByDefault({}) EventPublisher eventPublisher; private @NonNullByDefault({}) ItemRegistry itemRegistry; - private final ConcurrentLinkedQueue receivedEvents = new ConcurrentLinkedQueue<>(); + private final Queue receivedEvents = new ConcurrentLinkedQueue<>(); @BeforeEach public void setUp() { diff --git a/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/GroupItemOSGiTest.java b/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/GroupItemOSGiTest.java index e1d8afe2c82..b9c1567dc16 100644 --- a/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/GroupItemOSGiTest.java +++ b/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/GroupItemOSGiTest.java @@ -84,7 +84,7 @@ public class GroupItemOSGiTest extends JavaOSGiTest { private final List events = new LinkedList<>(); private final GroupFunctionHelper groupFunctionHelper = new GroupFunctionHelper(); - private final EventPublisher publisher = event -> events.add(event); + private final EventPublisher publisher = events::add; private @NonNullByDefault({}) ItemRegistry itemRegistry; private @NonNullByDefault({}) ItemStateConverter itemStateConverter; @@ -135,7 +135,7 @@ public void testItemUpdateWithItemRegistry() { itemRegistry.update(updatedItem); waitForAssert(() -> assertThat(events.size(), is(1))); - List stateChanges = events.stream().filter(it -> it instanceof ItemUpdatedEvent) + List stateChanges = events.stream().filter(ItemUpdatedEvent.class::isInstance) .collect(Collectors.toList()); assertThat(stateChanges.size(), is(1)); @@ -281,7 +281,7 @@ public void testGetAllMembersWithFilter() { subGroup.addMember(member1); rootGroupItem.addMember(subGroup); - Set members = rootGroupItem.getMembers(i -> i instanceof GroupItem); + Set members = rootGroupItem.getMembers(GroupItem.class::isInstance); assertThat(members.size(), is(1)); members = rootGroupItem.getMembers(i -> "mem1".equals(i.getLabel())); @@ -440,7 +440,7 @@ public void assertThatGroupItemPostsEventsForChangesCorrectly() { waitForAssert(() -> assertThat(events.size(), is(2))); - List updates = events.stream().filter(it -> it instanceof GroupStateUpdatedEvent) + List updates = events.stream().filter(GroupStateUpdatedEvent.class::isInstance) .collect(Collectors.toList()); assertThat(updates.size(), is(1)); @@ -451,7 +451,7 @@ public void assertThatGroupItemPostsEventsForChangesCorrectly() { .replace("{itemName}", groupItem.getName()))); assertThat(update.getItemState(), is(groupItem.getState())); - List changes = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent) + List changes = events.stream().filter(GroupItemStateChangedEvent.class::isInstance) .collect(Collectors.toList()); assertThat(changes.size(), is(1)); @@ -490,7 +490,7 @@ public void assertThatGroupItemChangesRespectGroupFunctionOR() throws Interrupte waitForAssert(() -> assertThat(events, hasSize(2))); - List groupItemStateChangedEvents = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent) + List groupItemStateChangedEvents = events.stream().filter(GroupItemStateChangedEvent.class::isInstance) .collect(Collectors.toList()); assertThat(groupItemStateChangedEvents, hasSize(1)); @@ -533,11 +533,11 @@ public void assertThatItemCommandEventsAreEmittedFromCommand() { waitForAssert(() -> assertThat(events, hasSize(2))); - List itemCommandEvents = events.stream().filter(it -> it instanceof ItemCommandEvent) + List itemCommandEvents = events.stream().filter(ItemCommandEvent.class::isInstance) .collect(Collectors.toList()); assertThat(itemCommandEvents, hasSize(2)); - List groupItemStateChangedEvents = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent) + List groupItemStateChangedEvents = events.stream().filter(GroupItemStateChangedEvent.class::isInstance) .collect(Collectors.toList()); assertThat(groupItemStateChangedEvents, hasSize(0)); @@ -563,11 +563,11 @@ public void assertThatGroupItemChangesRespectGroupFunctionORWithUNDEF() throws I waitForAssert(() -> assertThat(events, hasSize(2))); - List changes = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent) + List changes = events.stream().filter(GroupItemStateChangedEvent.class::isInstance) .collect(Collectors.toList()); assertThat(changes, hasSize(1)); - List updates = events.stream().filter(it -> it instanceof GroupStateUpdatedEvent) + List updates = events.stream().filter(GroupStateUpdatedEvent.class::isInstance) .collect(Collectors.toList()); assertThat(updates, hasSize(1)); @@ -592,10 +592,10 @@ public void assertThatGroupItemChangesRespectGroupFunctionORWithUNDEF() throws I assertThat(events, hasSize(2)); - changes = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent).collect(Collectors.toList()); + changes = events.stream().filter(GroupItemStateChangedEvent.class::isInstance).collect(Collectors.toList()); assertThat(changes, hasSize(0)); - updates = events.stream().filter(it -> it instanceof GroupStateUpdatedEvent).collect(Collectors.toList()); + updates = events.stream().filter(GroupStateUpdatedEvent.class::isInstance).collect(Collectors.toList()); assertThat(updates, hasSize(2)); assertThat(groupItem.getState(), is(OnOffType.ON)); @@ -620,7 +620,7 @@ public void assertThatGroupItemChangesRespectGroupFunctionAND() { waitForAssert(() -> assertThat(events, hasSize(2))); - List changes = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent) + List changes = events.stream().filter(GroupItemStateChangedEvent.class::isInstance) .collect(Collectors.toList()); assertThat(changes, hasSize(1)); @@ -640,7 +640,7 @@ public void assertThatGroupItemChangesRespectGroupFunctionAND() { waitForAssert(() -> assertThat(events, hasSize(2))); - changes = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent).collect(Collectors.toList()); + changes = events.stream().filter(GroupItemStateChangedEvent.class::isInstance).collect(Collectors.toList()); assertThat(changes, hasSize(1)); change = (GroupItemStateChangedEvent) changes.get(0); @@ -754,7 +754,7 @@ public void assertThatGroupItemwithDimmeritemAcceptsGetsPercentTypeStateIfMember waitForAssert(() -> assertThat(events.size(), is(2))); - List changes = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent) + List changes = events.stream().filter(GroupItemStateChangedEvent.class::isInstance) .collect(Collectors.toList()); GroupItemStateChangedEvent change = (GroupItemStateChangedEvent) changes.get(0); assertThat(change.getItemName(), is(groupItem.getName())); @@ -773,7 +773,7 @@ public void assertThatGroupItemwithDimmeritemAcceptsGetsPercentTypeStateIfMember waitForAssert(() -> assertThat(events.size(), is(2))); - changes = events.stream().filter(it -> it instanceof GroupItemStateChangedEvent).collect(Collectors.toList()); + changes = events.stream().filter(GroupItemStateChangedEvent.class::isInstance).collect(Collectors.toList()); assertThat(changes.size(), is(1)); change = (GroupItemStateChangedEvent) changes.get(0); diff --git a/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/ItemRegistryImplTest.java b/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/ItemRegistryImplTest.java index 03dfe1c8cc7..c7d98955117 100644 --- a/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/ItemRegistryImplTest.java +++ b/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/ItemRegistryImplTest.java @@ -132,7 +132,7 @@ public void assertGetItemsByTagReturnsItemFromRegisteredItemProvider() { List items = new ArrayList<>(itemRegistry.getItemsByTag(CAMERA_TAG)); assertThat(items, hasSize(4)); - List itemNames = items.stream().map(i -> i.getName()).collect(toList()); + List itemNames = items.stream().map(Item::getName).collect(toList()); assertThat(itemNames, hasItem(CAMERA_ITEM_NAME1)); assertThat(itemNames, hasItem(CAMERA_ITEM_NAME2)); assertThat(itemNames, hasItem(CAMERA_ITEM_NAME3)); @@ -144,7 +144,7 @@ public void assertGetItemsByTagInUppercaseReturnsItemFromRegisteredItemProvider( List items = new ArrayList<>(itemRegistry.getItemsByTag(CAMERA_TAG_UPPERCASE)); assertThat(items, hasSize(4)); - List itemNames = items.stream().map(i -> i.getName()).collect(toList()); + List itemNames = items.stream().map(Item::getName).collect(toList()); assertThat(itemNames, hasItem(CAMERA_ITEM_NAME1)); assertThat(itemNames, hasItem(CAMERA_ITEM_NAME2)); assertThat(itemNames, hasItem(CAMERA_ITEM_NAME3)); @@ -156,7 +156,7 @@ public void assertGetItemsByTagAndTypeReturnsItemFromRegistereItemProvider() { List items = new ArrayList<>(itemRegistry.getItemsByTagAndType("Switch", CAMERA_TAG)); assertThat(items, hasSize(2)); - List itemNames = items.stream().map(i -> i.getName()).collect(toList()); + List itemNames = items.stream().map(Item::getName).collect(toList()); assertThat(itemNames, hasItem(CAMERA_ITEM_NAME1)); assertThat(itemNames, hasItem(CAMERA_ITEM_NAME2)); } @@ -178,7 +178,7 @@ public void assertGetItemsByTagCanFilterByClassAndTag() { List items = new ArrayList<>(itemRegistry.getItemsByTag(SwitchItem.class, CAMERA_TAG)); assertThat(items, hasSize(2)); - List itemNames = items.stream().map(i -> i.getName()).collect(toList()); + List itemNames = items.stream().map(GenericItem::getName).collect(toList()); assertThat(itemNames, hasItem(CAMERA_ITEM_NAME1)); assertThat(itemNames, hasItem(CAMERA_ITEM_NAME2)); } diff --git a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/binding/BindingBaseClassesOSGiTest.java b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/binding/BindingBaseClassesOSGiTest.java index 6a81832a98b..c9fb1b9142f 100644 --- a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/binding/BindingBaseClassesOSGiTest.java +++ b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/binding/BindingBaseClassesOSGiTest.java @@ -146,7 +146,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { @Override protected @Nullable ThingHandler createHandler(Thing thing) { - ThingHandler handler = (thing instanceof Bridge) ? new SimpleBridgeHandler((Bridge) thing) + ThingHandler handler = (thing instanceof Bridge b) ? new SimpleBridgeHandler(b) : new SimpleThingHandler(thing); handlers.add(handler); return handler; diff --git a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/binding/firmware/FirmwareTest.java b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/binding/firmware/FirmwareTest.java index 42997f7b59a..50aec670b11 100644 --- a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/binding/firmware/FirmwareTest.java +++ b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/binding/firmware/FirmwareTest.java @@ -287,7 +287,7 @@ public void assertThatFirmwareWithInvalidMD5HashValueThrowsExceptionForGetBytes( Firmware firmware = FirmwareBuilder.create(THING_TYPE_UID, "1") .withInputStream(bundleContext.getBundle().getResource(FILE_NAME).openStream()) .withMd5Hash("78805a221a988e79ef3f42d7c5bfd419").build(); - assertThrows(IllegalStateException.class, () -> firmware.getBytes()); + assertThrows(IllegalStateException.class, firmware::getBytes); } @Test diff --git a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/ChannelLinkNotifierOSGiTest.java b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/ChannelLinkNotifierOSGiTest.java index 42d01aa87d1..f6e74158452 100644 --- a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/ChannelLinkNotifierOSGiTest.java +++ b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/ChannelLinkNotifierOSGiTest.java @@ -218,14 +218,14 @@ public void afterEach() throws Exception { @Override public void receive(Event event) { logger.debug("Received event: {}", event); - if (event instanceof AbstractItemChannelLinkRegistryEvent) { - ItemChannelLinkDTO link = ((AbstractItemChannelLinkRegistryEvent) event).getLink(); + if (event instanceof AbstractItemChannelLinkRegistryEvent registryEvent) { + ItemChannelLinkDTO link = registryEvent.getLink(); removedItemChannelLinkUIDs .add(AbstractLink.getIDFor(link.itemName, new ChannelUID(link.channelUID))); - } else if (event instanceof AbstractItemRegistryEvent) { - removedItemNames.add(((AbstractItemRegistryEvent) event).getItem().name); - } else if (event instanceof AbstractThingRegistryEvent) { - removedThingUIDs.add(((AbstractThingRegistryEvent) event).getThing().UID); + } else if (event instanceof AbstractItemRegistryEvent registryEvent) { + removedItemNames.add(registryEvent.getItem().name); + } else if (event instanceof AbstractThingRegistryEvent registryEvent) { + removedThingUIDs.add(registryEvent.getThing().UID); } } @@ -295,7 +295,7 @@ private Channel createChannel(ThingUID thingUID, int index) { } private void forEachThingChannelUID(Thing thing, Consumer consumer) { - thing.getChannels().stream().map(Channel::getUID).forEach(channelUID -> consumer.accept(channelUID)); + thing.getChannels().stream().map(Channel::getUID).forEach(consumer::accept); } private void addItemsAndLinks(Thing thing, String itemSuffix) { diff --git a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/firmware/FirmwareUpdateServiceTest.java b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/firmware/FirmwareUpdateServiceTest.java index 9a1e46d85c8..b76c45ed81d 100644 --- a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/firmware/FirmwareUpdateServiceTest.java +++ b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/firmware/FirmwareUpdateServiceTest.java @@ -164,7 +164,7 @@ private void initialFirmwareRegistryMocking() { || THING_TYPE_UID3.equals(thing.getThingTypeUID())) { return Collections.emptySet(); } else { - Supplier> supplier = () -> new TreeSet<>(); + Supplier> supplier = TreeSet::new; return Stream.of(FW009_EN, FW111_EN, FW112_EN).collect(Collectors.toCollection(supplier)); } }; @@ -318,7 +318,7 @@ public void testUpdateFirmware() { firmwareUpdateService.updateFirmware(THING1_UID, V112, null); waitForAssert(() -> { - assertThat(thing1.getProperties().get(Thing.PROPERTY_FIRMWARE_VERSION), is(V112.toString())); + assertThat(thing1.getProperties().get(Thing.PROPERTY_FIRMWARE_VERSION), is(V112)); }); assertThat(firmwareUpdateService.getFirmwareStatusInfo(THING1_UID), is(upToDateInfo)); @@ -461,7 +461,7 @@ public void testUpdateFirmwareDowngrade() { firmwareUpdateService.updateFirmware(THING2_UID, V111, null); waitForAssert(() -> { - assertThat(thing2.getProperties().get(Thing.PROPERTY_FIRMWARE_VERSION), is(V111.toString())); + assertThat(thing2.getProperties().get(Thing.PROPERTY_FIRMWARE_VERSION), is(V111)); }); assertThat(firmwareUpdateService.getFirmwareStatusInfo(THING2_UID), is(updateExecutableInfoFw112)); @@ -547,7 +547,7 @@ public void testPrerequisiteVersionCheckIllegalVersion() { || THING_TYPE_UID2.equals(thing.getThingTypeUID())) { return Collections.emptySet(); } else { - Supplier> supplier = () -> new TreeSet<>(); + Supplier> supplier = TreeSet::new; return Stream.of(FW111_FIX_EN, FW113_EN).collect(Collectors.toCollection(supplier)); } }); @@ -583,7 +583,7 @@ public void testPrerequisiteVersionCheck() { || THING_TYPE_UID2.equals(thing.getThingTypeUID())) { return Collections.emptySet(); } else { - Supplier> supplier = () -> new TreeSet<>(); + Supplier> supplier = TreeSet::new; return Stream.of(FW111_FIX_EN, FW113_EN).collect(Collectors.toCollection(supplier)); } }; @@ -702,7 +702,7 @@ public void testEvents() { verify(eventPublisherMock, atLeast(SEQUENCE.length + 1)).post(eventCaptor.capture()); }); events.get().addAll(eventCaptor.getAllValues()); - List list = events.get().stream().filter(event -> event instanceof FirmwareUpdateProgressInfoEvent) + List list = events.get().stream().filter(FirmwareUpdateProgressInfoEvent.class::isInstance) .collect(Collectors.toList()); assertTrue(list.size() >= SEQUENCE.length); for (int i = 0; i < SEQUENCE.length; i++) { @@ -758,7 +758,7 @@ public void testUpdateFirmwareError() { assertResultInfoEvent(THING1_UID, FW112_EN, "unexpected-handler-error", Locale.ENGLISH, "english", 1); assertResultInfoEvent(THING1_UID, FW112_EN, "unexpected-handler-error", Locale.GERMAN, "deutsch", 2); - assertThat(thing1.getProperties().get(Thing.PROPERTY_FIRMWARE_VERSION), is(V111.toString())); + assertThat(thing1.getProperties().get(Thing.PROPERTY_FIRMWARE_VERSION), is(V111)); assertThat(firmwareUpdateService.getFirmwareStatusInfo(THING1_UID), is(updateExecutableInfoFw112)); } @@ -777,7 +777,7 @@ public void testUpdateFirmwareCustomError() { assertResultInfoEvent(THING1_UID, FW112_EN, "test-error", Locale.ENGLISH, "english", 1); assertResultInfoEvent(THING1_UID, FW112_EN, "test-error", Locale.GERMAN, "deutsch", 2); - assertThat(thing1.getProperties().get(Thing.PROPERTY_FIRMWARE_VERSION), is(V111.toString())); + assertThat(thing1.getProperties().get(Thing.PROPERTY_FIRMWARE_VERSION), is(V111)); assertThat(firmwareUpdateService.getFirmwareStatusInfo(THING1_UID), is(updateExecutableInfoFw112)); } @@ -821,9 +821,7 @@ public void testBackgroundTransfer() throws Exception { FirmwareUpdateBackgroundTransferHandler handler4 = mock(FirmwareUpdateBackgroundTransferHandler.class); when(handler4.getThing()).thenReturn(thing4); - doAnswer(invocation -> { - return updateExecutable.get(); - }).when(handler4).isUpdateExecutable(); + doAnswer(invocation -> updateExecutable.get()).when(handler4).isUpdateExecutable(); doAnswer(invocation -> { Firmware firmware = (Firmware) invocation.getArguments()[0]; thing4.setProperty(Thing.PROPERTY_FIRMWARE_VERSION, firmware.getVersion()); @@ -905,7 +903,7 @@ private void assertResultInfoEvent(ThingUID thingUID, Firmware firmware, String ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); verify(eventPublisherMock, atLeast(expectedEventCount)).post(eventCaptor.capture()); List allValues = eventCaptor.getAllValues().stream() - .filter(e -> e instanceof FirmwareUpdateResultInfoEvent).collect(Collectors.toList()); + .filter(FirmwareUpdateResultInfoEvent.class::isInstance).collect(Collectors.toList()); assertEquals(expectedEventCount, allValues.size()); assertFailedFirmwareUpdate(THING1_UID, allValues.get(expectedEventCount - 1), text); }); diff --git a/itests/org.openhab.core.voice.tests/src/main/java/org/openhab/core/voice/internal/TTSServiceStub.java b/itests/org.openhab.core.voice.tests/src/main/java/org/openhab/core/voice/internal/TTSServiceStub.java index 0e768737976..3e90f20e5be 100644 --- a/itests/org.openhab.core.voice.tests/src/main/java/org/openhab/core/voice/internal/TTSServiceStub.java +++ b/itests/org.openhab.core.voice.tests/src/main/java/org/openhab/core/voice/internal/TTSServiceStub.java @@ -73,7 +73,7 @@ public Set getAvailableVoices() { try { Collection> refs = bundleContext.getServiceReferences(Voice.class, null); return refs.stream() // - .map(ref -> bundleContext.getService(ref)) // + .map(bundleContext::getService) // .filter(service -> service.getUID().startsWith(getId())) // .collect(Collectors.toSet()); } catch (InvalidSyntaxException e) { diff --git a/tools/i18n-plugin/src/main/java/org/openhab/core/tools/i18n/plugin/BundleInfoReader.java b/tools/i18n-plugin/src/main/java/org/openhab/core/tools/i18n/plugin/BundleInfoReader.java index 6a11b508d62..0462066d2db 100644 --- a/tools/i18n-plugin/src/main/java/org/openhab/core/tools/i18n/plugin/BundleInfoReader.java +++ b/tools/i18n-plugin/src/main/java/org/openhab/core/tools/i18n/plugin/BundleInfoReader.java @@ -116,20 +116,17 @@ private void readThingInfo(Path ohinfPath, BundleInfo bundleInfo) throws IOExcep return; } for (Object type : types) { - if (type instanceof ThingTypeXmlResult) { - ThingTypeXmlResult result = (ThingTypeXmlResult) type; + if (type instanceof ThingTypeXmlResult result) { bundleInfo.getThingTypesXml().add(result); if (bundleInfo.getAddonId().isBlank()) { bundleInfo.setAddonId(result.getUID().getBindingId()); } - } else if (type instanceof ChannelGroupTypeXmlResult) { - ChannelGroupTypeXmlResult result = (ChannelGroupTypeXmlResult) type; + } else if (type instanceof ChannelGroupTypeXmlResult result) { bundleInfo.getChannelGroupTypesXml().add(result); if (bundleInfo.getAddonId().isBlank()) { bundleInfo.setAddonId(result.getUID().getBindingId()); } - } else if (type instanceof ChannelTypeXmlResult) { - ChannelTypeXmlResult result = (ChannelTypeXmlResult) type; + } else if (type instanceof ChannelTypeXmlResult result) { bundleInfo.getChannelTypesXml().add(result); if (bundleInfo.getAddonId().isBlank()) { bundleInfo.setAddonId(result.toChannelType().getUID().getBindingId()); From 58df2b272dd2f14d95d0a9e959ca452f02eaa37d Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Mon, 1 May 2023 02:38:25 +1000 Subject: [PATCH 055/126] Remove GenericScriptEngineFactory (#3550) Signed-off-by: Jimmy Tanagra --- .../internal/GenericScriptEngineFactory.java | 30 ------------- .../internal/ScriptEngineFactoryHelper.java | 1 - .../internal/ScriptEngineManagerImpl.java | 43 ++++--------------- 3 files changed, 8 insertions(+), 66 deletions(-) delete mode 100644 bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/GenericScriptEngineFactory.java diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/GenericScriptEngineFactory.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/GenericScriptEngineFactory.java deleted file mode 100644 index e6fe0c49fd6..00000000000 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/GenericScriptEngineFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.automation.module.script.internal; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.automation.module.script.AbstractScriptEngineFactory; -import org.openhab.core.automation.module.script.ScriptEngineFactory; -import org.osgi.service.component.annotations.Component; - -/** - * An implementation of {@link ScriptEngineFactory} for ScriptEngines that do not require customizations. - * - * @author Simon Merschjohann - Initial contribution - * @author Scott Rushworth - added service and removed methods now inherited from AbstractScriptEngineFactory - */ -@NonNullByDefault -@Component(service = ScriptEngineFactory.class) -public class GenericScriptEngineFactory extends AbstractScriptEngineFactory { - -} diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryHelper.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryHelper.java index 46871bc9cc4..aa9935baba1 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryHelper.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryHelper.java @@ -76,7 +76,6 @@ public static String getLanguageName(javax.script.ScriptEngineFactory factory) { } public static Optional getPreferredExtension(ScriptEngineFactory factory) { - // return an Optional because GenericScriptEngineFactory has no scriptTypes return factory.getScriptTypes().stream().filter(type -> !type.contains("/")) .min(Comparator.comparing(String::length)); } diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineManagerImpl.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineManagerImpl.java index 3467aa7698e..bdab13e386f 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineManagerImpl.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineManagerImpl.java @@ -60,8 +60,7 @@ public class ScriptEngineManagerImpl implements ScriptEngineManager { private final Logger logger = LoggerFactory.getLogger(ScriptEngineManagerImpl.class); private final Map loadedScriptEngineInstances = new HashMap<>(); - private final Map customSupport = new HashMap<>(); - private final Map genericSupport = new HashMap<>(); + private final Map factories = new HashMap<>(); private final ScriptExtensionManager scriptExtensionManager; private final Set listeners = new HashSet<>(); @@ -75,11 +74,7 @@ public void addScriptEngineFactory(ScriptEngineFactory engineFactory) { List scriptTypes = engineFactory.getScriptTypes(); logger.trace("{}.getScriptTypes(): {}", engineFactory.getClass().getSimpleName(), scriptTypes); for (String scriptType : scriptTypes) { - if (isCustomFactory(engineFactory)) { - this.customSupport.put(scriptType, engineFactory); - } else { - this.genericSupport.put(scriptType, engineFactory); - } + factories.put(scriptType, engineFactory); listeners.forEach(listener -> listener.factoryAdded(scriptType)); } if (logger.isDebugEnabled()) { @@ -88,10 +83,10 @@ public void addScriptEngineFactory(ScriptEngineFactory engineFactory) { if (scriptEngine != null) { javax.script.ScriptEngineFactory factory = scriptEngine.getFactory(); logger.debug( - "Initialized a {} ScriptEngineFactory for {} ({}): supports {} ({}) with file extensions {}, names {}, and mimetypes {}", - (isCustomFactory(engineFactory)) ? "custom" : "generic", factory.getEngineName(), - factory.getEngineVersion(), factory.getLanguageName(), factory.getLanguageVersion(), - factory.getExtensions(), factory.getNames(), factory.getMimeTypes()); + "Initialized a ScriptEngineFactory for {} ({}): supports {} ({}) with file extensions {}, names {}, and mimetypes {}", + factory.getEngineName(), factory.getEngineVersion(), factory.getLanguageName(), + factory.getLanguageVersion(), factory.getExtensions(), factory.getNames(), + factory.getMimeTypes()); } else { logger.trace("addScriptEngineFactory: engine was null"); } @@ -105,26 +100,12 @@ public void removeScriptEngineFactory(ScriptEngineFactory engineFactory) { List scriptTypes = engineFactory.getScriptTypes(); logger.trace("{}.getScriptTypes(): {}", engineFactory.getClass().getSimpleName(), scriptTypes); for (String scriptType : scriptTypes) { - if (isCustomFactory(engineFactory)) { - this.customSupport.remove(scriptType, engineFactory); - } else { - this.genericSupport.remove(scriptType, engineFactory); - } + factories.remove(scriptType, engineFactory); listeners.forEach(listener -> listener.factoryRemoved(scriptType)); } logger.debug("Removed {}", engineFactory.getClass().getSimpleName()); } - /** - * This method is used to determine if a given {@link ScriptEngineFactory} is generic or customized. - * - * @param engineFactory {@link ScriptEngineFactory} - * @return true, if the {@link ScriptEngineFactory} is custom, otherwise false - */ - private boolean isCustomFactory(ScriptEngineFactory engineFactory) { - return !(engineFactory instanceof GenericScriptEngineFactory); - } - @Override public @Nullable ScriptEngineContainer createScriptEngine(String scriptType, String engineIdentifier) { ScriptEngineContainer result = null; @@ -256,15 +237,7 @@ private void removeScriptExtensions(String pathIdentifier) { * @return {@link ScriptEngineFactory} or null */ private @Nullable ScriptEngineFactory findEngineFactory(String scriptType) { - ScriptEngineFactory customFactory = customSupport.get(scriptType); - if (customFactory != null) { - return customFactory; - } - ScriptEngineFactory genericFactory = genericSupport.get(scriptType); - if (genericFactory != null) { - return genericFactory; - } - return null; + return factories.get(scriptType); } @Override From a77a303dc3710f6a6d927f523e49ac6d4b642b6c Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Mon, 1 May 2023 08:27:15 +0200 Subject: [PATCH 056/126] Code cleanup: Use Java 17 features (#3585) * Code cleanup: Use Java 17 features Signed-off-by: Holger Friedrich --- .../commands/AutomationCommandList.java | 2 +- .../internal/AbstractAuthPageServlet.java | 2 +- .../openhab/core/io/net/http/HttpUtil.java | 2 +- .../core/io/transport/modbus/BitArray.java | 3 +- .../transport/modbus/ModbusRegisterArray.java | 3 +- .../item/internal/GenericItemProvider.java | 16 +++---- .../core/model/script/actions/BusEvent.java | 3 +- .../script/engine/ScriptParsingException.java | 6 +-- .../model/script/lib/NumberExtensions.java | 48 +++++++++---------- .../core/thing/ManagedThingProvider.java | 1 + .../xml/internal/ThingDescriptionList.java | 2 + .../core/service/StartLevelService.java | 4 +- 12 files changed, 44 insertions(+), 48 deletions(-) diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/commands/AutomationCommandList.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/commands/AutomationCommandList.java index fbe9e956ebc..12762f0de99 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/commands/AutomationCommandList.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/commands/AutomationCommandList.java @@ -71,7 +71,7 @@ public AutomationCommandList(String command, String[] params, int adminType, */ @Override public String execute() { - if (parsingResult != SUCCESS) { + if (!parsingResult.equals(SUCCESS)) { return parsingResult; } if (providerType == AutomationCommands.MODULE_TYPE_PROVIDER) { diff --git a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AbstractAuthPageServlet.java b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AbstractAuthPageServlet.java index d08b6cf18bf..ff3e18aa610 100644 --- a/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AbstractAuthPageServlet.java +++ b/bundles/org.openhab.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AbstractAuthPageServlet.java @@ -51,7 +51,7 @@ @NonNullByDefault public abstract class AbstractAuthPageServlet extends HttpServlet { - protected static final long serialVersionUID = 5340598701104679840L; + private static final long serialVersionUID = 5340598701104679840L; private static final String MESSAGES_BUNDLE_NAME = "messages"; diff --git a/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/HttpUtil.java b/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/HttpUtil.java index cb108e33737..76deaab77a1 100644 --- a/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/HttpUtil.java +++ b/bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/HttpUtil.java @@ -319,7 +319,7 @@ private static boolean shouldUseProxy(String urlString, String nonProxyHosts) { if (host.contains("*")) { // the nonProxyHots-pattern allows wildcards '*' which must // be masked to be used with regular expressions - String hostRegexp = host.replaceAll("\\.", "\\\\."); + String hostRegexp = host.replace(".", "\\."); hostRegexp = hostRegexp.replaceAll("\\*", ".*"); if (givenHost.matches(hostRegexp)) { return false; diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/BitArray.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/BitArray.java index 9713d41a0ad..370b4e122d9 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/BitArray.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/BitArray.java @@ -113,8 +113,7 @@ public int size() { @Override public String toString() { - return new StringBuilder("BitArray(bits=").append(length == 0 ? "" : toBinaryString()).append(")") - .toString(); + return "BitArray(bits=" + (length == 0 ? "" : toBinaryString()) + ")"; } @Override diff --git a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusRegisterArray.java b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusRegisterArray.java index cf72c82ce35..63902f8d1e1 100644 --- a/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusRegisterArray.java +++ b/bundles/org.openhab.core.io.transport.modbus/src/main/java/org/openhab/core/io/transport/modbus/ModbusRegisterArray.java @@ -94,8 +94,7 @@ public String toString() { if (bytes.length == 0) { return "ModbusRegisterArray()"; } - return new StringBuilder(bytes.length).append("ModbusRegisterArray(").append(toHexString()).append(')') - .toString(); + return "ModbusRegisterArray(" + toHexString() + ')'; } /** diff --git a/bundles/org.openhab.core.model.item/src/org/openhab/core/model/item/internal/GenericItemProvider.java b/bundles/org.openhab.core.model.item/src/org/openhab/core/model/item/internal/GenericItemProvider.java index 3e39efa4e15..f4063d55c8e 100644 --- a/bundles/org.openhab.core.model.item/src/org/openhab/core/model/item/internal/GenericItemProvider.java +++ b/bundles/org.openhab.core.model.item/src/org/openhab/core/model/item/internal/GenericItemProvider.java @@ -249,7 +249,7 @@ private void processBindingConfigsFromModel(String modelName, EventType type) { return null; } } - if (item instanceof ActiveItem) { + if (item instanceof ActiveItem activeItem) { String label = modelItem.getLabel(); String format = extractFormat(label); if (format != null) { @@ -257,9 +257,9 @@ private void processBindingConfigsFromModel(String modelName, EventType type) { stateDescriptionFragments.put(modelItem.getName(), StateDescriptionFragmentBuilder.create().withPattern(format).build()); } - ((ActiveItem) item).setLabel(label); - ((ActiveItem) item).setCategory(modelItem.getIcon()); - assignTags(modelItem, (ActiveItem) item); + activeItem.setLabel(label); + activeItem.setCategory(modelItem.getIcon()); + assignTags(modelItem, activeItem); return item; } else { return null; @@ -444,11 +444,11 @@ private boolean hasGroupItemChanged(Item item1, Item item2) { GroupItem gItem1 = null; GroupItem gItem2 = null; - if (item1 instanceof GroupItem) { - gItem1 = (GroupItem) item1; + if (item1 instanceof GroupItem item) { + gItem1 = item; } - if (item2 instanceof GroupItem) { - gItem2 = (GroupItem) item2; + if (item2 instanceof GroupItem item) { + gItem2 = item; } if (gItem1 == null && gItem2 == null) { diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/BusEvent.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/BusEvent.java index 4c1771378f4..c2cf8e1a3b1 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/BusEvent.java +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/BusEvent.java @@ -200,8 +200,7 @@ public static Map storeStates(Item... items) { Map statesMap = new HashMap<>(); if (items != null) { for (Item item : items) { - if (item instanceof GroupItem) { - GroupItem groupItem = (GroupItem) item; + if (item instanceof GroupItem groupItem) { for (Item member : groupItem.getAllMembers()) { statesMap.put(member, member.getState()); } diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/engine/ScriptParsingException.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/engine/ScriptParsingException.java index 88e07d16732..758f77927c3 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/engine/ScriptParsingException.java +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/engine/ScriptParsingException.java @@ -38,11 +38,9 @@ public ScriptParsingException(String message, String scriptAsString, Throwable t public ScriptParsingException addDiagnosticErrors(List errors) { for (Diagnostic emfDiagnosticError : errors) { - if (emfDiagnosticError instanceof AbstractDiagnostic) { - AbstractDiagnostic e = (AbstractDiagnostic) emfDiagnosticError; + if (emfDiagnosticError instanceof AbstractDiagnostic e) { this.getErrors().add(new ScriptError(e.getMessage(), e.getLine(), e.getOffset(), e.getLength())); - } else if (emfDiagnosticError instanceof ExceptionDiagnostic) { - ExceptionDiagnostic e = (ExceptionDiagnostic) emfDiagnosticError; + } else if (emfDiagnosticError instanceof ExceptionDiagnostic e) { this.getErrors().add(new ScriptError(e.getMessage(), e.getLine(), e.getOffset(), e.getLength())); } else { this.getErrors().add(new ScriptError(emfDiagnosticError.getMessage(), -1, -1, -1)); diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/NumberExtensions.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/NumberExtensions.java index 8dd94aff595..4b05df4e84b 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/NumberExtensions.java +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/NumberExtensions.java @@ -175,22 +175,22 @@ public static boolean operator_greaterEqualsThan(Number left, Number right) { // Comparison operators between types and numbers public static boolean operator_equals(Type type, Number x) { - if (type instanceof QuantityType && x instanceof QuantityType) { - return operator_equals((QuantityType) type, (QuantityType) x); + if (type instanceof QuantityType qtype && x instanceof QuantityType qx) { + return operator_equals(qtype, qx); } - if (type != null && type instanceof DecimalType && x != null) { - return ((DecimalType) type).toBigDecimal().compareTo(numberToBigDecimal(x)) == 0; + if (type != null && type instanceof DecimalType decimalType && x != null) { + return decimalType.toBigDecimal().compareTo(numberToBigDecimal(x)) == 0; } else { return type == x; // both might be null, then we should return true } } public static boolean operator_notEquals(Type type, Number x) { - if (type instanceof QuantityType && x instanceof QuantityType) { - return operator_notEquals((QuantityType) type, (QuantityType) x); + if (type instanceof QuantityType qtype && x instanceof QuantityType qx) { + return operator_notEquals(qtype, qx); } - if (type != null && type instanceof DecimalType && x != null) { - return ((DecimalType) type).toBigDecimal().compareTo(numberToBigDecimal(x)) != 0; + if (type != null && type instanceof DecimalType decimalType && x != null) { + return decimalType.toBigDecimal().compareTo(numberToBigDecimal(x)) != 0; } else { return type != x; // both might be null, then we should return // false, otherwise true @@ -198,44 +198,44 @@ public static boolean operator_notEquals(Type type, Number x) { } public static boolean operator_greaterThan(Type type, Number x) { - if (type instanceof QuantityType && x instanceof QuantityType) { - return operator_greaterThan((QuantityType) type, (QuantityType) x); + if (type instanceof QuantityType qtype && x instanceof QuantityType qx) { + return operator_greaterThan(qtype, qx); } - if (type != null && type instanceof DecimalType && x != null) { - return ((DecimalType) type).toBigDecimal().compareTo(numberToBigDecimal(x)) > 0; + if (type != null && type instanceof DecimalType decimalType && x != null) { + return decimalType.toBigDecimal().compareTo(numberToBigDecimal(x)) > 0; } else { return false; } } public static boolean operator_greaterEqualsThan(Type type, Number x) { - if (type instanceof QuantityType && x instanceof QuantityType) { - return operator_greaterEqualsThan((QuantityType) type, (QuantityType) x); + if (type instanceof QuantityType qtype && x instanceof QuantityType qx) { + return operator_greaterEqualsThan(qtype, qx); } - if (type != null && type instanceof DecimalType && x != null) { - return ((DecimalType) type).toBigDecimal().compareTo(numberToBigDecimal(x)) >= 0; + if (type != null && type instanceof DecimalType decimalType && x != null) { + return decimalType.toBigDecimal().compareTo(numberToBigDecimal(x)) >= 0; } else { return false; } } public static boolean operator_lessThan(Type type, Number x) { - if (type instanceof QuantityType && x instanceof QuantityType) { - return operator_lessThan((QuantityType) type, (QuantityType) x); + if (type instanceof QuantityType qtype && x instanceof QuantityType qx) { + return operator_lessThan(qtype, qx); } - if (type != null && type instanceof DecimalType && x != null) { - return ((DecimalType) type).toBigDecimal().compareTo(numberToBigDecimal(x)) < 0; + if (type != null && type instanceof DecimalType decimalType && x != null) { + return decimalType.toBigDecimal().compareTo(numberToBigDecimal(x)) < 0; } else { return false; } } public static boolean operator_lessEqualsThan(Type type, Number x) { - if (type instanceof QuantityType && x instanceof QuantityType) { - return operator_lessEqualsThan((QuantityType) type, (QuantityType) x); + if (type instanceof QuantityType qtype && x instanceof QuantityType qx) { + return operator_lessEqualsThan(qtype, qx); } - if (type != null && type instanceof DecimalType && x != null) { - return ((DecimalType) type).toBigDecimal().compareTo(numberToBigDecimal(x)) <= 0; + if (type != null && type instanceof DecimalType decimalType && x != null) { + return decimalType.toBigDecimal().compareTo(numberToBigDecimal(x)) <= 0; } else { return false; } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ManagedThingProvider.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ManagedThingProvider.java index cf953fbace3..cab11cad2a8 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ManagedThingProvider.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ManagedThingProvider.java @@ -63,6 +63,7 @@ protected String keyToString(ThingUID key) { return null; } + @Override protected ThingStorageEntity toPersistableElement(Thing element) { return new ThingStorageEntity(ThingDTOMapper.map(element), element instanceof BridgeImpl); } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/xml/internal/ThingDescriptionList.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/xml/internal/ThingDescriptionList.java index 8f96fe215ea..7827dc5df92 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/xml/internal/ThingDescriptionList.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/xml/internal/ThingDescriptionList.java @@ -27,6 +27,8 @@ @NonNullByDefault public class ThingDescriptionList extends ArrayList { + private static final long serialVersionUID = -1579556977347296301L; + @SuppressWarnings("unchecked") public ThingDescriptionList(Collection list) { super(list); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/service/StartLevelService.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/service/StartLevelService.java index 6eebbf9aa46..2ad325632d2 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/service/StartLevelService.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/service/StartLevelService.java @@ -117,9 +117,7 @@ protected void activate(Map configuration) { if (openHABStartLevel >= 10) { for (Integer level : new TreeSet<>(startlevels.keySet())) { - if (openHABStartLevel >= level) { - continue; - } else { + if (openHABStartLevel < level) { boolean reached = isStartLevelReached(startlevels.get(level)); if (reached) { setStartLevel(level); From 5e0a4ff25acadf0eceb0509dd8eae46218882264 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Mon, 1 May 2023 09:08:26 +0200 Subject: [PATCH 057/126] Reduce SAT warnings (#3586) Signed-off-by: Holger Friedrich --- .../rest/core/internal/profile/ProfileTypeResource.java | 2 -- .../core/io/rest/core/internal/tag/TagResource.java | 5 ----- .../openhab/core/io/rest/sse/internal/util/SseUtil.java | 1 - .../persistence/extensions/PersistenceExtensions.java | 2 -- .../openhab/core/thing/internal/AutoUpdateManager.java | 1 - .../openhab/core/internal/events/OSGiEventPublisher.java | 1 - .../openhab/core/internal/service/WatchServiceImpl.java | 1 - .../java/org/openhab/core/library/types/HSBTypeTest.java | 8 -------- 8 files changed, 21 deletions(-) diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/profile/ProfileTypeResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/profile/ProfileTypeResource.java index 408c600ae18..3e202346890 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/profile/ProfileTypeResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/profile/ProfileTypeResource.java @@ -153,7 +153,6 @@ private boolean profileTypeMatchesItemType(ProfileType pt, String itemType) { private boolean triggerProfileMatchesProfileType(ProfileType profileType, ChannelType channelType) { if (profileType instanceof TriggerProfileType triggerProfileType) { - if (triggerProfileType.getSupportedChannelTypeUIDs().isEmpty()) { return true; } @@ -167,7 +166,6 @@ private boolean triggerProfileMatchesProfileType(ProfileType profileType, Channe private boolean stateProfileMatchesProfileType(ProfileType profileType, ChannelType channelType) { if (profileType instanceof StateProfileType stateProfileType) { - if (stateProfileType.getSupportedItemTypesOfChannel().isEmpty()) { return true; } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagResource.java index ef0b6f95d66..be55ca2d26c 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagResource.java @@ -47,8 +47,6 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -75,8 +73,6 @@ public class TagResource implements RESTResource { /** The URI path to this resource */ public static final String PATH_TAGS = "tags"; - private final Logger logger = LoggerFactory.getLogger(TagResource.class); - private final LocaleService localeService; @Activate @@ -91,7 +87,6 @@ public TagResource(final @Reference LocaleService localeService) { @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = TagDTO.class)))) }) public Response getTags(final @Context UriInfo uriInfo, final @Context HttpHeaders httpHeaders, @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) { - final Locale locale = localeService.getLocale(language); Map> tags = Map.of( // diff --git a/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/util/SseUtil.java b/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/util/SseUtil.java index bbe05c7c59c..9dd6af379aa 100644 --- a/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/util/SseUtil.java +++ b/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/util/SseUtil.java @@ -51,7 +51,6 @@ public static EventDTO buildDTO(final Event event) { * @return a new OutboundEvent */ public static OutboundSseEvent buildEvent(OutboundSseEvent.Builder eventBuilder, EventDTO event) { - return eventBuilder.name("message") // .mediaType(MediaType.APPLICATION_JSON_TYPE) // .data(event) // diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java index 9bd3801e736..4b2bd7d7386 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java @@ -52,8 +52,6 @@ @Component(immediate = true) public class PersistenceExtensions { - private static final BigDecimal BIG_DECIMAL_TWO = BigDecimal.valueOf(2); - private static PersistenceServiceRegistry registry; @Activate diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/AutoUpdateManager.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/AutoUpdateManager.java index ec9cb4912a5..e7abeef6198 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/AutoUpdateManager.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/AutoUpdateManager.java @@ -142,7 +142,6 @@ public void receiveCommand(ItemCommandEvent commandEvent, Item item) { final String itemName = commandEvent.getItemName(); final Command command = commandEvent.getItemCommand(); if (command instanceof State state) { - Recommendation autoUpdate = shouldAutoUpdate(item); // consider user-override via item meta-data diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/events/OSGiEventPublisher.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/events/OSGiEventPublisher.java index d3cb35dc164..aebbabbe9a0 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/events/OSGiEventPublisher.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/events/OSGiEventPublisher.java @@ -60,7 +60,6 @@ private void postAsOSGiEvent(final EventAdmin eventAdmin, final Event event) thr properties.put("source", source); } eventAdmin.postEvent(new org.osgi.service.event.Event("openhab", properties)); - } catch (Exception e) { throw new IllegalStateException("Cannot post the event via the event bus. Error message: " + e.getMessage(), e); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/WatchServiceImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/WatchServiceImpl.java index d3faf761fc6..a90de4c0eb7 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/WatchServiceImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/WatchServiceImpl.java @@ -231,7 +231,6 @@ public void onEvent(@Nullable DirectoryChangeEvent directoryChangeEvent) throws future = scheduler.schedule(() -> notifyListeners(path), PROCESSING_TIME, TimeUnit.MILLISECONDS); scheduledEventKinds.computeIfAbsent(path, k -> new CopyOnWriteArrayList<>()).add(directoryChangeEvent); scheduledEvents.put(path, future); - } } diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/HSBTypeTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/HSBTypeTest.java index 43ce328e0ae..b10dbf4f24a 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/HSBTypeTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/HSBTypeTest.java @@ -16,9 +16,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.*; -import java.math.BigDecimal; -import java.math.RoundingMode; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; @@ -63,11 +60,6 @@ public void testHsbToRgbConversion() { compareHsbToRgbValues("300,100,40", 102, 0, 102); } - private int convertPercentToByte(PercentType percent) { - return percent.value.multiply(BigDecimal.valueOf(255)).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP) - .intValue(); - } - private void compareHsbToRgbValues(String hsbValues, int red, int green, int blue) { HSBType hsb = new HSBType(hsbValues); HSBType hsbRgb = HSBType.fromRGB(red, green, blue); From cdab53b814c08380bba52a3f5f31543117be52cb Mon Sep 17 00:00:00 2001 From: J-N-K Date: Mon, 1 May 2023 15:04:59 +0200 Subject: [PATCH 058/126] Change default imperial unit for length to inch (#3587) Signed-off-by: Jan N. Klug --- .../java/org/openhab/core/internal/i18n/I18nProviderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java index 80151468a3d..0e2ce22fa2b 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java @@ -404,7 +404,7 @@ private void initDimensionMap() { addDefaultUnit(Frequency.class, Units.HERTZ); addDefaultUnit(Illuminance.class, Units.LUX); addDefaultUnit(Intensity.class, Units.IRRADIANCE); - addDefaultUnit(Length.class, SIUnits.METRE, ImperialUnits.FOOT); + addDefaultUnit(Length.class, SIUnits.METRE, ImperialUnits.INCH); addDefaultUnit(LuminousFlux.class, Units.LUMEN); addDefaultUnit(LuminousIntensity.class, Units.CANDELA); addDefaultUnit(MagneticFlux.class, Units.WEBER); From c40dd73d777ad088da31c7948d45d02df69de003 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Mon, 1 May 2023 21:58:11 +0200 Subject: [PATCH 059/126] Initial contribution of an CLI upgrade-tool (#3268) * Initial contribution of an upgrade-tool Signed-off-by: Jan N. Klug --- tools/pom.xml | 1 + tools/upgradetool/pom.xml | 123 +++++++++++++ .../org/openhab/core/tools/UpgradeTool.java | 84 +++++++++ .../openhab/core/tools/internal/Upgrader.java | 171 ++++++++++++++++++ 4 files changed, 379 insertions(+) create mode 100644 tools/upgradetool/pom.xml create mode 100644 tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java create mode 100644 tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java diff --git a/tools/pom.xml b/tools/pom.xml index 14eab0fefb6..d427db4c478 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -19,6 +19,7 @@ archetype i18n-plugin + upgradetool diff --git a/tools/upgradetool/pom.xml b/tools/upgradetool/pom.xml new file mode 100644 index 00000000000..874ed8d18b6 --- /dev/null +++ b/tools/upgradetool/pom.xml @@ -0,0 +1,123 @@ + + + + 4.0.0 + + + org.openhab.core.tools + org.openhab.core.reactor.tools + 4.0.0-SNAPSHOT + + + upgradetool + + jar + + openHAB Core :: Tools :: Upgrade tool + A tool for upgrading openHAB from 3.4 to 4.0 + + + + org.openhab.core.bundles + org.openhab.core + ${project.version} + + + org.openhab.core.bundles + org.openhab.core.thing + ${project.version} + + + org.openhab.core.bundles + org.openhab.core.storage.json + ${project.version} + + + commons-cli + commons-cli + 1.5.0 + + + org.slf4j + slf4j-simple + ${slf4j.version} + + + com.google.code.gson + gson + 2.9.1 + + + javax.measure + unit-api + 2.1.3 + + + si.uom + si-units + 2.1 + + + tech.units + indriya + 2.1.2 + + + org.eclipse.jdt + org.eclipse.jdt.annotation + 2.2.600 + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.3.0 + + + unpack-eea + + unpack + + + + + org.lastnpe.eea + eea-all + ${eea.version} + true + + + + + + + + maven-assembly-plugin + 3.4.2 + + + + org.openhab.core.tools.UpgradeTool + + + + jar-with-dependencies + + + + + make-assembly + + single + + package + + + + + + diff --git a/tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java b/tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java new file mode 100644 index 00000000000..19f009cd0df --- /dev/null +++ b/tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.tools; + +import static org.openhab.core.tools.internal.Upgrader.*; + +import java.util.Set; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.tools.internal.Upgrader; + +/** + * The {@link UpgradeTool} is a tool for upgrading openHAB to mitigate breaking changes + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class UpgradeTool { + private static final Set LOG_LEVELS = Set.of("TRACE", "DEBUG", "INFO", "WARN", "ERROR"); + private static final String OPT_COMMAND = "command"; + private static final String OPT_DIR = "dir"; + private static final String OPT_LOG = "log"; + private static final String OPT_FORCE = "force"; + + private static Options getOptions() { + Options options = new Options(); + + options.addOption( + Option.builder().longOpt(OPT_DIR).desc("directory to process").numberOfArgs(1).required().build()); + options.addOption(Option.builder().longOpt(OPT_COMMAND).numberOfArgs(1).desc("command to execute").build()); + options.addOption(Option.builder().longOpt(OPT_LOG).numberOfArgs(1).desc("log verbosity").build()); + options.addOption(Option.builder().longOpt(OPT_FORCE).desc("force execution (even if already done)").build()); + + return options; + } + + public static void main(String[] args) { + Options options = getOptions(); + try { + CommandLine commandLine = new DefaultParser().parse(options, args); + + String loglevel = commandLine.hasOption(OPT_LOG) ? commandLine.getOptionValue(OPT_LOG).toUpperCase() + : "INFO"; + if (!LOG_LEVELS.contains(loglevel)) { + System.out.println("Allowed log-levels are " + LOG_LEVELS); + System.exit(0); + } + + System.setProperty(org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY, loglevel); + + String baseDir = commandLine.hasOption(OPT_DIR) ? commandLine.getOptionValue(OPT_DIR) : ""; + boolean force = commandLine.hasOption(OPT_FORCE) ? true : false; + + Upgrader upgrader = new Upgrader(baseDir, force); + if (commandLine.hasOption(ITEM_COPY_UNIT_TO_METADATA)) { + upgrader.itemCopyUnitToMetadata(); + } else if (commandLine.hasOption(LINK_UPGRADE_JS_PROFILE)) { + upgrader.linkUpgradeJsProfile(); + } + } catch (ParseException e) { + HelpFormatter formatter = new HelpFormatter(); + String commands = Set.of(ITEM_COPY_UNIT_TO_METADATA, LINK_UPGRADE_JS_PROFILE).toString(); + formatter.printHelp("upgradetool", "", options, "Available commands: " + commands, true); + } + + System.exit(0); + } +} diff --git a/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java b/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java new file mode 100644 index 00000000000..0c896ad248b --- /dev/null +++ b/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.tools.internal; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Objects; + +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.items.ManagedItemProvider; +import org.openhab.core.items.Metadata; +import org.openhab.core.items.MetadataKey; +import org.openhab.core.storage.json.internal.JsonStorage; +import org.openhab.core.thing.internal.link.ItemChannelLinkConfigDescriptionProvider; +import org.openhab.core.thing.link.ItemChannelLink; +import org.openhab.core.types.util.UnitUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link Upgrader} contains the implementation of the upgrade methods + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class Upgrader { + public static final String ITEM_COPY_UNIT_TO_METADATA = "itemCopyUnitToMetadata"; + public static final String LINK_UPGRADE_JS_PROFILE = "linkUpgradeJsProfile"; + + private final Logger logger = LoggerFactory.getLogger(Upgrader.class); + private final String baseDir; + private final boolean force; + private final JsonStorage upgradeRecords; + + public Upgrader(String baseDir, boolean force) { + this.baseDir = baseDir; + this.force = force; + + Path upgradeJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.tools.UpgradeTool"); + upgradeRecords = new JsonStorage<>(upgradeJsonDatabasePath.toFile(), null, 5, 0, 0, List.of()); + } + + private boolean checkUpgradeRecord(String key) { + UpgradeRecord upgradeRecord = upgradeRecords.get(key); + if (upgradeRecord != null && !force) { + logger.info("Already executed '{}' on {}. Use '--force' to execute it again.", key, + upgradeRecord.executionDate); + return false; + } + return true; + } + + public void itemCopyUnitToMetadata() { + if (checkUpgradeRecord(ITEM_COPY_UNIT_TO_METADATA)) { + return; + } + Path itemJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.items.Item.json"); + Path metadataJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.items.Metadata.json"); + logger.info("Copying item unit from state description to metadata in database '{}'", itemJsonDatabasePath); + + if (!Files.isReadable(itemJsonDatabasePath)) { + logger.error("Cannot access item database '{}', check path and access rights.", itemJsonDatabasePath); + return; + } + if (!Files.isWritable(metadataJsonDatabasePath)) { + logger.error("Cannot access metadata database '{}', check path and access rights.", + metadataJsonDatabasePath); + return; + } + + JsonStorage itemStorage = new JsonStorage<>(itemJsonDatabasePath.toFile(), + null, 5, 0, 0, List.of()); + JsonStorage metadataStorage = new JsonStorage<>(metadataJsonDatabasePath.toFile(), null, 5, 0, 0, + List.of()); + + itemStorage.getKeys().forEach(itemName -> { + ManagedItemProvider.PersistedItem item = itemStorage.get(itemName); + if (item != null && item.itemType.startsWith("Number:")) { + if (metadataStorage.containsKey("unit" + ":" + itemName)) { + logger.debug("{}: already contains a 'unit' metadata, skipping it", itemName); + } else { + Metadata metadata = metadataStorage.get("stateDescription:" + itemName); + if (metadata == null) { + logger.debug("{}: Nothing to do, no state description found.", itemName); + } else { + String pattern = (String) metadata.getConfiguration().get("pattern"); + if (pattern.contains(UnitUtils.UNIT_PLACEHOLDER)) { + logger.warn( + "{}: State description contains unit place-holder '%unit%', check if 'unit' metadata is needed!", + itemName); + } else { + Unit stateDescriptionUnit = UnitUtils.parseUnit(pattern); + if (stateDescriptionUnit != null) { + String unit = stateDescriptionUnit.toString(); + MetadataKey defaultUnitMetadataKey = new MetadataKey("unit", itemName); + Metadata defaultUnitMetadata = new Metadata(defaultUnitMetadataKey, unit, null); + metadataStorage.put(defaultUnitMetadataKey.toString(), defaultUnitMetadata); + logger.info("{}: Wrote 'unit={}' to metadata.", itemName, unit); + } + } + } + } + } + }); + + metadataStorage.flush(); + upgradeRecords.put(ITEM_COPY_UNIT_TO_METADATA, new UpgradeRecord(ZonedDateTime.now())); + } + + public void linkUpgradeJsProfile() { + if (checkUpgradeRecord(LINK_UPGRADE_JS_PROFILE)) { + return; + } + + Path linkJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.thing.link.ItemChannelLink.json"); + logger.info("Upgrading JS profile configuration in database '{}'", linkJsonDatabasePath); + + if (!Files.isWritable(linkJsonDatabasePath)) { + logger.error("Cannot access link database '{}', check path and access rights.", linkJsonDatabasePath); + return; + } + JsonStorage linkStorage = new JsonStorage<>(linkJsonDatabasePath.toFile(), null, 5, 0, 0, + List.of()); + + List.copyOf(linkStorage.getKeys()).forEach(linkUid -> { + ItemChannelLink link = Objects.requireNonNull(linkStorage.get(linkUid)); + Configuration configuration = link.getConfiguration(); + String profileName = (String) configuration.get(ItemChannelLinkConfigDescriptionProvider.PARAM_PROFILE); + if ("transform:JS".equals(profileName)) { + String function = (String) configuration.get("function"); + if (function != null) { + configuration.put("toItemScript", function); + configuration.put("toHandlerScript", "|input"); + configuration.remove("function"); + configuration.remove("sourceFormat"); + + linkStorage.put(linkUid, link); + logger.info("{}: rewrote JS profile link to new format", linkUid); + } else { + logger.info("{}: link already has correct configuration", linkUid); + } + } + }); + + linkStorage.flush(); + upgradeRecords.put(LINK_UPGRADE_JS_PROFILE, new UpgradeRecord(ZonedDateTime.now())); + } + + private static class UpgradeRecord { + public final ZonedDateTime executionDate; + + public UpgradeRecord(ZonedDateTime executionDate) { + this.executionDate = executionDate; + } + } +} From 476975a012cb374c6ef4a11cdb4ba603c587676c Mon Sep 17 00:00:00 2001 From: Kai Kreuzer Date: Tue, 2 May 2023 09:10:34 +0200 Subject: [PATCH 060/126] Allow OnOffType as valid conversion types for HSBType and PercentType (#3588) * Allow OnOffType as valid conversion types for HSBType and PercentType * Add clause for supported PercentType Signed-off-by: Kai Kreuzer --- .../openhab/core/thing/internal/CommunicationManager.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java index 6bf05ad282b..85e05e0f257 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java @@ -46,6 +46,8 @@ import org.openhab.core.items.events.ItemStateUpdatedEvent; import org.openhab.core.library.items.NumberItem; import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; @@ -467,6 +469,12 @@ && channelTypeDefinesDimension(channel.getChannelTypeUID())) { if (acceptedTypes.contains(originalType.getClass())) { return originalType; + } else if (acceptedTypes.contains(PercentType.class) && originalType instanceof State state + && PercentType.class.isAssignableFrom(originalType.getClass())) { + return (@Nullable T) state.as(PercentType.class); + } else if (acceptedTypes.contains(OnOffType.class) && originalType instanceof State state + && PercentType.class.isAssignableFrom(originalType.getClass())) { + return (@Nullable T) state.as(OnOffType.class); } else { // Look for class hierarchy and convert appropriately for (Class typeClass : acceptedTypes) { From c8464870bbbe2bce916dc7cae37b350d01834f27 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Tue, 2 May 2023 09:11:59 +0200 Subject: [PATCH 061/126] [ephemeris] Add support for overriding holiday definitions (#3573) * Add support for overriding holiday definitions End Danish General Prayer Day Signed-off-by: Jacob Laursen --- .../internal/EphemerisManagerImpl.java | 55 ++++++++++++------- .../jollyday/holidays/Holidays_dk.xml | 17 ++++++ .../EphemerisManagerImplOSGiTest.java | 15 +++++ 3 files changed, 68 insertions(+), 19 deletions(-) create mode 100644 bundles/org.openhab.core.ephemeris/src/main/resources/jollyday/holidays/Holidays_dk.xml diff --git a/bundles/org.openhab.core.ephemeris/src/main/java/org/openhab/core/ephemeris/internal/EphemerisManagerImpl.java b/bundles/org.openhab.core.ephemeris/src/main/java/org/openhab/core/ephemeris/internal/EphemerisManagerImpl.java index ab27188f319..65b9b8bf9d0 100644 --- a/bundles/org.openhab.core.ephemeris/src/main/java/org/openhab/core/ephemeris/internal/EphemerisManagerImpl.java +++ b/bundles/org.openhab.core.ephemeris/src/main/java/org/openhab/core/ephemeris/internal/EphemerisManagerImpl.java @@ -46,8 +46,8 @@ import org.openhab.core.ephemeris.EphemerisManager; import org.openhab.core.i18n.LocaleProvider; import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; -import org.osgi.framework.FrameworkUtil; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Modified; @@ -59,6 +59,7 @@ import de.jollyday.HolidayManager; import de.jollyday.ManagerParameter; import de.jollyday.ManagerParameters; +import de.jollyday.parameter.CalendarPartManagerParameter; import de.jollyday.util.ResourceUtil; /** @@ -82,7 +83,9 @@ public class EphemerisManagerImpl implements EphemerisManager, ConfigOptionProvi public static final String CONFIG_REGION = "region"; public static final String CONFIG_CITY = "city"; - private static final String JOLLYDAY_COUNTRY_DESCRIPTIONS = "jollyday/descriptions/country_descriptions.properties"; + private static final String RESOURCES_ROOT = "jollyday/"; + private static final String JOLLYDAY_COUNTRY_DESCRIPTIONS = RESOURCES_ROOT + + "descriptions/country_descriptions.properties"; private static final String PROPERTY_COUNTRY_DESCRIPTION_PREFIX = "country.description."; private static final String PROPERTY_COUNTRY_DESCRIPTION_DELIMITER = "\\."; @@ -99,15 +102,16 @@ public class EphemerisManagerImpl implements EphemerisManager, ConfigOptionProvi private final ResourceUtil resourceUtil = new ResourceUtil(); private final LocaleProvider localeProvider; + private final Bundle bundle; private @NonNullByDefault({}) String country; private @Nullable String region; @Activate - public EphemerisManagerImpl(final @Reference LocaleProvider localeProvider) { + public EphemerisManagerImpl(final @Reference LocaleProvider localeProvider, final BundleContext bundleContext) { this.localeProvider = localeProvider; + bundle = bundleContext.getBundle(); - final Bundle bundle = FrameworkUtil.getBundle(getClass()); try (InputStream stream = bundle.getResource(JOLLYDAY_COUNTRY_DESCRIPTIONS).openStream()) { final Properties properties = new Properties(); properties.load(stream); @@ -164,23 +168,26 @@ protected void modified(Map config) { } }); - if (config.containsKey(CONFIG_COUNTRY)) { - country = config.get(CONFIG_COUNTRY).toString().toLowerCase(); + Object configValue = config.get(CONFIG_COUNTRY); + if (configValue != null) { + country = configValue.toString().toLowerCase(); } else { country = localeProvider.getLocale().getCountry().toLowerCase(); logger.debug("Using system default country '{}' ", country); } - if (config.containsKey(CONFIG_REGION)) { - String region = config.get(CONFIG_REGION).toString().toLowerCase(); + configValue = config.get(CONFIG_REGION); + if (configValue != null) { + String region = configValue.toString().toLowerCase(); countryParameters.add(region); this.region = region; } else { this.region = null; } - if (config.containsKey(CONFIG_CITY)) { - countryParameters.add(config.get(CONFIG_CITY).toString()); + configValue = config.get(CONFIG_CITY); + if (configValue != null) { + countryParameters.add(configValue.toString()); } } @@ -231,9 +238,16 @@ private URL getUrl(String filename) throws FileNotFoundException { private HolidayManager getHolidayManager(Object managerKey) { HolidayManager holidayManager = holidayManagers.get(managerKey); if (holidayManager == null) { - final ManagerParameter parameters = managerKey.getClass() == String.class - ? ManagerParameters.create((String) managerKey) - : ManagerParameters.create((URL) managerKey); + final ManagerParameter parameters; + if (managerKey instanceof String stringKey) { + URL urlOverride = bundle + .getResource(RESOURCES_ROOT + CalendarPartManagerParameter.getConfigurationFileName(stringKey)); + parameters = urlOverride != null // + ? ManagerParameters.create(urlOverride) + : ManagerParameters.create(stringKey); + } else { + parameters = ManagerParameters.create((URL) managerKey); + } holidayManager = HolidayManager.getInstance(parameters); holidayManagers.put(managerKey, holidayManager); } @@ -297,9 +311,10 @@ public boolean isWeekend(ZonedDateTime date) { @Override public boolean isInDayset(String daysetName, ZonedDateTime date) { - if (daysets.containsKey(daysetName)) { + Set dayset = daysets.get(daysetName); + if (dayset != null) { DayOfWeek dow = date.getDayOfWeek(); - return daysets.get(daysetName).contains(dow); + return dayset.contains(dow); } else { logger.warn("This dayset is not configured : {}", daysetName); return false; @@ -381,8 +396,9 @@ void parseProperty(Object key, Object value) throws IllegalArgumentException { case 2: part = getValidPart(parts[0]); option = new ParameterOption(getValidPart(parts[1]), name); - if (regions.containsKey(part)) { - regions.get(part).add(option); + List regionsPart = regions.get(part); + if (regionsPart != null) { + regionsPart.add(option); } else { final List options = new ArrayList<>(); options.add(option); @@ -392,8 +408,9 @@ void parseProperty(Object key, Object value) throws IllegalArgumentException { case 3: part = getValidPart(parts[1]); option = new ParameterOption(getValidPart(parts[2]), name); - if (cities.containsKey(part)) { - cities.get(part).add(option); + List citiesPart = cities.get(part); + if (citiesPart != null) { + citiesPart.add(option); } else { final List options = new ArrayList<>(); options.add(option); diff --git a/bundles/org.openhab.core.ephemeris/src/main/resources/jollyday/holidays/Holidays_dk.xml b/bundles/org.openhab.core.ephemeris/src/main/resources/jollyday/holidays/Holidays_dk.xml new file mode 100644 index 00000000000..e7510def3c3 --- /dev/null +++ b/bundles/org.openhab.core.ephemeris/src/main/resources/jollyday/holidays/Holidays_dk.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + diff --git a/itests/org.openhab.core.ephemeris.tests/src/main/java/org/openhab/core/ephemeris/internal/EphemerisManagerImplOSGiTest.java b/itests/org.openhab.core.ephemeris.tests/src/main/java/org/openhab/core/ephemeris/internal/EphemerisManagerImplOSGiTest.java index 5f086250f77..5a95b81b8d0 100644 --- a/itests/org.openhab.core.ephemeris.tests/src/main/java/org/openhab/core/ephemeris/internal/EphemerisManagerImplOSGiTest.java +++ b/itests/org.openhab.core.ephemeris.tests/src/main/java/org/openhab/core/ephemeris/internal/EphemerisManagerImplOSGiTest.java @@ -47,6 +47,7 @@ public class EphemerisManagerImplOSGiTest extends JavaOSGiTest { private static final String INTERNAL_DAYSET = "internal"; private static final URI CONFIG_URI = URI.create("system:ephemeris"); + private static final String COUNTRY_DENMARK_KEY = "dk"; private static final String COUNTRY_AUSTRALIA_KEY = "au"; private static final String COUNTRY_AUSTRALIA_NAME = "Australia"; private static final String REGION_BAVARIA_KEY = "by"; @@ -263,6 +264,20 @@ public void testIsBankHoliday() { assertFalse(vacation); } + @Test + public void testIsBankHolidayWhenGeneralPrayerDay2023() { + ZonedDateTime generalPrayerDay = ZonedDateTime.of(2023, 05, 05, 0, 0, 0, 0, ZoneId.of("Europe/Paris")); + ephemerisManager.modified(Map.of(CONFIG_COUNTRY, COUNTRY_DENMARK_KEY)); + assertTrue(ephemerisManager.isBankHoliday(generalPrayerDay)); + } + + @Test + public void testIsBankHolidayWhenGeneralPrayerDay2024() { + ZonedDateTime generalPrayerDay = ZonedDateTime.of(2024, 04, 26, 0, 0, 0, 0, ZoneId.of("Europe/Paris")); + ephemerisManager.modified(Map.of(CONFIG_COUNTRY, COUNTRY_DENMARK_KEY)); + assertFalse(ephemerisManager.isBankHoliday(generalPrayerDay)); + } + @Test public void testGetBankHoliday() { ZonedDateTime theDay = ZonedDateTime.of(2019, 01, 01, 0, 0, 0, 0, ZoneId.of("Europe/Paris")); From 9c5907103821350e5e95786a7f48d5e2398d614a Mon Sep 17 00:00:00 2001 From: Kai Kreuzer Date: Thu, 4 May 2023 09:08:26 +0200 Subject: [PATCH 062/126] Make --dir param optional for upgradetool (#3591) * Make --dir param optional for upgradetool Almost all openHAB installations should have the $OPENHAB_USERDATA env variable set. If this is the case, the user does not have to provide a directory, so this parameter can be made optional. Signed-off-by: Kai Kreuzer --- .../org/openhab/core/tools/UpgradeTool.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java b/tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java index 19f009cd0df..7df66d741e5 100644 --- a/tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java +++ b/tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java @@ -41,8 +41,7 @@ public class UpgradeTool { private static Options getOptions() { Options options = new Options(); - options.addOption( - Option.builder().longOpt(OPT_DIR).desc("directory to process").numberOfArgs(1).required().build()); + options.addOption(Option.builder().longOpt(OPT_DIR).desc("directory to process").numberOfArgs(1).build()); options.addOption(Option.builder().longOpt(OPT_COMMAND).numberOfArgs(1).desc("command to execute").build()); options.addOption(Option.builder().longOpt(OPT_LOG).numberOfArgs(1).desc("log verbosity").build()); options.addOption(Option.builder().longOpt(OPT_FORCE).desc("force execution (even if already done)").build()); @@ -64,14 +63,21 @@ public static void main(String[] args) { System.setProperty(org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY, loglevel); - String baseDir = commandLine.hasOption(OPT_DIR) ? commandLine.getOptionValue(OPT_DIR) : ""; - boolean force = commandLine.hasOption(OPT_FORCE) ? true : false; + String baseDir = commandLine.hasOption(OPT_DIR) ? commandLine.getOptionValue(OPT_DIR) + : System.getenv("OPENHAB_USERDATA"); + if (baseDir == null || baseDir.isBlank()) { + System.out.println( + "Please either set the environment variable ${OPENHAB_USERDATA} or provide a directory through the --dir option."); + System.exit(0); + } else { + boolean force = commandLine.hasOption(OPT_FORCE) ? true : false; - Upgrader upgrader = new Upgrader(baseDir, force); - if (commandLine.hasOption(ITEM_COPY_UNIT_TO_METADATA)) { - upgrader.itemCopyUnitToMetadata(); - } else if (commandLine.hasOption(LINK_UPGRADE_JS_PROFILE)) { - upgrader.linkUpgradeJsProfile(); + Upgrader upgrader = new Upgrader(baseDir, force); + if (commandLine.hasOption(ITEM_COPY_UNIT_TO_METADATA)) { + upgrader.itemCopyUnitToMetadata(); + } else if (commandLine.hasOption(LINK_UPGRADE_JS_PROFILE)) { + upgrader.linkUpgradeJsProfile(); + } } } catch (ParseException e) { HelpFormatter formatter = new HelpFormatter(); From d7ba8ad6366dc4f12bbf90077474b87c03df1fc7 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Fri, 5 May 2023 20:16:55 +0200 Subject: [PATCH 063/126] Improve message when rule action fails (#3593) Also logs the full stacktrace at DEBUG level Signed-off-by: Jan N. Klug --- .../org/openhab/core/automation/internal/RuleEngineImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java index 2fa454ae5a6..1585d0be863 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java @@ -1183,8 +1183,9 @@ private void executeActions(WrappedRule rule, boolean stopOnFirstFail) { updateContext(ruleUID, action.getId(), outputs); } } catch (Throwable t) { - String errMessage = "Fail to execute action: " + action.getId(); + String errMessage = "Failed to execute action: " + action.getId() + "(" + t.getMessage() + ")"; if (stopOnFirstFail) { + logger.debug("Action {}-{} threw an exception: ", ruleUID, action.getId(), t); throw new RuntimeException(errMessage, t); } else { logger.warn(errMessage, t); From 28ec419afd8a7ac1843a07d5fdc8cb5db94a6977 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Fri, 5 May 2023 20:25:33 +0200 Subject: [PATCH 064/126] Fix non-unique module handler identifiers (#3592) Signed-off-by: Jan N. Klug --- .../core/automation/handler/BaseModuleHandlerFactory.java | 8 ++++++-- .../internal/composite/CompositeModuleHandlerFactory.java | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/BaseModuleHandlerFactory.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/BaseModuleHandlerFactory.java index acd496dc71a..cde2d8a830b 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/BaseModuleHandlerFactory.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/BaseModuleHandlerFactory.java @@ -60,7 +60,7 @@ protected Map getHandlers() { @Override @SuppressWarnings("null") public @Nullable ModuleHandler getHandler(Module module, String ruleUID) { - String id = ruleUID + module.getId(); + String id = getModuleIdentifier(ruleUID, module.getId()); ModuleHandler handler = handlers.get(id); handler = handler == null ? internalCreate(module, ruleUID) : handler; if (handler != null) { @@ -80,8 +80,12 @@ protected Map getHandlers() { @Override public void ungetHandler(Module module, String ruleUID, ModuleHandler handler) { - if (handlers.remove(ruleUID + module.getId(), handler)) { + if (handlers.remove(getModuleIdentifier(ruleUID, module.getId()), handler)) { handler.dispose(); } } + + protected String getModuleIdentifier(String ruleUid, String moduleId) { + return ruleUid + "$" + moduleId; + } } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/composite/CompositeModuleHandlerFactory.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/composite/CompositeModuleHandlerFactory.java index 00c52653b67..1f9e7a97220 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/composite/CompositeModuleHandlerFactory.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/composite/CompositeModuleHandlerFactory.java @@ -92,7 +92,7 @@ public Collection getTypes() { @SuppressWarnings({ "unchecked" }) @Override public void ungetHandler(Module module, String childModulePrefix, ModuleHandler handler) { - ModuleHandler handlerOfModule = getHandlers().get(childModulePrefix + module.getId()); + ModuleHandler handlerOfModule = getHandlers().get(getModuleIdentifier(childModulePrefix, module.getId())); if (handlerOfModule instanceof AbstractCompositeModuleHandler) { AbstractCompositeModuleHandler h = (AbstractCompositeModuleHandler) handlerOfModule; Set modules = h.moduleHandlerMap.keySet(); From 2e00efc9bbcb9bd283589e76240e1787126bf036 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 7 May 2023 20:33:28 +0200 Subject: [PATCH 065/126] Allow managing persistence configurations and enable filters (#2871) * Allow managing persistence configurations Signed-off-by: Jan N. Klug --- .../persistence/PersistenceResource.java | 113 +++- .../persistence/PersistenceResourceTest.java | 5 + .../bnd.bnd | 2 + .../core/model/persistence/Persistence.xtext | 2 +- .../internal/PersistenceModelManager.java | 134 +++-- .../core/persistence/FilterCriteria.java | 19 +- .../core/persistence/HistoricItem.java | 4 +- .../ModifiablePersistenceService.java | 4 +- .../PersistenceItemConfiguration.java | 31 +- .../core/persistence/PersistenceManager.java | 39 -- .../QueryablePersistenceService.java | 4 +- .../core/persistence/dto/ItemHistoryDTO.java | 4 +- .../PersistenceCronStrategyDTO.java} | 12 +- .../persistence/dto/PersistenceFilterDTO.java | 27 + .../dto/PersistenceItemConfigurationDTO.java | 33 ++ .../PersistenceServiceConfigurationDTO.java | 35 ++ .../dto/PersistenceStrategyDTO.java | 36 ++ .../extensions/PersistenceExtensions.java | 9 +- .../persistence/filter/PersistenceFilter.java | 85 +++ .../filter/PersistenceThresholdFilter.java | 122 +++++ .../filter/PersistenceTimeFilter.java | 85 +++ .../persistence/internal/PersistItemsJob.java | 91 ---- .../internal/PersistenceManager.java | 465 +++++++++++++++++ .../internal/PersistenceManagerImpl.java | 489 ------------------ ...tenceServiceConfigurationRegistryImpl.java | 118 +++++ ...rsistenceServiceConfigurationProvider.java | 63 +++ .../PersistenceServiceConfiguration.java | 42 +- ...sistenceServiceConfigurationDTOMapper.java | 166 ++++++ ...rsistenceServiceConfigurationProvider.java | 25 + ...rsistenceServiceConfigurationRegistry.java | 32 ++ ...ceConfigurationRegistryChangeListener.java | 47 ++ .../strategy/PersistenceStrategy.java | 14 +- .../extensions/TestPersistenceService.java | 7 +- .../PersistenceThresholdFilterTest.java | 97 ++++ .../filter/PersistenceTimeFilterTest.java | 45 ++ .../internal/PersistenceManagerTest.java | 405 +++++++++++++++ 36 files changed, 2152 insertions(+), 759 deletions(-) delete mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceManager.java rename bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/{PersistenceFilter.java => dto/PersistenceCronStrategyDTO.java} (61%) create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceFilterDTO.java create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceItemConfigurationDTO.java create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceServiceConfigurationDTO.java create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceStrategyDTO.java create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceFilter.java create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceThresholdFilter.java create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceTimeFilter.java delete mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistItemsJob.java create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManager.java delete mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceServiceConfigurationRegistryImpl.java create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/ManagedPersistenceServiceConfigurationProvider.java rename bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/{ => registry}/PersistenceServiceConfiguration.java (51%) create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationDTOMapper.java create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationProvider.java create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationRegistry.java create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationRegistryChangeListener.java create mode 100644 bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceThresholdFilterTest.java create mode 100644 bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceTimeFilterTest.java create mode 100644 bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/internal/PersistenceManagerTest.java diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java index ef1945f8bbc..fe0f01a6af0 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java @@ -20,6 +20,7 @@ import java.util.Locale; import javax.annotation.security.RolesAllowed; +import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; @@ -33,6 +34,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -57,7 +59,12 @@ import org.openhab.core.persistence.PersistenceServiceRegistry; import org.openhab.core.persistence.QueryablePersistenceService; import org.openhab.core.persistence.dto.ItemHistoryDTO; +import org.openhab.core.persistence.dto.PersistenceServiceConfigurationDTO; import org.openhab.core.persistence.dto.PersistenceServiceDTO; +import org.openhab.core.persistence.registry.ManagedPersistenceServiceConfigurationProvider; +import org.openhab.core.persistence.registry.PersistenceServiceConfiguration; +import org.openhab.core.persistence.registry.PersistenceServiceConfigurationDTOMapper; +import org.openhab.core.persistence.registry.PersistenceServiceConfigurationRegistry; import org.openhab.core.types.State; import org.openhab.core.types.TypeParser; import org.osgi.service.component.annotations.Activate; @@ -114,6 +121,8 @@ public class PersistenceResource implements RESTResource { private final ItemRegistry itemRegistry; private final LocaleService localeService; private final PersistenceServiceRegistry persistenceServiceRegistry; + private final PersistenceServiceConfigurationRegistry persistenceServiceConfigurationRegistry; + private final ManagedPersistenceServiceConfigurationProvider managedPersistenceServiceConfigurationProvider; private final TimeZoneProvider timeZoneProvider; @Activate @@ -121,10 +130,14 @@ public PersistenceResource( // final @Reference ItemRegistry itemRegistry, // final @Reference LocaleService localeService, final @Reference PersistenceServiceRegistry persistenceServiceRegistry, + final @Reference PersistenceServiceConfigurationRegistry persistenceServiceConfigurationRegistry, + final @Reference ManagedPersistenceServiceConfigurationProvider managedPersistenceServiceConfigurationProvider, final @Reference TimeZoneProvider timeZoneProvider) { this.itemRegistry = itemRegistry; this.localeService = localeService; this.persistenceServiceRegistry = persistenceServiceRegistry; + this.persistenceServiceConfigurationRegistry = persistenceServiceConfigurationRegistry; + this.managedPersistenceServiceConfigurationProvider = managedPersistenceServiceConfigurationProvider; this.timeZoneProvider = timeZoneProvider; } @@ -142,6 +155,96 @@ public Response httpGetPersistenceServices(@Context HttpHeaders headers, return Response.ok(responseObject).build(); } + @GET + @RolesAllowed({ Role.ADMIN }) + @Produces({ MediaType.APPLICATION_JSON }) + @Path("{serviceId: [a-zA-Z0-9]+}") + @Operation(operationId = "getPersistenceServiceConfiguration", summary = "Gets a persistence service configuration.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = PersistenceServiceConfigurationDTO.class))), + @ApiResponse(responseCode = "404", description = "Service configuration not found.") }) + public Response httpGetPersistenceServiceConfiguration(@Context HttpHeaders headers, + @Parameter(description = "Id of the persistence service.") @PathParam("serviceId") String serviceId) { + PersistenceServiceConfiguration configuration = persistenceServiceConfigurationRegistry.get(serviceId); + + if (configuration != null) { + PersistenceServiceConfigurationDTO configurationDTO = PersistenceServiceConfigurationDTOMapper + .map(configuration); + configurationDTO.editable = managedPersistenceServiceConfigurationProvider.get(serviceId) != null; + return JSONResponse.createResponse(Status.OK, configurationDTO, null); + } else { + return Response.status(Status.NOT_FOUND).build(); + } + } + + @PUT + @RolesAllowed({ Role.ADMIN }) + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Path("{serviceId: [a-zA-Z0-9]+}") + @Operation(operationId = "putPersistenceServiceConfiguration", summary = "Sets a persistence service configuration.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = PersistenceServiceConfigurationDTO.class))), + @ApiResponse(responseCode = "201", description = "PersistenceServiceConfiguration created."), + @ApiResponse(responseCode = "400", description = "Payload invalid."), + @ApiResponse(responseCode = "405", description = "PersistenceServiceConfiguration not editable.") }) + public Response httpPutPersistenceServiceConfiguration(@Context UriInfo uriInfo, @Context HttpHeaders headers, + @Parameter(description = "Id of the persistence service.") @PathParam("serviceId") String serviceId, + @Parameter(description = "service configuration", required = true) @Nullable PersistenceServiceConfigurationDTO serviceConfigurationDTO) { + if (serviceConfigurationDTO == null) { + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "Payload must not be null."); + } + if (!serviceId.equals(serviceConfigurationDTO.serviceId)) { + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "serviceId in payload '" + + serviceConfigurationDTO.serviceId + "' differs from serviceId in URL '" + serviceId + "'"); + } + + PersistenceServiceConfiguration persistenceServiceConfiguration; + try { + persistenceServiceConfiguration = PersistenceServiceConfigurationDTOMapper.map(serviceConfigurationDTO); + } catch (IllegalArgumentException e) { + logger.warn("Received HTTP PUT request at '{}' with an invalid payload: '{}'.", uriInfo.getPath(), + e.getMessage()); + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, e.getMessage()); + } + + if (persistenceServiceConfigurationRegistry.get(serviceId) == null) { + managedPersistenceServiceConfigurationProvider.add(persistenceServiceConfiguration); + return JSONResponse.createResponse(Status.CREATED, serviceConfigurationDTO, null); + } else if (managedPersistenceServiceConfigurationProvider.get(serviceId) != null) { + // item already exists as a managed item, update it + managedPersistenceServiceConfigurationProvider.update(persistenceServiceConfiguration); + return JSONResponse.createResponse(Status.OK, serviceConfigurationDTO, null); + } else { + // Configuration exists but cannot be updated + logger.warn("Cannot update existing persistence service configuration '{}', because is not managed.", + serviceId); + return JSONResponse.createErrorResponse(Status.METHOD_NOT_ALLOWED, + "Cannot update non-managed persistence service configuration " + serviceId); + } + } + + @DELETE + @RolesAllowed({ Role.ADMIN }) + @Path("{serviceId: [a-zA-Z0-9]+}") + @Operation(operationId = "deletePersistenceServiceConfiguration", summary = "Deletes a persistence service configuration.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Persistence service configuration not found."), + @ApiResponse(responseCode = "405", description = "Persistence service configuration not editable.") }) + public Response httpDeletePersistenceServiceConfiguration(@Context UriInfo uriInfo, @Context HttpHeaders headers, + @Parameter(description = "Id of the persistence service.") @PathParam("serviceId") String serviceId) { + if (persistenceServiceConfigurationRegistry.get(serviceId) == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + if (managedPersistenceServiceConfigurationProvider.remove(serviceId) == null) { + return Response.status(Status.METHOD_NOT_ALLOWED).build(); + } else { + return Response.ok().build(); + } + } + @GET @RolesAllowed({ Role.ADMIN }) @Path("/items") @@ -238,7 +341,7 @@ private Response getItemHistoryDTO(@Nullable String serviceId, String itemName, protected @Nullable ItemHistoryDTO createDTO(@Nullable String serviceId, String itemName, @Nullable String timeBegin, @Nullable String timeEnd, int pageNumber, int pageLength, boolean boundary) { // If serviceId is null, then use the default service - PersistenceService service = null; + PersistenceService service; String effectiveServiceId = serviceId != null ? serviceId : persistenceServiceRegistry.getDefaultId(); service = persistenceServiceRegistry.get(effectiveServiceId); @@ -283,9 +386,9 @@ private Response getItemHistoryDTO(@Nullable String serviceId, String itemName, } Iterable result; - State state = null; + State state; - Long quantity = 0l; + long quantity = 0L; ItemHistoryDTO dto = new ItemHistoryDTO(); dto.name = itemName; @@ -363,7 +466,7 @@ private Response getItemHistoryDTO(@Nullable String serviceId, String itemName, /** * Gets a list of persistence services currently configured in the system * - * @return list of persistence services as {@link ServiceBean} + * @return list of persistence services */ private List getPersistenceServiceList(Locale locale) { List dtoList = new ArrayList<>(); @@ -389,7 +492,7 @@ private List getPersistenceServiceList(Locale locale) { private Response getServiceItemList(@Nullable String serviceId) { // If serviceId is null, then use the default service - PersistenceService service = null; + PersistenceService service; if (serviceId == null) { service = persistenceServiceRegistry.getDefault(); } else { diff --git a/bundles/org.openhab.core.io.rest.core/src/test/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResourceTest.java b/bundles/org.openhab.core.io.rest.core/src/test/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResourceTest.java index 6cf040ab7f5..7ad448a9ff2 100644 --- a/bundles/org.openhab.core.io.rest.core/src/test/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResourceTest.java +++ b/bundles/org.openhab.core.io.rest.core/src/test/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResourceTest.java @@ -39,6 +39,8 @@ import org.openhab.core.persistence.QueryablePersistenceService; import org.openhab.core.persistence.dto.ItemHistoryDTO; import org.openhab.core.persistence.dto.ItemHistoryDTO.HistoryDataBean; +import org.openhab.core.persistence.registry.ManagedPersistenceServiceConfigurationProvider; +import org.openhab.core.persistence.registry.PersistenceServiceConfigurationRegistry; import org.openhab.core.types.State; /** @@ -58,11 +60,14 @@ public class PersistenceResourceTest { private @Mock @NonNullByDefault({}) ItemRegistry itemRegistryMock; private @Mock @NonNullByDefault({}) LocaleService localeServiceMock; private @Mock @NonNullByDefault({}) PersistenceServiceRegistry persistenceServiceRegistryMock; + private @Mock @NonNullByDefault({}) PersistenceServiceConfigurationRegistry persistenceServiceConfigurationRegistryMock; + private @Mock @NonNullByDefault({}) ManagedPersistenceServiceConfigurationProvider managedPersistenceServiceConfigurationProviderMock; private @Mock @NonNullByDefault({}) TimeZoneProvider timeZoneProviderMock; @BeforeEach public void beforeEach() { pResource = new PersistenceResource(itemRegistryMock, localeServiceMock, persistenceServiceRegistryMock, + persistenceServiceConfigurationRegistryMock, managedPersistenceServiceConfigurationProviderMock, timeZoneProviderMock); int startValue = 2016; diff --git a/bundles/org.openhab.core.model.persistence/bnd.bnd b/bundles/org.openhab.core.model.persistence/bnd.bnd index caef2b8c1c8..c5067b3cc13 100644 --- a/bundles/org.openhab.core.model.persistence/bnd.bnd +++ b/bundles/org.openhab.core.model.persistence/bnd.bnd @@ -21,6 +21,8 @@ Import-Package: \ org.openhab.core.persistence,\ org.openhab.core.persistence.config,\ org.openhab.core.persistence.strategy,\ + org.openhab.core.persistence.filter,\ + org.openhab.core.persistence.registry,\ org.openhab.core.types,\ org.openhab.core.model.core,\ com.google.common.*;version="14",\ diff --git a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/Persistence.xtext b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/Persistence.xtext index 9bdb11e1137..4e34511c2e1 100644 --- a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/Persistence.xtext +++ b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/Persistence.xtext @@ -31,7 +31,7 @@ FilterDetails: ; ThresholdFilter: - '>' value=DECIMAL percent?='%' + '>' value=DECIMAL unit=STRING ; TimeFilter: diff --git a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/internal/PersistenceModelManager.java b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/internal/PersistenceModelManager.java index 626da1d702c..3531eb4976c 100644 --- a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/internal/PersistenceModelManager.java +++ b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/internal/PersistenceModelManager.java @@ -12,10 +12,15 @@ */ package org.openhab.core.model.persistence.internal; +import java.util.Collection; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.eclipse.emf.ecore.EObject; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.common.registry.AbstractProvider; import org.openhab.core.model.core.EventType; import org.openhab.core.model.core.ModelRepository; import org.openhab.core.model.core.ModelRepositoryChangeListener; @@ -27,18 +32,24 @@ import org.openhab.core.model.persistence.persistence.PersistenceConfiguration; import org.openhab.core.model.persistence.persistence.PersistenceModel; import org.openhab.core.model.persistence.persistence.Strategy; -import org.openhab.core.persistence.PersistenceFilter; +import org.openhab.core.model.persistence.persistence.ThresholdFilter; +import org.openhab.core.model.persistence.persistence.TimeFilter; import org.openhab.core.persistence.PersistenceItemConfiguration; -import org.openhab.core.persistence.PersistenceManager; import org.openhab.core.persistence.PersistenceService; -import org.openhab.core.persistence.PersistenceServiceConfiguration; import org.openhab.core.persistence.config.PersistenceAllConfig; import org.openhab.core.persistence.config.PersistenceConfig; import org.openhab.core.persistence.config.PersistenceGroupConfig; import org.openhab.core.persistence.config.PersistenceItemConfig; +import org.openhab.core.persistence.filter.PersistenceFilter; +import org.openhab.core.persistence.filter.PersistenceThresholdFilter; +import org.openhab.core.persistence.filter.PersistenceTimeFilter; +import org.openhab.core.persistence.registry.PersistenceServiceConfiguration; +import org.openhab.core.persistence.registry.PersistenceServiceConfigurationProvider; import org.openhab.core.persistence.strategy.PersistenceCronStrategy; import org.openhab.core.persistence.strategy.PersistenceStrategy; +import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; /** @@ -47,75 +58,71 @@ * * @author Kai Kreuzer - Initial contribution * @author Markus Rathgeb - Move non-model logic to core.persistence + * @author Jan N. Klug - Refactored to {@link PersistenceServiceConfigurationProvider} */ -@Component(immediate = true) -public class PersistenceModelManager implements ModelRepositoryChangeListener { - - private ModelRepository modelRepository; - - private PersistenceManager manager; - - public PersistenceModelManager() { - } +@Component(immediate = true, service = PersistenceServiceConfigurationProvider.class) +@NonNullByDefault +public class PersistenceModelManager extends AbstractProvider + implements ModelRepositoryChangeListener, PersistenceServiceConfigurationProvider { + private final Map configurations = new ConcurrentHashMap<>(); + private final ModelRepository modelRepository; + + @Activate + public PersistenceModelManager(@Reference ModelRepository modelRepository) { + this.modelRepository = modelRepository; - protected void activate() { modelRepository.addModelRepositoryChangeListener(this); - for (String modelName : modelRepository.getAllModelNamesOfType("persist")) { - addModel(modelName); - } + modelRepository.getAllModelNamesOfType("persist") + .forEach(modelName -> modelChanged(modelName, EventType.ADDED)); } + @Deactivate protected void deactivate() { modelRepository.removeModelRepositoryChangeListener(this); - for (String modelName : modelRepository.getAllModelNamesOfType("persist")) { - removeModel(modelName); - } - } - - @Reference - protected void setModelRepository(ModelRepository modelRepository) { - this.modelRepository = modelRepository; - } - - protected void unsetModelRepository(ModelRepository modelRepository) { - this.modelRepository = null; - } - - @Reference - protected void setPersistenceManager(final PersistenceManager manager) { - this.manager = manager; - } - - protected void unsetPersistenceManager(final PersistenceManager manager) { - this.manager = null; + modelRepository.getAllModelNamesOfType("persist") + .forEach(modelName -> modelChanged(modelName, EventType.REMOVED)); } @Override public void modelChanged(String modelName, EventType type) { if (modelName.endsWith(".persist")) { - if (type == EventType.REMOVED || type == EventType.MODIFIED) { - removeModel(modelName); - } - if (type == EventType.ADDED || type == EventType.MODIFIED) { - addModel(modelName); - } - } - } - - private void addModel(String modelName) { - final PersistenceModel model = (PersistenceModel) modelRepository.getModel(modelName); - if (model != null) { String serviceName = serviceName(modelName); - manager.addConfig(serviceName, new PersistenceServiceConfiguration(mapConfigs(model.getConfigs()), - mapStrategies(model.getDefaults()), mapStrategies(model.getStrategies()))); + if (type == EventType.REMOVED) { + PersistenceServiceConfiguration removed = configurations.remove(serviceName); + notifyListenersAboutRemovedElement(removed); + } else { + final PersistenceModel model = (PersistenceModel) modelRepository.getModel(modelName); + + if (model != null) { + PersistenceServiceConfiguration newConfiguration = new PersistenceServiceConfiguration(serviceName, + mapConfigs(model.getConfigs()), mapStrategies(model.getDefaults()), + mapStrategies(model.getStrategies()), mapFilters(model.getFilters())); + PersistenceServiceConfiguration oldConfiguration = configurations.put(serviceName, + newConfiguration); + if (oldConfiguration == null) { + if (type != EventType.ADDED) { + logger.warn( + "Model {} is inconsistent: An updated event was sent, but there is no old configuration. Adding it now.", + modelName); + } + notifyListenersAboutAddedElement(newConfiguration); + } else { + if (type != EventType.MODIFIED) { + logger.warn( + "Model {} is inconsistent: An added event was sent, but there is an old configuration. Replacing it now.", + modelName); + } + notifyListenersAboutUpdatedElement(oldConfiguration, newConfiguration); + } + } else { + logger.error( + "The model repository reported a {} event for model '{}' but the model could not be found in the repository. ", + type, modelName); + } + } } } - private void removeModel(String modelName) { - String serviceName = serviceName(modelName); - manager.removeConfig(serviceName); - } - private String serviceName(String modelName) { return modelName.substring(0, modelName.length() - ".persist".length()); } @@ -168,6 +175,19 @@ private List mapFilters(List filters) { } private PersistenceFilter mapFilter(Filter filter) { - return new PersistenceFilter(); + if (filter.getDefinition() instanceof TimeFilter) { + TimeFilter timeFilter = (TimeFilter) filter.getDefinition(); + return new PersistenceTimeFilter(filter.getName(), timeFilter.getValue(), timeFilter.getUnit()); + } else if (filter.getDefinition() instanceof ThresholdFilter) { + ThresholdFilter thresholdFilter = (ThresholdFilter) filter.getDefinition(); + return new PersistenceThresholdFilter(filter.getName(), thresholdFilter.getValue(), + thresholdFilter.getUnit()); + } + throw new IllegalArgumentException("Unknown filter type " + filter.getClass()); + } + + @Override + public Collection getAll() { + return List.copyOf(configurations.values()); } } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/FilterCriteria.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/FilterCriteria.java index aaf2b5a458c..132660ea71b 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/FilterCriteria.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/FilterCriteria.java @@ -14,6 +14,8 @@ import java.time.ZonedDateTime; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.types.State; /** @@ -36,6 +38,7 @@ * @author Lyubomir Papazov - Deprecate methods using java.util and add methods * that use Java8's ZonedDateTime */ +@NonNullByDefault public class FilterCriteria { /** Enumeration with all possible compare options */ @@ -65,13 +68,13 @@ public enum Ordering { } /** filter result to only contain entries for the given item */ - private String itemName; + private @Nullable String itemName; /** filter result to only contain entries that are equal to or after the given datetime */ - private ZonedDateTime beginDate; + private @Nullable ZonedDateTime beginDate; /** filter result to only contain entries that are equal to or before the given datetime */ - private ZonedDateTime endDate; + private @Nullable ZonedDateTime endDate; /** return the result list from starting index pageNumber*pageSize only */ private int pageNumber = 0; @@ -86,17 +89,17 @@ public enum Ordering { private Ordering ordering = Ordering.DESCENDING; /** Filter result to only contain entries that evaluate to true with the given operator and state */ - private State state; + private @Nullable State state; - public String getItemName() { + public @Nullable String getItemName() { return itemName; } - public ZonedDateTime getBeginDate() { + public @Nullable ZonedDateTime getBeginDate() { return beginDate; } - public ZonedDateTime getEndDate() { + public @Nullable ZonedDateTime getEndDate() { return endDate; } @@ -116,7 +119,7 @@ public Ordering getOrdering() { return ordering; } - public State getState() { + public @Nullable State getState() { return state; } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/HistoricItem.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/HistoricItem.java index aab98c2ef56..a6c9bc24dd5 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/HistoricItem.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/HistoricItem.java @@ -15,7 +15,6 @@ import java.time.ZonedDateTime; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.items.Item; import org.openhab.core.types.State; /** @@ -23,7 +22,8 @@ * with a certain state at a given point in time. * *

- * Note that this interface does not extend {@link Item} as the persistence services could not provide an implementation + * Note that this interface does not extend {@link org.openhab.core.items.Item} as the persistence services could not + * provide an implementation * that correctly implement getAcceptedXTypes() and getGroupNames(). * * @author Kai Kreuzer - Initial contribution diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/ModifiablePersistenceService.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/ModifiablePersistenceService.java index 10c577a36ed..2feb4f4660f 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/ModifiablePersistenceService.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/ModifiablePersistenceService.java @@ -49,11 +49,11 @@ public interface ModifiablePersistenceService extends QueryablePersistenceServic /** * Removes data associated with an item from a persistence service. * If all data is removed for the specified item, the persistence service should free any resources associated with - * the item (eg. remove any tables or delete files from the storage). + * the item (e.g. remove any tables or delete files from the storage). * * @param filter the filter to apply to the data removal. ItemName can not be null. * @return true if the query executed successfully - * @throws {@link IllegalArgumentException} if item name is null. + * @throws IllegalArgumentException if item name is null. */ boolean remove(FilterCriteria filter) throws IllegalArgumentException; } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceItemConfiguration.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceItemConfiguration.java index 30b5f935595..36d462333d4 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceItemConfiguration.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceItemConfiguration.java @@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.persistence.config.PersistenceConfig; +import org.openhab.core.persistence.filter.PersistenceFilter; import org.openhab.core.persistence.strategy.PersistenceStrategy; /** @@ -26,12 +27,8 @@ * @author Markus Rathgeb - Initial contribution */ @NonNullByDefault -public class PersistenceItemConfiguration { - - private final List items; - private final @Nullable String alias; - private final List strategies; - private final List filters; +public record PersistenceItemConfiguration(List items, @Nullable String alias, + List strategies, List filters) { public PersistenceItemConfiguration(final List items, @Nullable final String alias, @Nullable final List strategies, @Nullable final List filters) { @@ -40,26 +37,4 @@ public PersistenceItemConfiguration(final List items, @Nullab this.strategies = Objects.requireNonNullElse(strategies, List.of()); this.filters = Objects.requireNonNullElse(filters, List.of()); } - - public List getItems() { - return items; - } - - public @Nullable String getAlias() { - return alias; - } - - public List getStrategies() { - return strategies; - } - - public List getFilters() { - return filters; - } - - @Override - public String toString() { - return String.format("%s [items=%s, alias=%s, strategies=%s, filters=%s]", getClass().getSimpleName(), items, - alias, strategies, filters); - } } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceManager.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceManager.java deleted file mode 100644 index 6e8d5b80c5a..00000000000 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceManager.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.persistence; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * A persistence manager service which could be used to start event handling or supply configuration for persistence - * services. - * - * @author Markus Rathgeb - Initial contribution - */ -@NonNullByDefault -public interface PersistenceManager { - /** - * Add a configuration for a persistence service. - * - * @param dbId the database id used by the persistence service - * @param config the configuration of the persistence service - */ - void addConfig(String dbId, PersistenceServiceConfiguration config); - - /** - * Remove a configuration for a persistence service. - * - * @param dbId the database id used by the persistence service - */ - void removeConfig(String dbId); -} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/QueryablePersistenceService.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/QueryablePersistenceService.java index 6b8c2dc56c3..f07bdcfec6f 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/QueryablePersistenceService.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/QueryablePersistenceService.java @@ -15,7 +15,6 @@ import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.items.Item; /** * A queryable persistence service which can be used to store and retrieve @@ -37,7 +36,8 @@ public interface QueryablePersistenceService extends PersistenceService { /** * Returns a set of {@link PersistenceItemInfo} about items that are stored in the persistence service. This allows - * the persistence service to return information about items that are no long available as an {@link Item} in + * the persistence service to return information about items that are no long available as an + * {@link org.openhab.core.items.Item} in * openHAB. If it is not possible to retrieve the information an empty set should be returned. * * @return a set of information about the persisted items diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/ItemHistoryDTO.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/ItemHistoryDTO.java index 2bf26b1e9bf..949805fe686 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/ItemHistoryDTO.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/ItemHistoryDTO.java @@ -45,9 +45,9 @@ public ItemHistoryDTO() { public void addData(Long time, State state) { HistoryDataBean newVal = new HistoryDataBean(); newVal.time = time; - if (state instanceof QuantityType type) { + if (state instanceof QuantityType quantityState) { // we strip the unit from the state, since historic item states are expected to be all in the default unit - newVal.state = type.toBigDecimal().toString(); + newVal.state = quantityState.toBigDecimal().toString(); } else { newVal.state = state.toString(); } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceFilter.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceCronStrategyDTO.java similarity index 61% rename from bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceFilter.java rename to bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceCronStrategyDTO.java index f7ae88e1f3c..50c098618c7 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceFilter.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceCronStrategyDTO.java @@ -10,16 +10,18 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.persistence; +package org.openhab.core.persistence.dto; import org.eclipse.jdt.annotation.NonNullByDefault; /** + * The {@link PersistenceCronStrategyDTO} is used for transferring persistence cron + * strategies * - * - * @author Markus Rathgeb - Initial contribution + * @author Jan N. Klug - Initial contribution */ @NonNullByDefault -public class PersistenceFilter { - +public class PersistenceCronStrategyDTO { + public String name = ""; + public String cronExpression = ""; } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceFilterDTO.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceFilterDTO.java new file mode 100644 index 00000000000..d59bc9a480d --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceFilterDTO.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.dto; + +import java.math.BigDecimal; + +/** + * The {@link org.openhab.core.persistence.dto.PersistenceFilterDTO} is used for transferring persistence filter + * configurations + * + * @author Jan N. Klug - Initial contribution + */ +public class PersistenceFilterDTO { + public String name = ""; + public BigDecimal value = BigDecimal.ZERO; + public String unit = ""; +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceItemConfigurationDTO.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceItemConfigurationDTO.java new file mode 100644 index 00000000000..e1b0e082e60 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceItemConfigurationDTO.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.dto; + +import java.util.Collection; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link org.openhab.core.persistence.dto.PersistenceItemConfigurationDTO} is used for transferring persistence + * item configurations + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class PersistenceItemConfigurationDTO { + public Collection items = List.of(); + public Collection strategies = List.of(); + public Collection filters = List.of(); + public @Nullable String alias; +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceServiceConfigurationDTO.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceServiceConfigurationDTO.java new file mode 100644 index 00000000000..bc796a36871 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceServiceConfigurationDTO.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.dto; + +import java.util.Collection; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PersistenceServiceConfigurationDTO} is used for transferring persistence service configurations + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class PersistenceServiceConfigurationDTO { + public String serviceId = ""; + public Collection configs = List.of(); + public Collection defaults = List.of(); + public Collection cronStrategies = List.of(); + public Collection thresholdFilters = List.of(); + public Collection timeFilters = List.of(); + + public boolean editable = false; +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceStrategyDTO.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceStrategyDTO.java new file mode 100644 index 00000000000..67bc881c0f4 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceStrategyDTO.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PersistenceStrategyDTO} is used for transferring persistence strategies. + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class PersistenceStrategyDTO { + public String type; + public String configuration; + + // do not remove - needed by GSON + PersistenceStrategyDTO() { + this("", ""); + } + + public PersistenceStrategyDTO(String type, String configuration) { + this.type = type; + this.configuration = configuration; + } +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java index 4b2bd7d7386..478587314f4 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java @@ -1208,15 +1208,10 @@ public static long countStateChangesBetween(Item item, ZonedDateTime begin, @Nul } private static @Nullable PersistenceService getService(String serviceId) { - PersistenceService service = null; if (registry != null) { - if (serviceId != null) { - service = registry.get(serviceId); - } else { - service = registry.getDefault(); - } + return serviceId != null ? registry.get(serviceId) : registry.getDefault(); } - return service; + return null; } private static @Nullable String getDefaultServiceId() { diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceFilter.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceFilter.java new file mode 100644 index 00000000000..8ee2c8559c2 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceFilter.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.filter; + +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.items.Item; + +/** + * The {@link PersistenceFilter} is the base class for implementing persistence filters. + * + * @author Markus Rathgeb - Initial contribution + */ +@NonNullByDefault +public abstract class PersistenceFilter { + private final String name; + + public PersistenceFilter(final String name) { + this.name = name; + } + + /** + * Get the name of this filter + * + * @return a unique name + */ + public String getName() { + return name; + } + + /** + * Apply this filter to an item + * + * @param item the item to check + * @return true if the filter allows persisting this value + */ + public abstract boolean apply(Item item); + + /** + * Notify filter that item was persisted + * + * @param item the persisted item + */ + public abstract void persisted(Item item); + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + name.hashCode(); + return result; + } + + @Override + public boolean equals(final @Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof PersistenceFilter)) { + return false; + } + final PersistenceFilter other = (PersistenceFilter) obj; + return Objects.equals(name, other.name); + } + + @Override + public String toString() { + return String.format("%s [name=%s]", getClass().getSimpleName(), name); + } +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceThresholdFilter.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceThresholdFilter.java new file mode 100644 index 00000000000..efd3d6d968c --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceThresholdFilter.java @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.filter; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.HashMap; +import java.util.Map; + +import javax.measure.UnconvertibleException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.items.Item; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PersistenceThresholdFilter} is a filter to prevent persistence based on a threshold. + * + * The filter returns {@code false} if the new value deviates by less than {@link #value}. If unit is "%" is + * {@code true}, the filter returns {@code false} if the relative deviation is less than {@link #value}. + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class PersistenceThresholdFilter extends PersistenceFilter { + private static final BigDecimal HUNDRED = BigDecimal.valueOf(100); + + private final Logger logger = LoggerFactory.getLogger(PersistenceThresholdFilter.class); + + private final BigDecimal value; + private final String unit; + + private final transient Map valueCache = new HashMap<>(); + + public PersistenceThresholdFilter(String name, BigDecimal value, String unit) { + super(name); + this.value = value; + this.unit = unit; + } + + public BigDecimal getValue() { + return value; + } + + public String getUnit() { + return unit; + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public boolean apply(Item item) { + String itemName = item.getName(); + State state = item.getState(); + if (!(state instanceof DecimalType || state instanceof QuantityType)) { + return true; + } + + State cachedState = valueCache.get(itemName); + + if (cachedState == null || !state.getClass().equals(cachedState.getClass())) { + return true; + } + + if (state instanceof DecimalType) { + BigDecimal oldState = ((DecimalType) cachedState).toBigDecimal(); + BigDecimal delta = oldState.subtract(((DecimalType) state).toBigDecimal()); + if ("%".equals(unit) && !BigDecimal.ZERO.equals(oldState)) { + delta = delta.multiply(HUNDRED).divide(oldState, 2, RoundingMode.HALF_UP); + } + return delta.abs().compareTo(value) > 0; + } else { + try { + QuantityType oldState = (QuantityType) cachedState; + QuantityType delta = oldState.subtract((QuantityType) state); + if ("%".equals(unit)) { + if (BigDecimal.ZERO.equals(oldState.toBigDecimal())) { + // value is different and old value is 0 -> always above relative threshold + return true; + } else { + // calculate percent + delta = delta.multiply(HUNDRED).divide(oldState); + } + } else if (!unit.isBlank()) { + // consider unit only if not relative threshold + delta = delta.toUnit(unit); + if (delta == null) { + throw new UnconvertibleException(""); + } + } + + return delta.toBigDecimal().abs().compareTo(value) > 0; + } catch (UnconvertibleException e) { + logger.warn("Cannot compare {} to {}", cachedState, state); + return true; + } + } + } + + @Override + public void persisted(Item item) { + valueCache.put(item.getName(), item.getState()); + } + + @Override + public String toString() { + return String.format("%s [name=%s, value=%s, unit=%s]", getClass().getSimpleName(), getName(), value, unit); + } +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceTimeFilter.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceTimeFilter.java new file mode 100644 index 00000000000..c3edcfdc6f3 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceTimeFilter.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.filter; + +import java.time.Duration; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.items.Item; + +/** + * The {@link PersistenceTimeFilter} is a filter to prevent persistence base on intervals. + * + * The filter returns {@link false} if the time between now and the time of the last persisted value is less than + * {@link #duration} {@link #unit} + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class PersistenceTimeFilter extends PersistenceFilter { + private final int value; + private final String unit; + + private transient @Nullable Duration duration; + private final transient Map nextPersistenceTimes = new HashMap<>(); + + public PersistenceTimeFilter(String name, int value, String unit) { + super(name); + this.value = value; + this.unit = unit; + } + + public int getValue() { + return value; + } + + public String getUnit() { + return unit; + } + + @Override + public boolean apply(Item item) { + String itemName = item.getName(); + + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime nextPersistenceTime = nextPersistenceTimes.get(itemName); + + return nextPersistenceTime == null || !now.isBefore(nextPersistenceTime); + } + + @Override + public void persisted(Item item) { + Duration duration = this.duration; + if (duration == null) { + duration = switch (unit) { + case "m" -> Duration.of(value, ChronoUnit.MINUTES); + case "h" -> Duration.of(value, ChronoUnit.HOURS); + case "d" -> Duration.of(value, ChronoUnit.DAYS); + default -> Duration.of(value, ChronoUnit.SECONDS); + }; + + this.duration = duration; + } + nextPersistenceTimes.put(item.getName(), ZonedDateTime.now().plus(duration)); + } + + @Override + public String toString() { + return String.format("%s [name=%s, value=%s, unit=%s]", getClass().getSimpleName(), getName(), value, unit); + } +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistItemsJob.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistItemsJob.java deleted file mode 100644 index 586525ad9a2..00000000000 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistItemsJob.java +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.persistence.internal; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.items.Item; -import org.openhab.core.persistence.PersistenceItemConfiguration; -import org.openhab.core.persistence.PersistenceService; -import org.openhab.core.persistence.PersistenceServiceConfiguration; -import org.openhab.core.persistence.strategy.PersistenceStrategy; -import org.openhab.core.scheduler.SchedulerRunnable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Implementation of a persistence job that could be executed e.g. for specific cron expressions. - * - * @author Kai Kreuzer - Initial contribution - * @author Markus Rathgeb - Separation of persistence core and model, drop Quartz usage. - */ -@NonNullByDefault -public class PersistItemsJob implements SchedulerRunnable { - - private final Logger logger = LoggerFactory.getLogger(PersistItemsJob.class); - - private final PersistenceManagerImpl manager; - private final String dbId; - private final String strategyName; - - public PersistItemsJob(final PersistenceManagerImpl manager, final String dbId, final String strategyName) { - this.manager = manager; - this.dbId = dbId; - this.strategyName = strategyName; - } - - @Override - public void run() { - synchronized (manager.persistenceServiceConfigs) { - final PersistenceService persistenceService = manager.persistenceServices.get(dbId); - final PersistenceServiceConfiguration config = manager.persistenceServiceConfigs.get(dbId); - - if (persistenceService != null) { - for (PersistenceItemConfiguration itemConfig : config.getConfigs()) { - if (hasStrategy(config.getDefaults(), itemConfig, strategyName)) { - for (Item item : manager.getAllItems(itemConfig)) { - long startTime = System.nanoTime(); - persistenceService.store(item, itemConfig.getAlias()); - logger.trace("Storing item '{}' with persistence service '{}' took {}ms", item.getName(), - dbId, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)); - } - } - - } - } - } - } - - private boolean hasStrategy(List defaults, PersistenceItemConfiguration config, - String strategyName) { - // check if the strategy is directly defined on the config - for (PersistenceStrategy strategy : config.getStrategies()) { - if (strategyName.equals(strategy.getName())) { - return true; - } - } - // if no strategies are given, check the default strategies to use - return config.getStrategies().isEmpty() && isDefault(defaults, strategyName); - } - - private boolean isDefault(List defaults, String strategyName) { - for (PersistenceStrategy strategy : defaults) { - if (strategy.getName().equals(strategyName)) { - return true; - } - } - return false; - } -} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManager.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManager.java new file mode 100644 index 00000000000..69b7fae7c66 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManager.java @@ -0,0 +1,465 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.internal; + +import java.time.format.DateTimeFormatter; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.common.NamedThreadFactory; +import org.openhab.core.common.SafeCaller; +import org.openhab.core.items.GenericItem; +import org.openhab.core.items.GroupItem; +import org.openhab.core.items.Item; +import org.openhab.core.items.ItemNotFoundException; +import org.openhab.core.items.ItemRegistry; +import org.openhab.core.items.ItemRegistryChangeListener; +import org.openhab.core.items.StateChangeListener; +import org.openhab.core.persistence.FilterCriteria; +import org.openhab.core.persistence.HistoricItem; +import org.openhab.core.persistence.PersistenceItemConfiguration; +import org.openhab.core.persistence.PersistenceService; +import org.openhab.core.persistence.QueryablePersistenceService; +import org.openhab.core.persistence.config.PersistenceAllConfig; +import org.openhab.core.persistence.config.PersistenceConfig; +import org.openhab.core.persistence.config.PersistenceGroupConfig; +import org.openhab.core.persistence.config.PersistenceItemConfig; +import org.openhab.core.persistence.registry.PersistenceServiceConfiguration; +import org.openhab.core.persistence.registry.PersistenceServiceConfigurationRegistry; +import org.openhab.core.persistence.registry.PersistenceServiceConfigurationRegistryChangeListener; +import org.openhab.core.persistence.strategy.PersistenceCronStrategy; +import org.openhab.core.persistence.strategy.PersistenceStrategy; +import org.openhab.core.scheduler.CronScheduler; +import org.openhab.core.scheduler.ScheduledCompletableFuture; +import org.openhab.core.service.ReadyMarker; +import org.openhab.core.service.ReadyMarkerFilter; +import org.openhab.core.service.ReadyService; +import org.openhab.core.service.ReadyService.ReadyTracker; +import org.openhab.core.service.StartLevelService; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class implements a persistence manager to manage all persistence services etc. + * + * @author Kai Kreuzer - Initial contribution + * @author Markus Rathgeb - Separation of persistence core and model, drop Quartz usage. + * @author Jan N. Klug - Refactored to use service configuration registry + */ +@Component(immediate = true) +@NonNullByDefault +public class PersistenceManager implements ItemRegistryChangeListener, StateChangeListener, ReadyTracker, + PersistenceServiceConfigurationRegistryChangeListener { + + private final Logger logger = LoggerFactory.getLogger(PersistenceManager.class); + + private final ReadyMarker marker = new ReadyMarker("persistence", "restore"); + + // the scheduler used for timer events + private final CronScheduler scheduler; + private final ItemRegistry itemRegistry; + private final SafeCaller safeCaller; + private final ReadyService readyService; + private final PersistenceServiceConfigurationRegistry persistenceServiceConfigurationRegistry; + + private volatile boolean started = false; + + private final Map persistenceServiceContainers = new ConcurrentHashMap<>(); + + @Activate + public PersistenceManager(final @Reference CronScheduler scheduler, final @Reference ItemRegistry itemRegistry, + final @Reference SafeCaller safeCaller, final @Reference ReadyService readyService, + final @Reference PersistenceServiceConfigurationRegistry persistenceServiceConfigurationRegistry) { + this.scheduler = scheduler; + this.itemRegistry = itemRegistry; + this.safeCaller = safeCaller; + this.readyService = readyService; + this.persistenceServiceConfigurationRegistry = persistenceServiceConfigurationRegistry; + + persistenceServiceConfigurationRegistry.addRegistryChangeListener(this); + readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE) + .withIdentifier(Integer.toString(StartLevelService.STARTLEVEL_MODEL))); + } + + @Deactivate + protected void deactivate() { + itemRegistry.removeRegistryChangeListener(this); + persistenceServiceConfigurationRegistry.removeRegistryChangeListener(this); + started = false; + + persistenceServiceContainers.values().forEach(PersistenceServiceContainer::cancelPersistJobs); + + // remove item state change listeners + itemRegistry.stream().filter(GenericItem.class::isInstance) + .forEach(item -> ((GenericItem) item).removeStateChangeListener(this)); + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + protected void addPersistenceService(PersistenceService persistenceService) { + String serviceId = persistenceService.getId(); + logger.debug("Initializing {} persistence service.", serviceId); + PersistenceServiceContainer container = new PersistenceServiceContainer(persistenceService, + persistenceServiceConfigurationRegistry.get(serviceId)); + + PersistenceServiceContainer oldContainer = persistenceServiceContainers.put(serviceId, container); + + if (oldContainer != null) { // cancel all jobs if the persistence service is set and an old configuration is + // already present + oldContainer.cancelPersistJobs(); + } + + if (started) { + startEventHandling(container); + } + } + + protected void removePersistenceService(PersistenceService persistenceService) { + PersistenceServiceContainer container = persistenceServiceContainers.remove(persistenceService.getId()); + if (container != null) { + container.cancelPersistJobs(); + } + } + + /** + * Calls all persistence services which use change or update policy for the given item + * + * @param item the item to persist + * @param changed true, if it has the change strategy, false otherwise + */ + private void handleStateEvent(Item item, boolean changed) { + PersistenceStrategy changeStrategy = changed ? PersistenceStrategy.Globals.CHANGE + : PersistenceStrategy.Globals.UPDATE; + + persistenceServiceContainers.values() + .forEach(container -> container.getMatchingConfigurations(changeStrategy) + .filter(itemConfig -> appliesToItem(itemConfig, item)) + .filter(itemConfig -> itemConfig.filters().stream().allMatch(filter -> filter.apply(item))) + .forEach(itemConfig -> { + itemConfig.filters().forEach(filter -> filter.persisted(item)); + container.getPersistenceService().store(item, itemConfig.alias()); + })); + } + + /** + * Checks if a given persistence configuration entry is relevant for an item + * + * @param itemConfig the persistence configuration entry + * @param item to check if the configuration applies to + * @return true, if the configuration applies to the item + */ + private boolean appliesToItem(PersistenceItemConfiguration itemConfig, Item item) { + for (PersistenceConfig itemCfg : itemConfig.items()) { + if (itemCfg instanceof PersistenceAllConfig) { + return true; + } else if (itemCfg instanceof PersistenceItemConfig) { + if (item.getName().equals(((PersistenceItemConfig) itemCfg).getItem())) { + return true; + } + } else if (itemCfg instanceof PersistenceGroupConfig) { + try { + Item gItem = itemRegistry.getItem(((PersistenceGroupConfig) itemCfg).getGroup()); + if (gItem instanceof GroupItem) { + return ((GroupItem) gItem).getAllMembers().contains(item); + } + } catch (ItemNotFoundException e) { + // do nothing + } + } + } + return false; + } + + /** + * Retrieves all items for which the persistence configuration applies to. + * + * @param config the persistence configuration entry + * @return all items that this configuration applies to + */ + private Iterable getAllItems(PersistenceItemConfiguration config) { + // first check, if we should return them all + if (config.items().stream().anyMatch(PersistenceAllConfig.class::isInstance)) { + return itemRegistry.getItems(); + } + + // otherwise, go through the detailed definitions + Set items = new HashSet<>(); + for (Object itemCfg : config.items()) { + if (itemCfg instanceof PersistenceItemConfig) { + String itemName = ((PersistenceItemConfig) itemCfg).getItem(); + try { + items.add(itemRegistry.getItem(itemName)); + } catch (ItemNotFoundException e) { + logger.debug("Item '{}' does not exist.", itemName); + } + } + if (itemCfg instanceof PersistenceGroupConfig) { + String groupName = ((PersistenceGroupConfig) itemCfg).getGroup(); + try { + Item gItem = itemRegistry.getItem(groupName); + if (gItem instanceof GroupItem groupItem) { + items.addAll(groupItem.getAllMembers()); + } + } catch (ItemNotFoundException e) { + logger.debug("Item group '{}' does not exist.", groupName); + } + } + } + return items; + } + + /** + * Handles the "restoreOnStartup" strategy for the item. + * If the item state is still undefined when entering this method, all persistence configurations are checked, + * if they have the "restoreOnStartup" strategy configured for the item. If so, the item state will be set + * to its last persisted value. + * + * @param item the item to restore the state for + */ + private void restoreItemStateIfNeeded(Item item) { + // get the last persisted state from the persistence service if no state is yet set + if (UnDefType.NULL.equals(item.getState()) && item instanceof GenericItem) { + List matchingContainers = persistenceServiceContainers.values().stream() // + .filter(container -> container.getPersistenceService() instanceof QueryablePersistenceService) // + .filter(container -> container.getMatchingConfigurations(PersistenceStrategy.Globals.RESTORE) + .anyMatch(itemConfig -> appliesToItem(itemConfig, item))) + .toList(); + + for (PersistenceServiceContainer container : matchingContainers) { + QueryablePersistenceService queryService = (QueryablePersistenceService) container + .getPersistenceService(); + FilterCriteria filter = new FilterCriteria().setItemName(item.getName()).setPageSize(1); + Iterator result = safeCaller.create(queryService, QueryablePersistenceService.class) + .onTimeout(() -> logger.warn("Querying persistence service '{}' takes more than {}ms.", + queryService.getId(), SafeCaller.DEFAULT_TIMEOUT)) + .onException(e -> logger.error("Exception occurred while querying persistence service '{}': {}", + queryService.getId(), e.getMessage(), e)) + .build().query(filter).iterator(); + if (result.hasNext()) { + HistoricItem historicItem = result.next(); + GenericItem genericItem = (GenericItem) item; + genericItem.removeStateChangeListener(this); + genericItem.setState(historicItem.getState()); + genericItem.addStateChangeListener(this); + if (logger.isDebugEnabled()) { + logger.debug("Restored item state from '{}' for item '{}' -> '{}'", + DateTimeFormatter.ISO_ZONED_DATE_TIME.format(historicItem.getTimestamp()), + item.getName(), historicItem.getState()); + } + return; + } + } + } + } + + private void startEventHandling(PersistenceServiceContainer serviceContainer) { + serviceContainer.getMatchingConfigurations(PersistenceStrategy.Globals.RESTORE) + .forEach(itemConfig -> getAllItems(itemConfig).forEach(this::restoreItemStateIfNeeded)); + serviceContainer.schedulePersistJobs(); + } + + // ItemStateChangeListener methods + + @Override + public void allItemsChanged(Collection oldItemNames) { + itemRegistry.getItems().forEach(this::added); + } + + @Override + public void added(Item item) { + restoreItemStateIfNeeded(item); + if (item instanceof GenericItem genericItem) { + genericItem.addStateChangeListener(this); + } + } + + @Override + public void removed(Item item) { + if (item instanceof GenericItem genericItem) { + genericItem.removeStateChangeListener(this); + } + } + + @Override + public void updated(Item oldItem, Item item) { + removed(oldItem); + added(item); + } + + @Override + public void stateChanged(Item item, State oldState, State newState) { + handleStateEvent(item, true); + } + + @Override + public void stateUpdated(Item item, State state) { + handleStateEvent(item, false); + } + + @Override + public void onReadyMarkerAdded(ReadyMarker readyMarker) { + ExecutorService scheduler = Executors.newSingleThreadExecutor(new NamedThreadFactory("persistenceManager")); + scheduler.submit(() -> { + allItemsChanged(Set.of()); + persistenceServiceContainers.values().forEach(this::startEventHandling); + started = true; + readyService.markReady(marker); + itemRegistry.addRegistryChangeListener(this); + }); + scheduler.shutdown(); + } + + @Override + public void onReadyMarkerRemoved(ReadyMarker readyMarker) { + readyService.unmarkReady(marker); + } + + @Override + public void added(PersistenceServiceConfiguration element) { + PersistenceServiceContainer container = persistenceServiceContainers.get(element.getUID()); + if (container != null) { + container.setConfiguration(element); + if (started) { + startEventHandling(container); + } + } + } + + @Override + public void removed(PersistenceServiceConfiguration element) { + PersistenceServiceContainer container = persistenceServiceContainers.get(element.getUID()); + if (container != null) { + container.setConfiguration(null); + if (started) { + startEventHandling(container); + } + } + } + + @Override + public void updated(PersistenceServiceConfiguration oldElement, PersistenceServiceConfiguration element) { + // no need to remove before, configuration is overwritten if possible + added(element); + } + + private class PersistenceServiceContainer { + private final PersistenceService persistenceService; + private final Set> jobs = new HashSet<>(); + + private PersistenceServiceConfiguration configuration; + + public PersistenceServiceContainer(PersistenceService persistenceService, + @Nullable PersistenceServiceConfiguration configuration) { + this.persistenceService = persistenceService; + this.configuration = Objects.requireNonNullElseGet(configuration, this::getDefaultConfig); + } + + public PersistenceService getPersistenceService() { + return persistenceService; + } + + /** + * Set a new configuration for this persistence service (also cancels all cron jobs) + * + * @param configuration the new {@link PersistenceServiceConfiguration}, if {@code null} the default + * configuration of the service is used + */ + public void setConfiguration(@Nullable PersistenceServiceConfiguration configuration) { + cancelPersistJobs(); + this.configuration = Objects.requireNonNullElseGet(configuration, this::getDefaultConfig); + } + + /** + * Get all item configurations from this service that match a certain strategy + * + * @param strategy the {@link PersistenceStrategy} to look for + * @return a @link Stream} of the result + */ + public Stream getMatchingConfigurations(PersistenceStrategy strategy) { + boolean matchesDefaultStrategies = configuration.getDefaults().contains(strategy); + return configuration.getConfigs().stream().filter(itemConfig -> itemConfig.strategies().contains(strategy) + || (itemConfig.strategies().isEmpty() && matchesDefaultStrategies)); + } + + private PersistenceServiceConfiguration getDefaultConfig() { + List strategies = persistenceService.getDefaultStrategies(); + List configs = List + .of(new PersistenceItemConfiguration(List.of(new PersistenceAllConfig()), null, strategies, null)); + return new PersistenceServiceConfiguration(persistenceService.getId(), configs, strategies, strategies, + List.of()); + } + + /** + * Cancel all scheduled cron jobs / strategies for this service + */ + public void cancelPersistJobs() { + synchronized (jobs) { + jobs.forEach(job -> job.cancel(true)); + jobs.clear(); + } + logger.debug("Removed scheduled cron job for persistence service '{}'", configuration.getUID()); + } + + /** + * Schedule all necessary cron jobs / strategies for this service + */ + public void schedulePersistJobs() { + configuration.getStrategies().stream().filter(PersistenceCronStrategy.class::isInstance) + .forEach(strategy -> { + PersistenceCronStrategy cronStrategy = (PersistenceCronStrategy) strategy; + String cronExpression = cronStrategy.getCronExpression(); + List itemConfigs = getMatchingConfigurations(strategy) + .collect(Collectors.toList()); + jobs.add(scheduler.schedule(() -> persistJob(itemConfigs), cronExpression)); + + logger.debug("Scheduled strategy {} with cron expression {} for service {}", + cronStrategy.getName(), cronExpression, configuration.getUID()); + + }); + } + + private void persistJob(List itemConfigs) { + itemConfigs.forEach(itemConfig -> { + for (Item item : getAllItems(itemConfig)) { + if (itemConfig.filters().stream().allMatch(filter -> filter.apply(item))) { + long startTime = System.nanoTime(); + itemConfig.filters().forEach(filter -> filter.persisted(item)); + persistenceService.store(item, itemConfig.alias()); + logger.trace("Storing item '{}' with persistence service '{}' took {}ms", item.getName(), + configuration.getUID(), TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)); + } + } + }); + } + } +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java deleted file mode 100644 index be9dcf0e5bb..00000000000 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java +++ /dev/null @@ -1,489 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.persistence.internal; - -import java.time.format.DateTimeFormatter; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.common.NamedThreadFactory; -import org.openhab.core.common.SafeCaller; -import org.openhab.core.items.GenericItem; -import org.openhab.core.items.GroupItem; -import org.openhab.core.items.Item; -import org.openhab.core.items.ItemNotFoundException; -import org.openhab.core.items.ItemRegistry; -import org.openhab.core.items.ItemRegistryChangeListener; -import org.openhab.core.items.StateChangeListener; -import org.openhab.core.persistence.FilterCriteria; -import org.openhab.core.persistence.HistoricItem; -import org.openhab.core.persistence.PersistenceItemConfiguration; -import org.openhab.core.persistence.PersistenceManager; -import org.openhab.core.persistence.PersistenceService; -import org.openhab.core.persistence.PersistenceServiceConfiguration; -import org.openhab.core.persistence.QueryablePersistenceService; -import org.openhab.core.persistence.config.PersistenceAllConfig; -import org.openhab.core.persistence.config.PersistenceConfig; -import org.openhab.core.persistence.config.PersistenceGroupConfig; -import org.openhab.core.persistence.config.PersistenceItemConfig; -import org.openhab.core.persistence.strategy.PersistenceCronStrategy; -import org.openhab.core.persistence.strategy.PersistenceStrategy; -import org.openhab.core.scheduler.CronScheduler; -import org.openhab.core.scheduler.ScheduledCompletableFuture; -import org.openhab.core.service.ReadyMarker; -import org.openhab.core.service.ReadyMarkerFilter; -import org.openhab.core.service.ReadyService; -import org.openhab.core.service.ReadyService.ReadyTracker; -import org.openhab.core.service.StartLevelService; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * This class implements a persistence manager to manage all persistence services etc. - * - * @author Kai Kreuzer - Initial contribution - * @author Markus Rathgeb - Separation of persistence core and model, drop Quartz usage. - */ -@Component(immediate = true, service = PersistenceManager.class) -@NonNullByDefault -public class PersistenceManagerImpl - implements ItemRegistryChangeListener, PersistenceManager, StateChangeListener, ReadyTracker { - - private final Logger logger = LoggerFactory.getLogger(PersistenceManagerImpl.class); - - private final ReadyMarker marker = new ReadyMarker("persistence", "restore"); - - // the scheduler used for timer events - private final CronScheduler scheduler; - private final ItemRegistry itemRegistry; - private final SafeCaller safeCaller; - private final ReadyService readyService; - private volatile boolean started = false; - - final Map persistenceServices = new HashMap<>(); - final Map persistenceServiceConfigs = new HashMap<>(); - private final Map>> persistenceJobs = new HashMap<>(); - - @Activate - public PersistenceManagerImpl(final @Reference CronScheduler scheduler, final @Reference ItemRegistry itemRegistry, - final @Reference SafeCaller safeCaller, final @Reference ReadyService readyService) { - this.scheduler = scheduler; - this.itemRegistry = itemRegistry; - this.safeCaller = safeCaller; - this.readyService = readyService; - } - - @Activate - protected void activate() { - readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE) - .withIdentifier(Integer.toString(StartLevelService.STARTLEVEL_MODEL))); - } - - @Deactivate - protected void deactivate() { - itemRegistry.removeRegistryChangeListener(this); - started = false; - removeTimers(); - removeItemStateChangeListeners(); - } - - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - protected void addPersistenceService(PersistenceService persistenceService) { - logger.debug("Initializing {} persistence service.", persistenceService.getId()); - persistenceServices.put(persistenceService.getId(), persistenceService); - persistenceServiceConfigs.putIfAbsent(persistenceService.getId(), getDefaultConfig(persistenceService)); - if (started) { - stopEventHandling(persistenceService.getId()); - startEventHandling(persistenceService.getId()); - } - } - - protected void removePersistenceService(PersistenceService persistenceService) { - stopEventHandling(persistenceService.getId()); - persistenceServices.remove(persistenceService.getId()); - } - - /** - * Calls all persistence services which use change or update policy for the given item - * - * @param item the item to persist - * @param onlyChanges true, if it has the change strategy, false otherwise - */ - private void handleStateEvent(Item item, boolean onlyChanges) { - synchronized (persistenceServiceConfigs) { - for (Entry entry : persistenceServiceConfigs - .entrySet()) { - final String serviceName = entry.getKey(); - final PersistenceServiceConfiguration config = entry.getValue(); - if (config != null && persistenceServices.containsKey(serviceName)) { - for (PersistenceItemConfiguration itemConfig : config.getConfigs()) { - if (hasStrategy(config, itemConfig, onlyChanges ? PersistenceStrategy.Globals.CHANGE - : PersistenceStrategy.Globals.UPDATE)) { - if (appliesToItem(itemConfig, item)) { - persistenceServices.get(serviceName).store(item, itemConfig.getAlias()); - } - } - } - } - } - } - } - - /** - * Checks if a given persistence configuration entry has a certain strategy for the given service - * - * @param config the configuration to check for - * @param itemConfig the persistence configuration entry - * @param strategy the strategy to check for - * @return true, if it has the given strategy - */ - private boolean hasStrategy(PersistenceServiceConfiguration config, PersistenceItemConfiguration itemConfig, - PersistenceStrategy strategy) { - if (config.getDefaults().contains(strategy) && itemConfig.getStrategies().isEmpty()) { - return true; - } else { - for (PersistenceStrategy s : itemConfig.getStrategies()) { - if (s.equals(strategy)) { - return true; - } - } - return false; - } - } - - /** - * Checks if a given persistence configuration entry is relevant for an item - * - * @param config the persistence configuration entry - * @param item to check if the configuration applies to - * @return true, if the configuration applies to the item - */ - private boolean appliesToItem(PersistenceItemConfiguration config, Item item) { - for (PersistenceConfig itemCfg : config.getItems()) { - if (itemCfg instanceof PersistenceAllConfig) { - return true; - } - if (itemCfg instanceof PersistenceItemConfig singleItemConfig) { - if (item.getName().equals(singleItemConfig.getItem())) { - return true; - } - } - if (itemCfg instanceof PersistenceGroupConfig groupItemConfig) { - try { - Item gItem = itemRegistry.getItem(groupItemConfig.getGroup()); - if (gItem instanceof GroupItem groupItem) { - if (groupItem.getAllMembers().contains(item)) { - return true; - } - } - } catch (ItemNotFoundException e) { - // do nothing - } - } - } - return false; - } - - /** - * Retrieves all items for which the persistence configuration applies to. - * - * @param config the persistence configuration entry - * @return all items that this configuration applies to - */ - Iterable getAllItems(PersistenceItemConfiguration config) { - // first check, if we should return them all - for (Object itemCfg : config.getItems()) { - if (itemCfg instanceof PersistenceAllConfig) { - return itemRegistry.getItems(); - } - } - - // otherwise, go through the detailed definitions - Set items = new HashSet<>(); - for (Object itemCfg : config.getItems()) { - if (itemCfg instanceof PersistenceItemConfig singleItemConfig) { - String itemName = singleItemConfig.getItem(); - try { - items.add(itemRegistry.getItem(itemName)); - } catch (ItemNotFoundException e) { - logger.debug("Item '{}' does not exist.", itemName); - } - } - if (itemCfg instanceof PersistenceGroupConfig groupItemConfig) { - String groupName = groupItemConfig.getGroup(); - try { - Item gItem = itemRegistry.getItem(groupName); - if (gItem instanceof GroupItem groupItem) { - items.addAll(groupItem.getAllMembers()); - } - } catch (ItemNotFoundException e) { - logger.debug("Item group '{}' does not exist.", groupName); - } - } - } - return items; - } - - /** - * Handles the "restoreOnStartup" strategy for the item. - * If the item state is still undefined when entering this method, all persistence configurations are checked, - * if they have the "restoreOnStartup" strategy configured for the item. If so, the item state will be set - * to its last persisted value. - * - * @param item the item to restore the state for - */ - @SuppressWarnings("null") - private void initialize(Item item) { - // get the last persisted state from the persistence service if no state is yet set - if (UnDefType.NULL.equals(item.getState()) && item instanceof GenericItem genericItem) { - for (Entry entry : persistenceServiceConfigs - .entrySet()) { - final String serviceName = entry.getKey(); - final PersistenceServiceConfiguration config = entry.getValue(); - if (config != null) { - for (PersistenceItemConfiguration itemConfig : config.getConfigs()) { - if (hasStrategy(config, itemConfig, PersistenceStrategy.Globals.RESTORE)) { - if (appliesToItem(itemConfig, item)) { - PersistenceService service = persistenceServices.get(serviceName); - if (service instanceof QueryablePersistenceService queryService) { - FilterCriteria filter = new FilterCriteria().setItemName(item.getName()) - .setPageSize(1); - Iterable result = safeCaller - .create(queryService, QueryablePersistenceService.class).onTimeout(() -> { - logger.warn("Querying persistence service '{}' takes more than {}ms.", - queryService.getId(), SafeCaller.DEFAULT_TIMEOUT); - }).onException(e -> { - logger.error( - "Exception occurred while querying persistence service '{}': {}", - queryService.getId(), e.getMessage(), e); - }).build().query(filter); - if (result != null) { - Iterator it = result.iterator(); - if (it.hasNext()) { - HistoricItem historicItem = it.next(); - genericItem.removeStateChangeListener(this); - genericItem.setState(historicItem.getState()); - genericItem.addStateChangeListener(this); - if (logger.isDebugEnabled()) { - logger.debug("Restored item state from '{}' for item '{}' -> '{}'", - DateTimeFormatter.ISO_ZONED_DATE_TIME - .format(historicItem.getTimestamp()), - item.getName(), historicItem.getState()); - } - return; - } - } - } else if (service != null) { - logger.warn( - "Failed to restore item states as persistence service '{}' cannot be queried.", - serviceName); - } - } - } - } - } - } - } - } - - private void removeItemStateChangeListeners() { - for (Item item : itemRegistry.getAll()) { - if (item instanceof GenericItem genericItem) { - genericItem.removeStateChangeListener(this); - } - } - } - - /** - * Creates new {@link ScheduledCompletableFuture}s in the group dbId for the given collection of - * {@link PersistenceStrategy strategies}. - * - * @param dbId the database id used by the persistence service - * @param strategies a collection of strategies - */ - private void createTimers(final String dbId, List strategies) { - for (PersistenceStrategy strategy : strategies) { - if (strategy instanceof PersistenceCronStrategy cronStrategy) { - String cronExpression = cronStrategy.getCronExpression(); - - final PersistItemsJob job = new PersistItemsJob(this, dbId, cronStrategy.getName()); - ScheduledCompletableFuture schedule = scheduler.schedule(job, cronExpression); - if (persistenceJobs.containsKey(dbId)) { - persistenceJobs.get(dbId).add(schedule); - } else { - final Set> jobs = new HashSet<>(); - jobs.add(schedule); - persistenceJobs.put(dbId, jobs); - } - - logger.debug("Scheduled strategy {} with cron expression {}", cronStrategy.getName(), cronExpression); - } - } - } - - /** - * Deletes all {@link ScheduledCompletableFuture}s of the group dbId. - * - * @param dbId the database id used by the persistence service - */ - private void removeTimers(String dbId) { - if (!persistenceJobs.containsKey(dbId)) { - return; - } - for (final ScheduledCompletableFuture job : persistenceJobs.get(dbId)) { - job.cancel(true); - logger.debug("Removed scheduled cron job for persistence service '{}'", dbId); - } - persistenceJobs.remove(dbId); - } - - private void removeTimers() { - Set dbIds = new HashSet<>(persistenceJobs.keySet()); - for (String dbId : dbIds) { - removeTimers(dbId); - } - } - - @Override - public void addConfig(final String dbId, final PersistenceServiceConfiguration config) { - synchronized (persistenceServiceConfigs) { - if (started && persistenceServiceConfigs.containsKey(dbId)) { - stopEventHandling(dbId); - } - persistenceServiceConfigs.put(dbId, config); - if (started && persistenceServices.containsKey(dbId)) { - startEventHandling(dbId); - } - } - } - - @Override - public void removeConfig(final String dbId) { - synchronized (persistenceServiceConfigs) { - stopEventHandling(dbId); - PersistenceService persistenceService = persistenceServices.get(dbId); - if (persistenceService != null) { - persistenceServiceConfigs.put(dbId, getDefaultConfig(persistenceService)); - startEventHandling(dbId); - } else { - persistenceServiceConfigs.remove(dbId); - } - } - } - - private void startEventHandling(final String serviceName) { - synchronized (persistenceServiceConfigs) { - final PersistenceServiceConfiguration config = persistenceServiceConfigs.get(serviceName); - if (config != null) { - for (PersistenceItemConfiguration itemConfig : config.getConfigs()) { - if (hasStrategy(config, itemConfig, PersistenceStrategy.Globals.RESTORE)) { - for (Item item : getAllItems(itemConfig)) { - initialize(item); - } - } - } - createTimers(serviceName, config.getStrategies()); - } - } - } - - private void stopEventHandling(String dbId) { - synchronized (persistenceServiceConfigs) { - removeTimers(dbId); - } - } - - private @Nullable PersistenceServiceConfiguration getDefaultConfig(PersistenceService persistenceService) { - List strategies = persistenceService.getDefaultStrategies(); - List configs = List - .of(new PersistenceItemConfiguration(List.of(new PersistenceAllConfig()), null, strategies, null)); - return new PersistenceServiceConfiguration(configs, strategies, strategies); - } - - @Override - public void allItemsChanged(Collection oldItemNames) { - for (Item item : itemRegistry.getItems()) { - added(item); - } - } - - @Override - public void added(Item item) { - initialize(item); - if (item instanceof GenericItem genericItem) { - genericItem.addStateChangeListener(this); - } - } - - @Override - public void removed(Item item) { - if (item instanceof GenericItem genericItem) { - genericItem.removeStateChangeListener(this); - } - } - - @Override - public void updated(Item oldItem, Item item) { - removed(oldItem); - added(item); - } - - @Override - public void stateChanged(Item item, State oldState, State newState) { - handleStateEvent(item, true); - } - - @Override - public void stateUpdated(Item item, State state) { - handleStateEvent(item, false); - } - - @Override - public void onReadyMarkerAdded(ReadyMarker readyMarker) { - ExecutorService scheduler = Executors.newSingleThreadExecutor(new NamedThreadFactory("persistenceManager")); - scheduler.submit(() -> { - allItemsChanged(Collections.emptySet()); - for (String dbId : persistenceServices.keySet()) { - startEventHandling(dbId); - } - started = true; - readyService.markReady(marker); - itemRegistry.addRegistryChangeListener(this); - }); - scheduler.shutdown(); - } - - @Override - public void onReadyMarkerRemoved(ReadyMarker readyMarker) { - readyService.unmarkReady(marker); - } -} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceServiceConfigurationRegistryImpl.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceServiceConfigurationRegistryImpl.java new file mode 100644 index 00000000000..69e4cc151fa --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceServiceConfigurationRegistryImpl.java @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.internal; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.common.registry.AbstractRegistry; +import org.openhab.core.common.registry.Provider; +import org.openhab.core.persistence.registry.ManagedPersistenceServiceConfigurationProvider; +import org.openhab.core.persistence.registry.PersistenceServiceConfiguration; +import org.openhab.core.persistence.registry.PersistenceServiceConfigurationProvider; +import org.openhab.core.persistence.registry.PersistenceServiceConfigurationRegistry; +import org.openhab.core.persistence.registry.PersistenceServiceConfigurationRegistryChangeListener; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PersistenceServiceConfigurationRegistryImpl} implements the + * {@link PersistenceServiceConfigurationRegistry} + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +@Component(immediate = true, service = PersistenceServiceConfigurationRegistry.class) +public class PersistenceServiceConfigurationRegistryImpl + extends AbstractRegistry + implements PersistenceServiceConfigurationRegistry { + private final Logger logger = LoggerFactory.getLogger(PersistenceServiceConfigurationRegistryImpl.class); + private final Map> serviceToProvider = new HashMap<>(); + private final Set registryChangeListeners = new CopyOnWriteArraySet<>(); + + public PersistenceServiceConfigurationRegistryImpl() { + super(PersistenceServiceConfigurationProvider.class); + } + + @Override + public void added(Provider provider, PersistenceServiceConfiguration element) { + if (serviceToProvider.containsKey(element.getUID())) { + logger.warn("Tried to add strategy container with serviceId '{}', but it was already added before.", + element.getUID()); + } else { + super.added(provider, element); + } + } + + @Override + public void removed(Provider provider, PersistenceServiceConfiguration element) { + if (!provider.equals(serviceToProvider.getOrDefault(element.getUID(), provider))) { + logger.warn("Tried to remove strategy container with serviceId '{}', but it was added by another provider.", + element.getUID()); + } else { + super.removed(provider, element); + } + } + + @Override + public void updated(Provider provider, PersistenceServiceConfiguration oldelement, + PersistenceServiceConfiguration element) { + if (!provider.equals(serviceToProvider.getOrDefault(element.getUID(), provider))) { + logger.warn("Tried to update strategy container with serviceId '{}', but it was added by another provider.", + element.getUID()); + } else { + super.updated(provider, oldelement, element); + } + } + + protected void notifyListenersAboutAddedElement(PersistenceServiceConfiguration element) { + registryChangeListeners.forEach(listener -> listener.added(element)); + super.notifyListenersAboutAddedElement(element); + } + + protected void notifyListenersAboutRemovedElement(PersistenceServiceConfiguration element) { + registryChangeListeners.forEach(listener -> listener.removed(element)); + super.notifyListenersAboutRemovedElement(element); + } + + protected void notifyListenersAboutUpdatedElement(PersistenceServiceConfiguration oldElement, + PersistenceServiceConfiguration element) { + registryChangeListeners.forEach(listener -> listener.updated(oldElement, element)); + } + + @Override + public void addRegistryChangeListener(PersistenceServiceConfigurationRegistryChangeListener listener) { + registryChangeListeners.add(listener); + } + + @Override + public void removeRegistryChangeListener(PersistenceServiceConfigurationRegistryChangeListener listener) { + registryChangeListeners.remove(listener); + } + + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) + protected void setManagedProvider(ManagedPersistenceServiceConfigurationProvider provider) { + super.setManagedProvider(provider); + } + + protected void unsetManagedProvider(ManagedPersistenceServiceConfigurationProvider provider) { + super.unsetManagedProvider(provider); + } +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/ManagedPersistenceServiceConfigurationProvider.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/ManagedPersistenceServiceConfigurationProvider.java new file mode 100644 index 00000000000..4bbd84d9626 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/ManagedPersistenceServiceConfigurationProvider.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.registry; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.common.registry.AbstractManagedProvider; +import org.openhab.core.persistence.dto.PersistenceServiceConfigurationDTO; +import org.openhab.core.storage.StorageService; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link ManagedPersistenceServiceConfigurationProvider} implements a + * {@link PersistenceServiceConfigurationProvider} for managed configurations which are stored in a JSON database + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +@Component(immediate = true, service = { PersistenceServiceConfigurationProvider.class, + ManagedPersistenceServiceConfigurationProvider.class }) +public class ManagedPersistenceServiceConfigurationProvider + extends AbstractManagedProvider + implements PersistenceServiceConfigurationProvider { + private static final String STORAGE_NAME = "org.openhab.core.persistence.PersistenceServiceConfiguration"; + + @Activate + public ManagedPersistenceServiceConfigurationProvider(@Reference StorageService storageService) { + super(storageService); + } + + @Override + protected String getStorageName() { + return STORAGE_NAME; + } + + @Override + protected String keyToString(String key) { + return key; + } + + @Override + protected @Nullable PersistenceServiceConfiguration toElement(String key, + PersistenceServiceConfigurationDTO persistableElement) { + return PersistenceServiceConfigurationDTOMapper.map(persistableElement); + } + + @Override + protected PersistenceServiceConfigurationDTO toPersistableElement(PersistenceServiceConfiguration element) { + return PersistenceServiceConfigurationDTOMapper.map(element); + } +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceServiceConfiguration.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfiguration.java similarity index 51% rename from bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceServiceConfiguration.java rename to bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfiguration.java index b31930ad896..214d584a83d 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceServiceConfiguration.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfiguration.java @@ -10,32 +10,43 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.persistence; +package org.openhab.core.persistence.registry; import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.common.registry.Identifiable; +import org.openhab.core.persistence.PersistenceItemConfiguration; +import org.openhab.core.persistence.filter.PersistenceFilter; import org.openhab.core.persistence.strategy.PersistenceStrategy; /** - * This class represents the configuration for a persistence service. + * The {@link PersistenceServiceConfiguration} represents the configuration for a persistence service. * - * @author Markus Rathgeb - Initial contribution + * @author Jan N. Klug - Initial contribution */ @NonNullByDefault -public class PersistenceServiceConfiguration { +public class PersistenceServiceConfiguration implements Identifiable { + private final String serviceId; private final List configs; private final List defaults; private final List strategies; + private final List filters; - public PersistenceServiceConfiguration(final Collection configs, - final Collection defaults, final Collection strategies) { - this.configs = Collections.unmodifiableList(new LinkedList<>(configs)); - this.defaults = Collections.unmodifiableList(new LinkedList<>(defaults)); - this.strategies = Collections.unmodifiableList(new LinkedList<>(strategies)); + public PersistenceServiceConfiguration(String serviceId, Collection configs, + Collection defaults, Collection strategies, + Collection filters) { + this.serviceId = serviceId; + this.configs = List.copyOf(configs); + this.defaults = List.copyOf(defaults); + this.strategies = List.copyOf(strategies); + this.filters = List.copyOf(filters); + } + + @Override + public String getUID() { + return serviceId; } /** @@ -64,4 +75,13 @@ public List getDefaults() { public List getStrategies() { return strategies; } + + /** + * Get all defined filters. + * + * @return an unmodifiable list of the defined filters + */ + public List getFilters() { + return filters; + } } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationDTOMapper.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationDTOMapper.java new file mode 100644 index 00000000000..2b9b513ed18 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationDTOMapper.java @@ -0,0 +1,166 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.registry; + +import static org.openhab.core.persistence.strategy.PersistenceStrategy.Globals.STRATEGIES; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.persistence.PersistenceItemConfiguration; +import org.openhab.core.persistence.config.PersistenceAllConfig; +import org.openhab.core.persistence.config.PersistenceConfig; +import org.openhab.core.persistence.config.PersistenceGroupConfig; +import org.openhab.core.persistence.config.PersistenceItemConfig; +import org.openhab.core.persistence.dto.PersistenceCronStrategyDTO; +import org.openhab.core.persistence.dto.PersistenceFilterDTO; +import org.openhab.core.persistence.dto.PersistenceItemConfigurationDTO; +import org.openhab.core.persistence.dto.PersistenceServiceConfigurationDTO; +import org.openhab.core.persistence.filter.PersistenceFilter; +import org.openhab.core.persistence.filter.PersistenceThresholdFilter; +import org.openhab.core.persistence.filter.PersistenceTimeFilter; +import org.openhab.core.persistence.strategy.PersistenceCronStrategy; +import org.openhab.core.persistence.strategy.PersistenceStrategy; + +/** + * The {@link PersistenceServiceConfigurationDTOMapper} is a utility class to map persistence configurations for storage + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class PersistenceServiceConfigurationDTOMapper { + + private PersistenceServiceConfigurationDTOMapper() { + // prevent initialization + } + + public static PersistenceServiceConfigurationDTO map( + PersistenceServiceConfiguration persistenceServiceConfiguration) { + PersistenceServiceConfigurationDTO dto = new PersistenceServiceConfigurationDTO(); + dto.serviceId = persistenceServiceConfiguration.getUID(); + dto.configs = persistenceServiceConfiguration.getConfigs().stream() + .map(PersistenceServiceConfigurationDTOMapper::mapPersistenceItemConfig).toList(); + dto.defaults = persistenceServiceConfiguration.getDefaults().stream().map(PersistenceStrategy::getName) + .toList(); + dto.cronStrategies = filterList(persistenceServiceConfiguration.getStrategies(), PersistenceCronStrategy.class, + PersistenceServiceConfigurationDTOMapper::mapPersistenceCronStrategy); + dto.thresholdFilters = filterList(persistenceServiceConfiguration.getFilters(), + PersistenceThresholdFilter.class, + PersistenceServiceConfigurationDTOMapper::mapPersistenceThresholdFilter); + dto.timeFilters = filterList(persistenceServiceConfiguration.getFilters(), PersistenceTimeFilter.class, + PersistenceServiceConfigurationDTOMapper::mapPersistenceTimeFilter); + + return dto; + } + + public static PersistenceServiceConfiguration map(PersistenceServiceConfigurationDTO dto) { + Map strategyMap = dto.cronStrategies.stream() + .collect(Collectors.toMap(e -> e.name, e -> new PersistenceCronStrategy(e.name, e.cronExpression))); + + Map filterMap = Stream + .concat(dto.thresholdFilters.stream().map(f -> new PersistenceThresholdFilter(f.name, f.value, f.unit)), + dto.timeFilters.stream() + .map(f -> new PersistenceTimeFilter(f.name, f.value.intValue(), f.unit))) + .collect(Collectors.toMap(PersistenceFilter::getName, e -> e)); + + List defaults = dto.defaults.stream() + .map(str -> stringToPersistenceStrategy(str, strategyMap, dto.serviceId)).toList(); + + List configs = dto.configs.stream().map(config -> { + List items = config.items.stream() + .map(PersistenceServiceConfigurationDTOMapper::stringToPersistenceConfig).toList(); + List strategies = config.strategies.stream() + .map(str -> stringToPersistenceStrategy(str, strategyMap, dto.serviceId)).toList(); + return new PersistenceItemConfiguration(items, config.alias, strategies, List.of()); + }).toList(); + + return new PersistenceServiceConfiguration(dto.serviceId, configs, defaults, strategyMap.values(), + filterMap.values()); + } + + private static Collection filterList(Collection list, Class clazz, Function mapper) { + return list.stream().filter(clazz::isInstance).map(clazz::cast).map(mapper).toList(); + } + + private static PersistenceConfig stringToPersistenceConfig(String string) { + if ("*".equals(string)) { + return new PersistenceAllConfig(); + } else if (string.endsWith("*")) { + return new PersistenceGroupConfig(string.substring(0, string.length() - 1)); + } else { + return new PersistenceItemConfig(string); + } + } + + private static PersistenceStrategy stringToPersistenceStrategy(String string, + Map strategyMap, String serviceId) { + PersistenceStrategy strategy = strategyMap.get(string); + if (strategy != null) { + return strategy; + } + strategy = STRATEGIES.get(string); + if (strategy != null) { + return strategy; + } + throw new IllegalArgumentException("Strategy '" + string + "' unknown for service '" + serviceId + "'"); + } + + private static String persistenceConfigToString(PersistenceConfig config) { + if (config instanceof PersistenceAllConfig) { + return "*"; + } else if (config instanceof PersistenceGroupConfig persistenceGroupConfig) { + return persistenceGroupConfig.getGroup() + "*"; + } else if (config instanceof PersistenceItemConfig persistenceItemConfig) { + return persistenceItemConfig.getItem(); + } + throw new IllegalArgumentException("Unknown persistence config class " + config.getClass()); + } + + private static PersistenceItemConfigurationDTO mapPersistenceItemConfig(PersistenceItemConfiguration config) { + PersistenceItemConfigurationDTO itemDto = new PersistenceItemConfigurationDTO(); + itemDto.items = config.items().stream().map(PersistenceServiceConfigurationDTOMapper::persistenceConfigToString) + .toList(); + itemDto.strategies = config.strategies().stream().map(PersistenceStrategy::getName).toList(); + itemDto.alias = config.alias(); + return itemDto; + } + + private static PersistenceCronStrategyDTO mapPersistenceCronStrategy(PersistenceCronStrategy cronStrategy) { + PersistenceCronStrategyDTO cronStrategyDTO = new PersistenceCronStrategyDTO(); + cronStrategyDTO.name = cronStrategy.getName(); + cronStrategyDTO.cronExpression = cronStrategy.getCronExpression(); + return cronStrategyDTO; + } + + private static PersistenceFilterDTO mapPersistenceThresholdFilter(PersistenceThresholdFilter thresholdFilter) { + PersistenceFilterDTO filterDTO = new PersistenceFilterDTO(); + filterDTO.name = thresholdFilter.getName(); + filterDTO.value = thresholdFilter.getValue(); + filterDTO.unit = thresholdFilter.getUnit(); + return filterDTO; + } + + private static PersistenceFilterDTO mapPersistenceTimeFilter(PersistenceTimeFilter persistenceTimeFilter) { + PersistenceFilterDTO filterDTO = new PersistenceFilterDTO(); + filterDTO.name = persistenceTimeFilter.getName(); + filterDTO.value = new BigDecimal(persistenceTimeFilter.getValue()); + filterDTO.unit = persistenceTimeFilter.getUnit(); + return filterDTO; + } +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationProvider.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationProvider.java new file mode 100644 index 00000000000..942097f0900 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationProvider.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.registry; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.common.registry.Provider; + +/** + * The {@link PersistenceServiceConfigurationProvider} is an interface for persistence service configuration providers + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public interface PersistenceServiceConfigurationProvider extends Provider { +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationRegistry.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationRegistry.java new file mode 100644 index 00000000000..5435f5b8559 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationRegistry.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.registry; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.common.registry.Registry; + +/** + * The {@link PersistenceServiceConfigurationRegistry} is the central place to store persistence service configurations. + * Configurations are registered through {@link PersistenceServiceConfigurationProvider}. + * Because the {@link org.openhab.core.persistence.internal.PersistenceManager} implementation needs to listen to + * different registries, the {@link PersistenceServiceConfigurationRegistryChangeListener} can be used to add listeners + * to this registry. + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public interface PersistenceServiceConfigurationRegistry extends Registry { + void addRegistryChangeListener(PersistenceServiceConfigurationRegistryChangeListener listener); + + void removeRegistryChangeListener(PersistenceServiceConfigurationRegistryChangeListener listener); +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationRegistryChangeListener.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationRegistryChangeListener.java new file mode 100644 index 00000000000..4acf4d6adf1 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationRegistryChangeListener.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.registry; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PersistenceServiceConfigurationRegistryChangeListener} is an interface that can be implemented by services + * that need to listen to the {@link PersistenceServiceConfigurationRegistry} when more than one registry with different + * types is used. + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public interface PersistenceServiceConfigurationRegistryChangeListener { + /** + * Notifies the listener that a single element has been added. + * + * @param element the element that has been added + */ + void added(PersistenceServiceConfiguration element); + + /** + * Notifies the listener that a single element has been removed. + * + * @param element the element that has been removed + */ + void removed(PersistenceServiceConfiguration element); + + /** + * Notifies the listener that a single element has been updated. + * + * @param element the new element + * @param oldElement the element that has been updated + */ + void updated(PersistenceServiceConfiguration oldElement, PersistenceServiceConfiguration element); +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/strategy/PersistenceStrategy.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/strategy/PersistenceStrategy.java index 375f43e4024..4cbf6ca659a 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/strategy/PersistenceStrategy.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/strategy/PersistenceStrategy.java @@ -12,6 +12,7 @@ */ package org.openhab.core.persistence.strategy; +import java.util.Map; import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -28,6 +29,9 @@ public static class Globals { public static final PersistenceStrategy UPDATE = new PersistenceStrategy("everyUpdate"); public static final PersistenceStrategy CHANGE = new PersistenceStrategy("everyChange"); public static final PersistenceStrategy RESTORE = new PersistenceStrategy("restoreOnStartup"); + + public static final Map STRATEGIES = Map.of(UPDATE.name, UPDATE, CHANGE.name, + CHANGE, RESTORE.name, RESTORE); } private final String name; @@ -44,7 +48,7 @@ public String getName() { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + name.hashCode(); return result; } @@ -56,14 +60,10 @@ public boolean equals(final @Nullable Object obj) { if (obj == null) { return false; } - if (!(obj instanceof PersistenceStrategy)) { - return false; - } - final PersistenceStrategy other = (PersistenceStrategy) obj; - if (!Objects.equals(name, other.name)) { + if (!(obj instanceof final PersistenceStrategy other)) { return false; } - return true; + return Objects.equals(name, other.name); } @Override diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestPersistenceService.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestPersistenceService.java index 419e1aec34a..f497a542559 100644 --- a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestPersistenceService.java +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestPersistenceService.java @@ -19,6 +19,7 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.Set; import javax.measure.Unit; @@ -97,7 +98,7 @@ public State getState() { @Override public String getName() { - return filter.getItemName(); + return Objects.requireNonNull(filter.getItemName()); } }); } @@ -132,14 +133,14 @@ public ZonedDateTime getTimestamp() { @Override public State getState() { - Item item = itemRegistry.get(filter.getItemName()); + Item item = itemRegistry.get(Objects.requireNonNull(filter.getItemName())); Unit unit = item instanceof NumberItem ni ? ni.getUnit() : null; return unit == null ? new DecimalType(year) : QuantityType.valueOf(year, unit); } @Override public String getName() { - return filter.getItemName(); + return Objects.requireNonNull(filter.getItemName()); } }); } diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceThresholdFilterTest.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceThresholdFilterTest.java new file mode 100644 index 00000000000..bcefefb6cfd --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceThresholdFilterTest.java @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.filter; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import java.math.BigDecimal; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.openhab.core.library.items.NumberItem; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.types.State; +import org.openhab.core.types.util.UnitUtils; + +/** + * The {@link PersistenceThresholdFilterTest} contains tests for {@link PersistenceThresholdFilter} + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class PersistenceThresholdFilterTest { + private static final String ITEM_NAME_1 = "itemName1"; + private static final String ITEM_NAME_2 = "itemName2"; + + @Test + public void differentItemSameValue() { + filterTest(ITEM_NAME_2, DecimalType.ZERO, DecimalType.ZERO, "", true); + } + + @ParameterizedTest + @MethodSource("argumentProvider") + public void filterTest(State state1, State state2, String unit, boolean expected) { + filterTest(ITEM_NAME_1, state1, state2, unit, expected); + } + + private static Stream argumentProvider() { + return Stream.of(// + // same item, same value -> false + Arguments.of(DecimalType.ZERO, DecimalType.ZERO, "", false), + // plain decimal, below threshold, absolute + Arguments.of(DecimalType.ZERO, DecimalType.valueOf("5"), "", false), + // plain decimal, above threshold, absolute + Arguments.of(DecimalType.ZERO, DecimalType.valueOf("15"), "", true), + // plain decimal, below threshold, relative + Arguments.of(DecimalType.valueOf("10.0"), DecimalType.valueOf("9.5"), "%", false), + // plain decimal, above threshold, relative + Arguments.of(DecimalType.valueOf("10.0"), DecimalType.valueOf("11.5"), "%", true), + // quantity type, below threshold, relative + Arguments.of(new QuantityType<>("15 A"), new QuantityType<>("14000 mA"), "%", false), + // quantity type, above threshold, relative + Arguments.of(new QuantityType<>("2000 mbar"), new QuantityType<>("2.6 bar"), "%", true), + // quantity type, below threshold, absolute, no unit + Arguments.of(new QuantityType<>("100 K"), new QuantityType<>("105 K"), "", false), + // quantity type, above threshold, absolute, no unit + Arguments.of(new QuantityType<>("20 V"), new QuantityType<>("9000 mV"), "", true), + // quantity type, below threshold, absolute, with unit + Arguments.of(new QuantityType<>("10 m"), new QuantityType<>("10.002 m"), "mm", false), + // quantity type, above threshold, absolute, with unit + Arguments.of(new QuantityType<>("-10 °C"), new QuantityType<>("5 °C"), "K", true)); + } + + private void filterTest(String item2name, State state1, State state2, String unit, boolean expected) { + String itemType = "Number"; + if (state1 instanceof QuantityType q) { + itemType += ":" + UnitUtils.getDimensionName(q.getUnit()); + } + + NumberItem item1 = new NumberItem(itemType, PersistenceThresholdFilterTest.ITEM_NAME_1); + NumberItem item2 = new NumberItem(itemType, item2name); + + item1.setState(state1); + item2.setState(state2); + + PersistenceFilter filter = new PersistenceThresholdFilter("test", BigDecimal.TEN, unit); + + assertThat(filter.apply(item1), is(true)); + filter.persisted(item1); + assertThat(filter.apply(item2), is(expected)); + } +} diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceTimeFilterTest.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceTimeFilterTest.java new file mode 100644 index 00000000000..38b2879e729 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceTimeFilterTest.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.filter; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.core.library.items.StringItem; + +/** + * The {@link PersistenceTimeFilterTest} contains tests for {@link PersistenceTimeFilter} + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class PersistenceTimeFilterTest { + + @Test + public void testTimeFilter() throws InterruptedException { + PersistenceFilter filter = new PersistenceTimeFilter("test", 1, "s"); + + StringItem item = new StringItem("testItem"); + assertThat(filter.apply(item), is(true)); + filter.persisted(item); + + // immediate store returns false + assertThat(filter.apply(item), is(false)); + + // after interval returns true + Thread.sleep(1500); + assertThat(filter.apply(item), is(true)); + } +} diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/internal/PersistenceManagerTest.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/internal/PersistenceManagerTest.java new file mode 100644 index 00000000000..9172dd3e83e --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/internal/PersistenceManagerTest.java @@ -0,0 +1,405 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.internal; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.math.BigDecimal; +import java.time.ZonedDateTime; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.openhab.core.common.SafeCaller; +import org.openhab.core.common.SafeCallerBuilder; +import org.openhab.core.items.GroupItem; +import org.openhab.core.items.ItemNotFoundException; +import org.openhab.core.items.ItemRegistry; +import org.openhab.core.library.items.NumberItem; +import org.openhab.core.library.items.StringItem; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.persistence.HistoricItem; +import org.openhab.core.persistence.PersistenceItemConfiguration; +import org.openhab.core.persistence.PersistenceService; +import org.openhab.core.persistence.QueryablePersistenceService; +import org.openhab.core.persistence.config.PersistenceAllConfig; +import org.openhab.core.persistence.config.PersistenceConfig; +import org.openhab.core.persistence.config.PersistenceGroupConfig; +import org.openhab.core.persistence.config.PersistenceItemConfig; +import org.openhab.core.persistence.filter.PersistenceFilter; +import org.openhab.core.persistence.filter.PersistenceThresholdFilter; +import org.openhab.core.persistence.registry.PersistenceServiceConfiguration; +import org.openhab.core.persistence.registry.PersistenceServiceConfigurationRegistry; +import org.openhab.core.persistence.strategy.PersistenceCronStrategy; +import org.openhab.core.persistence.strategy.PersistenceStrategy; +import org.openhab.core.scheduler.CronScheduler; +import org.openhab.core.scheduler.ScheduledCompletableFuture; +import org.openhab.core.scheduler.SchedulerRunnable; +import org.openhab.core.service.ReadyMarker; +import org.openhab.core.service.ReadyService; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link PersistenceManagerTest} contains tests for the {@link PersistenceManager} + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class PersistenceManagerTest { + private static final String TEST_ITEM_NAME = "testItem"; + private static final String TEST_ITEM2_NAME = "testItem2"; + private static final String TEST_ITEM3_NAME = "testItem3"; + private static final String TEST_GROUP_ITEM_NAME = "groupItem"; + + private static final StringItem TEST_ITEM = new StringItem(TEST_ITEM_NAME); + private static final StringItem TEST_ITEM2 = new StringItem(TEST_ITEM2_NAME); + private static final NumberItem TEST_ITEM3 = new NumberItem(TEST_ITEM3_NAME); + private static final GroupItem TEST_GROUP_ITEM = new GroupItem(TEST_GROUP_ITEM_NAME); + + private static final State TEST_STATE = new StringType("testState1"); + + private static final HistoricItem TEST_HISTORIC_ITEM = new HistoricItem() { + @Override + public ZonedDateTime getTimestamp() { + return ZonedDateTime.now().minusDays(1); + } + + @Override + public State getState() { + return TEST_STATE; + } + + @Override + public String getName() { + return TEST_ITEM_NAME; + } + }; + + private static final String TEST_PERSISTENCE_SERVICE_ID = "testPersistenceService"; + private static final String TEST_QUERYABLE_PERSISTENCE_SERVICE_ID = "testQueryablePersistenceService"; + + private @NonNullByDefault({}) @Mock CronScheduler cronSchedulerMock; + private @NonNullByDefault({}) @Mock ScheduledCompletableFuture scheduledFutureMock; + private @NonNullByDefault({}) @Mock ItemRegistry itemRegistryMock; + private @NonNullByDefault({}) @Mock SafeCaller safeCallerMock; + private @NonNullByDefault({}) @Mock SafeCallerBuilder safeCallerBuilderMock; + private @NonNullByDefault({}) @Mock ReadyService readyServiceMock; + private @NonNullByDefault({}) @Mock PersistenceServiceConfigurationRegistry persistenceServiceConfigurationRegistryMock; + + private @NonNullByDefault({}) @Mock PersistenceService persistenceServiceMock; + private @NonNullByDefault({}) @Mock QueryablePersistenceService queryablePersistenceServiceMock; + + private @NonNullByDefault({}) PersistenceManager manager; + + @BeforeEach + public void setUp() throws ItemNotFoundException { + TEST_GROUP_ITEM.addMember(TEST_ITEM); + + // set initial states + TEST_ITEM.setState(UnDefType.NULL); + TEST_ITEM2.setState(UnDefType.NULL); + TEST_ITEM3.setState(DecimalType.ZERO); + TEST_GROUP_ITEM.setState(UnDefType.NULL); + + when(itemRegistryMock.getItem(TEST_GROUP_ITEM_NAME)).thenReturn(TEST_GROUP_ITEM); + when(itemRegistryMock.getItem(TEST_ITEM_NAME)).thenReturn(TEST_ITEM); + when(itemRegistryMock.getItem(TEST_ITEM2_NAME)).thenReturn(TEST_ITEM2); + when(itemRegistryMock.getItem(TEST_ITEM3_NAME)).thenReturn(TEST_ITEM3); + when(itemRegistryMock.getItems()).thenReturn(List.of(TEST_ITEM, TEST_ITEM2, TEST_ITEM3, TEST_GROUP_ITEM)); + when(persistenceServiceMock.getId()).thenReturn(TEST_PERSISTENCE_SERVICE_ID); + when(queryablePersistenceServiceMock.getId()).thenReturn(TEST_QUERYABLE_PERSISTENCE_SERVICE_ID); + when(queryablePersistenceServiceMock.query(any())).thenReturn(List.of(TEST_HISTORIC_ITEM)); + + manager = new PersistenceManager(cronSchedulerMock, itemRegistryMock, safeCallerMock, readyServiceMock, + persistenceServiceConfigurationRegistryMock); + manager.addPersistenceService(persistenceServiceMock); + manager.addPersistenceService(queryablePersistenceServiceMock); + + clearInvocations(persistenceServiceMock, queryablePersistenceServiceMock); + } + + @Test + public void appliesToItemWithItemConfig() { + addConfiguration(TEST_PERSISTENCE_SERVICE_ID, new PersistenceItemConfig(TEST_ITEM_NAME), + PersistenceStrategy.Globals.UPDATE, null); + + manager.stateUpdated(TEST_ITEM, TEST_STATE); + + verify(persistenceServiceMock).store(TEST_ITEM, null); + verifyNoMoreInteractions(persistenceServiceMock); + } + + @Test + public void doesNotApplyToItemWithItemConfig() { + addConfiguration(TEST_PERSISTENCE_SERVICE_ID, new PersistenceItemConfig(TEST_ITEM_NAME), + PersistenceStrategy.Globals.UPDATE, null); + + manager.stateUpdated(TEST_ITEM2, TEST_STATE); + + verifyNoMoreInteractions(persistenceServiceMock); + } + + @Test + public void appliesToGroupItemWithItemConfig() { + addConfiguration(TEST_PERSISTENCE_SERVICE_ID, new PersistenceItemConfig(TEST_GROUP_ITEM_NAME), + PersistenceStrategy.Globals.UPDATE, null); + + manager.stateUpdated(TEST_GROUP_ITEM, TEST_STATE); + + verify(persistenceServiceMock).store(TEST_GROUP_ITEM, null); + verifyNoMoreInteractions(persistenceServiceMock); + } + + @Test + public void appliesToItemWithGroupConfig() { + addConfiguration(TEST_PERSISTENCE_SERVICE_ID, new PersistenceGroupConfig(TEST_GROUP_ITEM_NAME), + PersistenceStrategy.Globals.UPDATE, null); + + manager.stateUpdated(TEST_ITEM, TEST_STATE); + + verify(persistenceServiceMock).store(TEST_ITEM, null); + verifyNoMoreInteractions(persistenceServiceMock); + } + + @Test + public void doesNotApplyToItemWithGroupConfig() { + addConfiguration(TEST_PERSISTENCE_SERVICE_ID, new PersistenceGroupConfig(TEST_GROUP_ITEM_NAME), + PersistenceStrategy.Globals.UPDATE, null); + + manager.stateUpdated(TEST_ITEM2, TEST_STATE); + manager.stateUpdated(TEST_GROUP_ITEM, TEST_STATE); + + verifyNoMoreInteractions(persistenceServiceMock); + } + + @Test + public void appliesToItemWithAllConfig() { + addConfiguration(TEST_PERSISTENCE_SERVICE_ID, new PersistenceAllConfig(), PersistenceStrategy.Globals.UPDATE, + null); + + manager.stateUpdated(TEST_ITEM, TEST_STATE); + manager.stateUpdated(TEST_ITEM2, TEST_STATE); + manager.stateUpdated(TEST_GROUP_ITEM, TEST_STATE); + + verify(persistenceServiceMock).store(TEST_ITEM, null); + verify(persistenceServiceMock).store(TEST_ITEM2, null); + verify(persistenceServiceMock).store(TEST_GROUP_ITEM, null); + + verifyNoMoreInteractions(persistenceServiceMock); + } + + @Test + public void updatedStatePersistsEveryUpdate() { + addConfiguration(TEST_PERSISTENCE_SERVICE_ID, new PersistenceAllConfig(), PersistenceStrategy.Globals.UPDATE, + null); + + manager.stateUpdated(TEST_ITEM, TEST_STATE); + manager.stateUpdated(TEST_ITEM, TEST_STATE); + + verify(persistenceServiceMock, times(2)).store(TEST_ITEM, null); + + verifyNoMoreInteractions(persistenceServiceMock); + } + + @Test + public void updatedStateDoesNotPersistWithChangeStrategy() { + addConfiguration(TEST_PERSISTENCE_SERVICE_ID, new PersistenceAllConfig(), PersistenceStrategy.Globals.CHANGE, + null); + + manager.stateUpdated(TEST_ITEM, TEST_STATE); + verifyNoMoreInteractions(persistenceServiceMock); + } + + @Test + public void changedStatePersistsWithChangeStrategy() { + addConfiguration(TEST_PERSISTENCE_SERVICE_ID, new PersistenceAllConfig(), PersistenceStrategy.Globals.CHANGE, + null); + + manager.stateChanged(TEST_ITEM, UnDefType.UNDEF, TEST_STATE); + + verify(persistenceServiceMock).store(TEST_ITEM, null); + verifyNoMoreInteractions(persistenceServiceMock); + } + + @Test + public void changedStateDoesNotPersistWithUpdateStrategy() { + addConfiguration(TEST_PERSISTENCE_SERVICE_ID, new PersistenceAllConfig(), PersistenceStrategy.Globals.UPDATE, + null); + + manager.stateChanged(TEST_ITEM, UnDefType.UNDEF, TEST_STATE); + + verifyNoMoreInteractions(persistenceServiceMock); + } + + @Test + public void restoreOnStartupWhenItemNull() { + setupPersistence(new PersistenceAllConfig()); + + manager.onReadyMarkerAdded(new ReadyMarker("", "")); + verify(readyServiceMock, timeout(1000)).markReady(any()); + + assertThat(TEST_ITEM2.getState(), is(TEST_STATE)); + assertThat(TEST_ITEM.getState(), is(TEST_STATE)); + assertThat(TEST_GROUP_ITEM.getState(), is(TEST_STATE)); + + verify(queryablePersistenceServiceMock, times(3)).query(any()); + + verifyNoMoreInteractions(queryablePersistenceServiceMock); + verifyNoMoreInteractions(persistenceServiceMock); + } + + @Test + public void noRestoreOnStartupWhenItemNotNull() { + setupPersistence(new PersistenceAllConfig()); + + // set TEST_ITEM state to a value + StringType initialValue = new StringType("value"); + TEST_ITEM.setState(initialValue); + + manager.onReadyMarkerAdded(new ReadyMarker("", "")); + verify(readyServiceMock, timeout(1000)).markReady(any()); + + assertThat(TEST_ITEM.getState(), is(initialValue)); + assertThat(TEST_ITEM2.getState(), is(TEST_STATE)); + assertThat(TEST_GROUP_ITEM.getState(), is(TEST_STATE)); + + verify(queryablePersistenceServiceMock, times(2)).query(any()); + + verifyNoMoreInteractions(queryablePersistenceServiceMock); + verifyNoMoreInteractions(persistenceServiceMock); + } + + @Test + public void cronStrategyIsScheduledAndCancelledAndPersistsValue() throws Exception { + ArgumentCaptor runnableCaptor = ArgumentCaptor.forClass(SchedulerRunnable.class); + when(cronSchedulerMock.schedule(runnableCaptor.capture(), any())).thenReturn(scheduledFutureMock); + + addConfiguration(TEST_PERSISTENCE_SERVICE_ID, new PersistenceItemConfig(TEST_ITEM3_NAME), + new PersistenceCronStrategy("withoutFilter", "0 0 * * * ?"), null); + addConfiguration(TEST_QUERYABLE_PERSISTENCE_SERVICE_ID, new PersistenceItemConfig(TEST_ITEM3_NAME), + new PersistenceCronStrategy("withFilter", "0 * * * * ?"), + new PersistenceThresholdFilter("test", BigDecimal.TEN, "")); + + manager.onReadyMarkerAdded(new ReadyMarker("", "")); + + verify(readyServiceMock, timeout(1000)).markReady(any()); + List runnables = runnableCaptor.getAllValues(); + assertThat(runnables.size(), is(2)); + runnables.get(0).run(); + runnables.get(0).run(); + runnables.get(1).run(); + runnables.get(1).run(); + + manager.deactivate(); + + verify(cronSchedulerMock, times(2)).schedule(any(), any()); + verify(scheduledFutureMock, times(2)).cancel(true); + // no filter - persist everything + verify(persistenceServiceMock, times(2)).store(TEST_ITEM3, null); + // filter - persist filtered value + verify(queryablePersistenceServiceMock, times(1)).store(TEST_ITEM3, null); + } + + @Test + public void cronStrategyIsProperlyUpdated() { + when(cronSchedulerMock.schedule(any(), any())).thenReturn(scheduledFutureMock); + + PersistenceServiceConfiguration configuration = addConfiguration(TEST_PERSISTENCE_SERVICE_ID, + new PersistenceItemConfig(TEST_ITEM_NAME), new PersistenceCronStrategy("everyHour", "0 0 * * * ?"), + null); + + manager.onReadyMarkerAdded(new ReadyMarker("", "")); + + verify(readyServiceMock, timeout(1000)).markReady(any()); + + manager.updated(configuration, configuration); + manager.deactivate(); + + verify(cronSchedulerMock, times(2)).schedule(any(), any()); + verify(scheduledFutureMock, times(2)).cancel(true); + } + + @Test + public void filterAppliesOnStateUpdate() { + addConfiguration(TEST_PERSISTENCE_SERVICE_ID, new PersistenceAllConfig(), PersistenceStrategy.Globals.UPDATE, + new PersistenceThresholdFilter("test", BigDecimal.TEN, "")); + + manager.stateUpdated(TEST_ITEM3, DecimalType.ZERO); + manager.stateUpdated(TEST_ITEM3, DecimalType.ZERO); + + verify(persistenceServiceMock, times(1)).store(TEST_ITEM3, null); + + verifyNoMoreInteractions(persistenceServiceMock); + } + + /** + * Add a configuration for restoring TEST_ITEM and mock the SafeCaller + */ + private void setupPersistence(PersistenceConfig itemConfig) { + addConfiguration(TEST_PERSISTENCE_SERVICE_ID, itemConfig, PersistenceStrategy.Globals.RESTORE, null); + addConfiguration(TEST_QUERYABLE_PERSISTENCE_SERVICE_ID, itemConfig, PersistenceStrategy.Globals.RESTORE, null); + + when(safeCallerMock.create(queryablePersistenceServiceMock, QueryablePersistenceService.class)) + .thenReturn(safeCallerBuilderMock); + when(safeCallerBuilderMock.onTimeout(any())).thenReturn(safeCallerBuilderMock); + when(safeCallerBuilderMock.onException(any())).thenReturn(safeCallerBuilderMock); + when(safeCallerBuilderMock.build()).thenReturn(queryablePersistenceServiceMock); + } + + /** + * Add a configuration to the manager + * + * @param serviceId the persistence service id + * @param itemConfig the item configuration + * @param strategy the strategy + * @param filter a persistence filter + * @return the added strategy + */ + private PersistenceServiceConfiguration addConfiguration(String serviceId, PersistenceConfig itemConfig, + PersistenceStrategy strategy, @Nullable PersistenceFilter filter) { + List filters = filter != null ? List.of(filter) : List.of(); + + PersistenceItemConfiguration itemConfiguration = new PersistenceItemConfiguration(List.of(itemConfig), null, + List.of(strategy), filters); + + List strategies = PersistenceStrategy.Globals.STRATEGIES.containsValue(strategy) + ? List.of() + : List.of(strategy); + + PersistenceServiceConfiguration serviceConfiguration = new PersistenceServiceConfiguration(serviceId, + List.of(itemConfiguration), List.of(), strategies, filters); + manager.added(serviceConfiguration); + + return serviceConfiguration; + } +} From 960cf0c1796a793ff6a83891d95d405c67d9a1c5 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 7 May 2023 20:46:42 +0200 Subject: [PATCH 066/126] Improve thing updates (#3576) * Improve thing updates Signed-off-by: Jan N. Klug --- .../OH-INF/i18n/validation.properties | 5 +- .../OH-INF/i18n/validation_cs.properties | 3 - .../OH-INF/i18n/validation_de.properties | 3 - .../OH-INF/i18n/validation_he.properties | 3 - .../OH-INF/i18n/validation_hu.properties | 3 - .../OH-INF/i18n/validation_it.properties | 3 - .../OH-INF/i18n/validation_no.properties | 3 - .../OH-INF/i18n/validation_pl.properties | 3 - .../OH-INF/i18n/validation_sl.properties | 3 - .../thing/internal/ThingFactoryHelper.java | 4 +- .../core/thing/internal/ThingManagerImpl.java | 28 ++-- .../update/ThingUpdateInstructionReader.java | 85 +----------- .../ThingUpdateInstructionReaderImpl.java | 122 ++++++++++++++++++ .../update/UpdateChannelInstructionImpl.java | 45 ++++++- .../thing/internal/ThingManagerImplTest.java | 6 +- .../internal/update/ThingUpdateOSGiTest.java | 36 +++--- 16 files changed, 207 insertions(+), 148 deletions(-) create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/update/ThingUpdateInstructionReaderImpl.java diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation.properties index aff65cbe021..07d06aa9e2f 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation.properties @@ -13,6 +13,5 @@ options_violated=The value {0} does not match allowed parameter options. Allowed multiple_limit_violated=Only {0} elements are allowed but {1} are provided. bridge_not_configured=Configuring a bridge is mandatory. -type_description_missing=Type description for '{0}' not found also we checked the presence before. -config_description_missing=Config description for '{0}' not found also we checked the presence before. - +type_description_missing=Type description {0} for {1} not found, although we checked the presence before. +config_description_missing=Config description {0} for {1} not found, although we checked the presence before. diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_cs.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_cs.properties index 7b9fb8c12fc..7306d12294b 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_cs.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_cs.properties @@ -13,6 +13,3 @@ options_violated=Hodnota {0} neodpovídá parametru povolených možností. Povo multiple_limit_violated=Pouze {0} prvků je povoleno, ale {1} je k dispozici. bridge_not_configured=Konfigurace mostu (bridge) je povinná. -type_description_missing=Popis typu pro ''{0}'' nebyl nalezen, také jsme zkontrolovali předchozí. -config_description_missing=Popis konfigurace pro ''{0}'' nebyl nalezen, také jsme zkontrolovali předchozí. - diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_de.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_de.properties index 093bbb739b0..0321c872143 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_de.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_de.properties @@ -13,6 +13,3 @@ options_violated=Der Wert {0} ist nicht in den erlaubten Optionen enthalten. Erl multiple_limit_violated=Nur {0} Optionen sind zulässig, aber {1} wurden übergeben. bridge_not_configured=Es wird eine Bridge benötigt. -type_description_missing=Typbeschreibung für ''{0}'' nicht gefunden, auch das Vorhandensein wurde vorher überprüft. -config_description_missing=Konfigurationsbeschreibung für ''{0}'' nicht gefunden, auch das Vorhandensein wurde vorher überprüft. - diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_he.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_he.properties index 0047c2555db..41f4b6ef97d 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_he.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_he.properties @@ -13,6 +13,3 @@ options_violated=הערך {0} אינו תואם לאפשרויות הפרמטר multiple_limit_violated=רק {0} אלמנטים מותרים אך הוזנו {1}. bridge_not_configured=הגדרת מגשר היא חובה. -type_description_missing=סוג תיאור עבור ''{0}'' לא נמצא גם בדקנו את הנוכחות בעבר. -config_description_missing=תיאור התצורה של ''{0}'' לא נמצא גם בדקנו את הנוכחות בעבר. - diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_hu.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_hu.properties index 13308502ae6..3d2bdf1cc5c 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_hu.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_hu.properties @@ -13,6 +13,3 @@ options_violated=A(z) {0} érték nem egyezik a megengedett paraméter-beállít multiple_limit_violated=Csak {0} elem engedélyezett, de {1} szerepel. bridge_not_configured=A híd beállítása kötelező. -type_description_missing=A {0} típus leírása nem található még az előzményekben sem. -config_description_missing=A {0} beállítás leírása nem található még az előzményekben sem. - diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_it.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_it.properties index cbd65218bd9..c3e941a1a42 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_it.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_it.properties @@ -13,6 +13,3 @@ options_violated=Il valore {0} non è tra le opzioni consentite per il parametro multiple_limit_violated=Sono consentiti {0} elementi, ma ne sono stati forniti {1}. bridge_not_configured=Configurare un bridge è obbligatorio. -type_description_missing=Descrizione del tipo per ''{0}'' non trovato anche se abbiamo prima controllato la presenza. -config_description_missing=Descrizione della configurazione per ''{0}'' non trovato anche se abbiamo prima controllato la presenza. - diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_no.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_no.properties index 03cb5ef14d0..0d20d68d8b7 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_no.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_no.properties @@ -13,6 +13,3 @@ options_violated=Verdien {0} samsvarer ikke med tillatte parameteralternativer. multiple_limit_violated=Kun {0} elementer er tillatt mens {1} er angitt. bridge_not_configured=Konfigurasjon av en bro er obligatorisk. -type_description_missing=Typebeskivelsen for ''{0}'' ble ikke funnet, vi sjekket også tilstedeværelsen tidligere. -config_description_missing=Konfigurasjonsbeskrivelsen for ''{0}'' ble ikke funnet, vi sjekket også tilstedeværelsen tidligere. - diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_pl.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_pl.properties index 077b74545bd..5bc41d2c0de 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_pl.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_pl.properties @@ -13,6 +13,3 @@ options_violated=Wartość {0} nie pasuje do dozwolonych opcji parametrów. Dozw multiple_limit_violated=Tylko {0} elementy są dozwolone, ale {1} jest zapewniony. bridge_not_configured=Konfiguracja mostka jest obowiązkowa. -type_description_missing=Nie znaleziono opisu dla ''{0}'' również sprawdzono wcześniejsze opisy. -config_description_missing=Nie znaleziono opisu konfiguracji dla ''{0}'' również sprawdzono wcześniejsze opisy. - diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_sl.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_sl.properties index 6e81dbf2794..c7d6d05b96f 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_sl.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_sl.properties @@ -13,6 +13,3 @@ options_violated=Vrednost {0} se ne ujema z dovoljenimim izborom parametrov. Dov multiple_limit_violated=Dovoljenih je le {0} elementov, podanih pa je {1}. bridge_not_configured=Konfiguriranje mostu je obvezno. -type_description_missing=Opis vrste za ''{0}'' ni bil najden. Obstoj smo preverili tudi že prej. -config_description_missing=Opis konfiguracije za ''{0}'' ni bil najden. Obstoj smo preverili tudi že prej. - diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingFactoryHelper.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingFactoryHelper.java index eb3194fcd55..3322f845f0b 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingFactoryHelper.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingFactoryHelper.java @@ -144,7 +144,7 @@ private static Channel createChannel(ChannelDefinition channelDefinition, ThingU return channelBuilder.withProperties(channelDefinition.getProperties()).build(); } - static ChannelBuilder createChannelBuilder(ChannelUID channelUID, ChannelType channelType, + public static ChannelBuilder createChannelBuilder(ChannelUID channelUID, ChannelType channelType, ConfigDescriptionRegistry configDescriptionRegistry) { final ChannelBuilder channelBuilder = ChannelBuilder.create(channelUID, channelType.getItemType()) // .withType(channelType.getUID()) // @@ -168,7 +168,7 @@ static ChannelBuilder createChannelBuilder(ChannelUID channelUID, ChannelType ch return channelBuilder; } - static ChannelBuilder createChannelBuilder(ChannelUID channelUID, ChannelDefinition channelDefinition, + public static ChannelBuilder createChannelBuilder(ChannelUID channelUID, ChannelDefinition channelDefinition, ConfigDescriptionRegistry configDescriptionRegistry) { ChannelType channelType = withChannelTypeRegistry(channelTypeRegistry -> (channelTypeRegistry != null) ? channelTypeRegistry.getChannelType(channelDefinition.getChannelTypeUID()) diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingManagerImpl.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingManagerImpl.java index 32a6b2a93b0..380e3cd998c 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingManagerImpl.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingManagerImpl.java @@ -186,8 +186,10 @@ public ThingManagerImpl( // final @Reference StorageService storageService, // final @Reference ThingRegistry thingRegistry, final @Reference ThingStatusInfoI18nLocalizationService thingStatusInfoI18nLocalizationService, - final @Reference ThingTypeRegistry thingTypeRegistry, final @Reference BundleResolver bundleResolver, - final @Reference TranslationProvider translationProvider, final BundleContext bundleContext) { + final @Reference ThingTypeRegistry thingTypeRegistry, + final @Reference ThingUpdateInstructionReader thingUpdateInstructionReader, + final @Reference BundleResolver bundleResolver, final @Reference TranslationProvider translationProvider, + final BundleContext bundleContext) { this.channelGroupTypeRegistry = channelGroupTypeRegistry; this.channelTypeRegistry = channelTypeRegistry; this.communicationManager = communicationManager; @@ -198,7 +200,7 @@ public ThingManagerImpl( // this.readyService = readyService; this.safeCaller = safeCaller; this.thingRegistry = (ThingRegistryImpl) thingRegistry; - this.thingUpdateInstructionReader = new ThingUpdateInstructionReader(bundleResolver); + this.thingUpdateInstructionReader = thingUpdateInstructionReader; this.thingStatusInfoI18nLocalizationService = thingStatusInfoI18nLocalizationService; this.thingTypeRegistry = thingTypeRegistry; this.translationProvider = translationProvider; @@ -404,13 +406,13 @@ public void thingUpdated(Thing oldThing, Thing newThing, ThingTrackerEvent thing try { normalizeThingConfiguration(oldThing); } catch (ConfigValidationException e) { - logger.warn("Failed to normalize configuration for thing '{}': {}", oldThing.getUID(), + logger.debug("Failed to normalize configuration for old thing during update '{}': {}", oldThing.getUID(), e.getValidationMessages(null)); } try { normalizeThingConfiguration(newThing); } catch (ConfigValidationException e) { - logger.warn("Failed to normalize configuration´for thing '{}': {}", newThing.getUID(), + logger.warn("Failed to normalize configuration for new thing during update '{}': {}", newThing.getUID(), e.getValidationMessages(null)); } if (thingUpdatedLock.contains(thingUID)) { @@ -661,23 +663,23 @@ private void normalizeThingConfiguration(Thing thing) throws ConfigValidationExc thing.getUID()); return; } - normalizeConfiguration(thingType, thing.getUID(), thing.getConfiguration()); + normalizeConfiguration(thingType, thing.getThingTypeUID(), thing.getUID(), thing.getConfiguration()); for (Channel channel : thing.getChannels()) { ChannelTypeUID channelTypeUID = channel.getChannelTypeUID(); if (channelTypeUID != null) { ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeUID); - normalizeConfiguration(channelType, channel.getUID(), channel.getConfiguration()); + normalizeConfiguration(channelType, channelTypeUID, channel.getUID(), channel.getConfiguration()); } } } - private void normalizeConfiguration(@Nullable AbstractDescriptionType prototype, UID targetUID, + private void normalizeConfiguration(@Nullable AbstractDescriptionType prototype, UID prototypeUID, UID targetUID, Configuration configuration) throws ConfigValidationException { if (prototype == null) { ConfigValidationMessage message = new ConfigValidationMessage("thing/channel", - "Type description for '{0}' not found although we checked the presence before.", - "type_description_missing", targetUID.toString()); + "Type description {0} for {1} not found, although we checked the presence before.", + "type_description_missing", prototypeUID.toString(), targetUID.toString()); throw new ConfigValidationException(bundleContext.getBundle(), translationProvider, List.of(message)); } @@ -691,8 +693,8 @@ private void normalizeConfiguration(@Nullable AbstractDescriptionType prototype, ConfigDescription configDescription = configDescriptionRegistry.getConfigDescription(configDescriptionURI); if (configDescription == null) { ConfigValidationMessage message = new ConfigValidationMessage("thing/channel", - "Config description for '{0}' not found also we checked the presence before.", - "config_description_missing", targetUID); + "Config description {0} for {1} not found, although we checked the presence before.", + "config_description_missing", configDescriptionURI.toString(), targetUID.toString()); throw new ConfigValidationException(bundleContext.getBundle(), translationProvider, List.of(message)); } @@ -1262,7 +1264,7 @@ public synchronized boolean isReady() { timesChecked++; if (timesChecked > MAX_CHECK_PREREQUISITE_TIME / CHECK_INTERVAL) { logger.warn( - "Channel types or config descriptions for thing '{}' are missing in the respective registry for more than {}s. This should be fixed in the binding.", + "Channel types or config descriptions for thing '{}' are missing in the respective registry for more than {}s. In case it does not happen immediately after an upgrade, it should be fixed in the binding.", thingUID, MAX_CHECK_PREREQUISITE_TIME); channelTypeUIDs.clear(); configDescriptionUris.clear(); diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/update/ThingUpdateInstructionReader.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/update/ThingUpdateInstructionReader.java index 0ebe8949e6b..a56f104399b 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/update/ThingUpdateInstructionReader.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/update/ThingUpdateInstructionReader.java @@ -12,100 +12,23 @@ */ package org.openhab.core.thing.internal.update; -import java.net.URL; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Enumeration; -import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.binding.ThingHandlerFactory; -import org.openhab.core.thing.internal.update.dto.XmlAddChannel; -import org.openhab.core.thing.internal.update.dto.XmlInstructionSet; -import org.openhab.core.thing.internal.update.dto.XmlRemoveChannel; -import org.openhab.core.thing.internal.update.dto.XmlThingType; -import org.openhab.core.thing.internal.update.dto.XmlUpdateChannel; -import org.openhab.core.thing.internal.update.dto.XmlUpdateDescriptions; -import org.openhab.core.util.BundleResolver; -import org.osgi.framework.Bundle; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * The {@link ThingUpdateInstructionReader} is used to read instructions for a given {@link ThingHandlerFactory} and - * * create a list of {@link ThingUpdateInstruction}s + * create a list of {@link ThingUpdateInstruction}s * * @author Jan N. Klug - Initial contribution */ @NonNullByDefault -public class ThingUpdateInstructionReader { - private final Logger logger = LoggerFactory.getLogger(ThingUpdateInstructionReader.class); - private final BundleResolver bundleResolver; - - public ThingUpdateInstructionReader(BundleResolver bundleResolver) { - this.bundleResolver = bundleResolver; - } - - public Map> readForFactory(ThingHandlerFactory factory) { - Bundle bundle = bundleResolver.resolveBundle(factory.getClass()); - if (bundle == null) { - logger.error( - "Could not get bundle for '{}', thing type updates will fail. If this occurs outside of tests, it is a bug.", - factory.getClass()); - return Map.of(); - } - - Map> updateInstructions = new HashMap<>(); - Enumeration entries = bundle.findEntries("OH-INF/update", "*.xml", true); - if (entries != null) { - while (entries.hasMoreElements()) { - URL url = entries.nextElement(); - try { - JAXBContext context = JAXBContext.newInstance(XmlUpdateDescriptions.class); - Unmarshaller u = context.createUnmarshaller(); - XmlUpdateDescriptions updateDescriptions = (XmlUpdateDescriptions) u.unmarshal(url); - - for (XmlThingType thingType : updateDescriptions.getThingType()) { - ThingTypeUID thingTypeUID = new ThingTypeUID(thingType.getUid()); - UpdateInstructionKey key = new UpdateInstructionKey(factory, thingTypeUID); - List instructions = new ArrayList<>(); - List instructionSets = thingType.getInstructionSet().stream() - .sorted(Comparator.comparing(XmlInstructionSet::getTargetVersion)).toList(); - for (XmlInstructionSet instructionSet : instructionSets) { - int targetVersion = instructionSet.getTargetVersion(); - for (Object instruction : instructionSet.getInstructions()) { - if (instruction instanceof XmlAddChannel addChannelType) { - instructions.add(new UpdateChannelInstructionImpl(targetVersion, addChannelType)); - } else if (instruction instanceof XmlUpdateChannel updateChannelType) { - instructions - .add(new UpdateChannelInstructionImpl(targetVersion, updateChannelType)); - } else if (instruction instanceof XmlRemoveChannel removeChannelType) { - instructions - .add(new RemoveChannelInstructionImpl(targetVersion, removeChannelType)); - } else { - logger.warn("Instruction type '{}' is unknown.", instruction.getClass()); - } - } - } - updateInstructions.put(key, instructions); - } - logger.trace("Reading update instructions from '{}'", url.getPath()); - } catch (IllegalArgumentException | JAXBException e) { - logger.warn("Failed to parse update instructions from '{}':", url, e); - } - } - } - - return updateInstructions; - } +public interface ThingUpdateInstructionReader { + Map> readForFactory(ThingHandlerFactory factory); - public record UpdateInstructionKey(ThingHandlerFactory factory, ThingTypeUID thingTypeId) { + record UpdateInstructionKey(ThingHandlerFactory factory, ThingTypeUID thingTypeId) { } } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/update/ThingUpdateInstructionReaderImpl.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/update/ThingUpdateInstructionReaderImpl.java new file mode 100644 index 00000000000..8cb809bd527 --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/update/ThingUpdateInstructionReaderImpl.java @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.internal.update; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.config.core.ConfigDescriptionRegistry; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.openhab.core.thing.internal.update.dto.XmlAddChannel; +import org.openhab.core.thing.internal.update.dto.XmlInstructionSet; +import org.openhab.core.thing.internal.update.dto.XmlRemoveChannel; +import org.openhab.core.thing.internal.update.dto.XmlThingType; +import org.openhab.core.thing.internal.update.dto.XmlUpdateChannel; +import org.openhab.core.thing.internal.update.dto.XmlUpdateDescriptions; +import org.openhab.core.thing.type.ChannelTypeRegistry; +import org.openhab.core.util.BundleResolver; +import org.osgi.framework.Bundle; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link ThingUpdateInstructionReaderImpl} is an implementation of {@link ThingUpdateInstructionReader} + * + * @author Jan N. Klug - Initial contribution + */ +@Component(service = ThingUpdateInstructionReader.class) +@NonNullByDefault +public class ThingUpdateInstructionReaderImpl implements ThingUpdateInstructionReader { + private final Logger logger = LoggerFactory.getLogger(ThingUpdateInstructionReaderImpl.class); + private final BundleResolver bundleResolver; + private final ChannelTypeRegistry channelTypeRegistry; + private final ConfigDescriptionRegistry configDescriptionRegistry; + + @Activate + public ThingUpdateInstructionReaderImpl(@Reference BundleResolver bundleResolver, + @Reference ChannelTypeRegistry channelTypeRegistry, + @Reference ConfigDescriptionRegistry configDescriptionRegistry) { + this.bundleResolver = bundleResolver; + this.channelTypeRegistry = channelTypeRegistry; + this.configDescriptionRegistry = configDescriptionRegistry; + } + + @Override + public Map> readForFactory(ThingHandlerFactory factory) { + Bundle bundle = bundleResolver.resolveBundle(factory.getClass()); + if (bundle == null) { + logger.error( + "Could not get bundle for '{}', thing type updates will fail. If this occurs outside of tests, it is a bug.", + factory.getClass()); + return Map.of(); + } + + Map> updateInstructions = new HashMap<>(); + Enumeration entries = bundle.findEntries("OH-INF/update", "*.xml", true); + if (entries != null) { + while (entries.hasMoreElements()) { + URL url = entries.nextElement(); + try { + JAXBContext context = JAXBContext.newInstance(XmlUpdateDescriptions.class); + Unmarshaller u = context.createUnmarshaller(); + XmlUpdateDescriptions updateDescriptions = (XmlUpdateDescriptions) u.unmarshal(url); + + for (XmlThingType thingType : updateDescriptions.getThingType()) { + ThingTypeUID thingTypeUID = new ThingTypeUID(thingType.getUid()); + UpdateInstructionKey key = new UpdateInstructionKey(factory, thingTypeUID); + List instructions = new ArrayList<>(); + List instructionSets = thingType.getInstructionSet().stream() + .sorted(Comparator.comparing(XmlInstructionSet::getTargetVersion)).toList(); + for (XmlInstructionSet instructionSet : instructionSets) { + int targetVersion = instructionSet.getTargetVersion(); + for (Object instruction : instructionSet.getInstructions()) { + if (instruction instanceof XmlAddChannel addChannelType) { + instructions.add(new UpdateChannelInstructionImpl(targetVersion, addChannelType, + channelTypeRegistry, configDescriptionRegistry)); + } else if (instruction instanceof XmlUpdateChannel updateChannelType) { + instructions.add(new UpdateChannelInstructionImpl(targetVersion, updateChannelType, + channelTypeRegistry, configDescriptionRegistry)); + } else if (instruction instanceof XmlRemoveChannel removeChannelType) { + instructions + .add(new RemoveChannelInstructionImpl(targetVersion, removeChannelType)); + } else { + logger.warn("Instruction type '{}' is unknown.", instruction.getClass()); + } + } + } + updateInstructions.put(key, instructions); + } + logger.trace("Reading update instructions from '{}'", url.getPath()); + } catch (IllegalArgumentException | JAXBException e) { + logger.warn("Failed to parse update instructions from '{}':", url, e); + } + } + } + + return updateInstructions; + } +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/update/UpdateChannelInstructionImpl.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/update/UpdateChannelInstructionImpl.java index 7433ad9f0f3..88c26aae26a 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/update/UpdateChannelInstructionImpl.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/update/UpdateChannelInstructionImpl.java @@ -19,14 +19,20 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.config.core.ConfigDescriptionRegistry; import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.binding.builder.ChannelBuilder; import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.thing.internal.ThingFactoryHelper; import org.openhab.core.thing.internal.update.dto.XmlAddChannel; import org.openhab.core.thing.internal.update.dto.XmlUpdateChannel; +import org.openhab.core.thing.type.ChannelType; +import org.openhab.core.thing.type.ChannelTypeRegistry; import org.openhab.core.thing.type.ChannelTypeUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The {@link UpdateChannelInstructionImpl} implements a {@link ThingUpdateInstruction} that updates a channel of a @@ -36,40 +42,49 @@ */ @NonNullByDefault public class UpdateChannelInstructionImpl implements ThingUpdateInstruction { + private final Logger logger = LoggerFactory.getLogger(UpdateChannelInstructionImpl.class); private final boolean removeOldChannel; private final int thingTypeVersion; private final boolean preserveConfig; private final List groupIds; private final String channelId; - private final String channelTypeUid; + private final ChannelTypeUID channelTypeUid; private final @Nullable String label; private final @Nullable String description; private final @Nullable List tags; + private final ChannelTypeRegistry channelTypeRegistry; + private final ConfigDescriptionRegistry configDescriptionRegistry; - UpdateChannelInstructionImpl(int thingTypeVersion, XmlUpdateChannel updateChannel) { + UpdateChannelInstructionImpl(int thingTypeVersion, XmlUpdateChannel updateChannel, + ChannelTypeRegistry channelTypeRegistry, ConfigDescriptionRegistry configDescriptionRegistry) { this.removeOldChannel = true; this.thingTypeVersion = thingTypeVersion; this.channelId = updateChannel.getId(); - this.channelTypeUid = updateChannel.getType(); + this.channelTypeUid = new ChannelTypeUID(updateChannel.getType()); String rawGroupIds = updateChannel.getGroupIds(); this.groupIds = rawGroupIds != null ? Arrays.asList(rawGroupIds.split(",")) : List.of(); this.label = updateChannel.getLabel(); this.description = updateChannel.getDescription(); this.tags = updateChannel.getTags(); this.preserveConfig = updateChannel.isPreserveConfiguration(); + this.channelTypeRegistry = channelTypeRegistry; + this.configDescriptionRegistry = configDescriptionRegistry; } - UpdateChannelInstructionImpl(int thingTypeVersion, XmlAddChannel addChannel) { + UpdateChannelInstructionImpl(int thingTypeVersion, XmlAddChannel addChannel, + ChannelTypeRegistry channelTypeRegistry, ConfigDescriptionRegistry configDescriptionRegistry) { this.removeOldChannel = false; this.thingTypeVersion = thingTypeVersion; this.channelId = addChannel.getId(); - this.channelTypeUid = addChannel.getType(); + this.channelTypeUid = new ChannelTypeUID(addChannel.getType()); String rawGroupIds = addChannel.getGroupIds(); this.groupIds = rawGroupIds != null ? Arrays.asList(rawGroupIds.split(",")) : List.of(); this.label = addChannel.getLabel(); this.description = addChannel.getDescription(); this.tags = addChannel.getTags(); this.preserveConfig = false; + this.channelTypeRegistry = channelTypeRegistry; + this.configDescriptionRegistry = configDescriptionRegistry; } @Override @@ -79,6 +94,7 @@ public int getThingTypeVersion() { @Override public void perform(Thing thing, ThingBuilder thingBuilder) { + logger.debug("Applying {} to thing '{}'", this, thing.getUID()); if (groupIds.isEmpty()) { doChannel(thing, thingBuilder, new ChannelUID(thing.getUID(), channelId)); } else { @@ -92,8 +108,15 @@ private void doChannel(Thing thing, ThingBuilder thingBuilder, ChannelUID affect thingBuilder.withoutChannel(affectedChannelUid); } - ChannelBuilder channelBuilder = ChannelBuilder.create(affectedChannelUid) - .withType(new ChannelTypeUID(channelTypeUid)); + ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeUid); + if (channelType == null) { + logger.error("Failed to create channel '{}' because channel type '{}' could not be found.", + affectedChannelUid, channelTypeUid); + return; + } + + ChannelBuilder channelBuilder = ThingFactoryHelper.createChannelBuilder(affectedChannelUid, channelType, + configDescriptionRegistry); if (preserveConfig) { Channel oldChannel = thing.getChannel(affectedChannelUid); @@ -115,4 +138,12 @@ private void doChannel(Thing thing, ThingBuilder thingBuilder, ChannelUID affect thingBuilder.withChannel(channelBuilder.build()); } + + @Override + public String toString() { + return "UpdateChannelInstructionImpl{" + "removeOldChannel=" + removeOldChannel + ", thingTypeVersion=" + + thingTypeVersion + ", preserveConfig=" + preserveConfig + ", groupIds=" + groupIds + ", channelId='" + + channelId + "'" + ", channelTypeUid=" + channelTypeUid + ", label='" + label + "'" + ", description='" + + description + "'" + ", tags=" + tags + "}"; + } } diff --git a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/ThingManagerImplTest.java b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/ThingManagerImplTest.java index 861cd7728d1..bf635ce2c29 100644 --- a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/ThingManagerImplTest.java +++ b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/ThingManagerImplTest.java @@ -41,6 +41,7 @@ import org.openhab.core.thing.binding.ThingHandlerFactory; import org.openhab.core.thing.i18n.ThingStatusInfoI18nLocalizationService; import org.openhab.core.thing.internal.ThingTracker.ThingTrackerEvent; +import org.openhab.core.thing.internal.update.ThingUpdateInstructionReader; import org.openhab.core.thing.link.ItemChannelLinkRegistry; import org.openhab.core.thing.type.ChannelGroupTypeRegistry; import org.openhab.core.thing.type.ChannelTypeRegistry; @@ -72,6 +73,7 @@ public class ThingManagerImplTest extends JavaTest { private @Mock @NonNullByDefault({}) Thing thingMock; private @Mock @NonNullByDefault({}) ThingRegistryImpl thingRegistryMock; private @Mock @NonNullByDefault({}) BundleResolver bundleResolverMock; + private @Mock @NonNullByDefault({}) ThingUpdateInstructionReader thingUpdateInstructionReaderMock; private @Mock @NonNullByDefault({}) TranslationProvider translationProviderMock; private @Mock @NonNullByDefault({}) BundleContext bundleContextMock; private @Mock @NonNullByDefault({}) ThingType thingTypeMock; @@ -92,8 +94,8 @@ private ThingManagerImpl createThingManager() { return new ThingManagerImpl(channelGroupTypeRegistryMock, channelTypeRegistryMock, communicationManagerMock, configDescriptionRegistryMock, configDescriptionValidatorMock, eventPublisherMock, itemChannelLinkRegistryMock, readyServiceMock, safeCallerMock, storageServiceMock, thingRegistryMock, - thingStatusInfoI18nLocalizationService, thingTypeRegistryMock, bundleResolverMock, - translationProviderMock, bundleContextMock); + thingStatusInfoI18nLocalizationService, thingTypeRegistryMock, thingUpdateInstructionReaderMock, + bundleResolverMock, translationProviderMock, bundleContextMock); } @Test diff --git a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/update/ThingUpdateOSGiTest.java b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/update/ThingUpdateOSGiTest.java index 18a3f616d4b..b0c698c7812 100644 --- a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/update/ThingUpdateOSGiTest.java +++ b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/update/ThingUpdateOSGiTest.java @@ -69,7 +69,7 @@ import org.slf4j.LoggerFactory; /** - * Tests for {@link ThingUpdateInstructionReader} and {@link ThingUpdateInstruction} implementations. + * Tests for {@link ThingUpdateInstructionReaderImpl} and {@link ThingUpdateInstruction} implementations. * * @author Jan N. Klug - Initial contribution */ @@ -149,14 +149,14 @@ public void testSingleChannelAddition() { assertThat(updatedThing.getChannels(), hasSize(3)); Channel channel1 = updatedThing.getChannel("testChannel1"); - assertChannel(channel1, channelTypeUID, null, null); + assertChannel(channel1, channelTypeUID, "Switch", "typeLabel", null); Channel channel2 = updatedThing.getChannel("testChannel2"); - assertChannel(channel2, channelTypeUID, "Test Label", null); + assertChannel(channel2, channelTypeUID, "Switch", "Test Label", null); assertThat(channel2.getDefaultTags(), containsInAnyOrder(ADDED_TAGS)); Channel channel3 = updatedThing.getChannel("testChannel3"); - assertChannel(channel3, channelTypeUID, "Test Label", "Test Description"); + assertChannel(channel3, channelTypeUID, "Switch", "Test Label", "Test Description"); } @Test @@ -186,11 +186,11 @@ public void testSingleChannelUpdate() { assertThat(updatedThing.getChannels(), hasSize(2)); Channel channel1 = updatedThing.getChannel(channelUID1); - assertChannel(channel1, channelTypeNewUID, "New Test Label", null); + assertChannel(channel1, channelTypeNewUID, "Number", "New Test Label", null); assertThat(channel1.getConfiguration(), is(channelConfig)); Channel channel2 = updatedThing.getChannel(channelUID2); - assertChannel(channel2, channelTypeNewUID, null, null); + assertChannel(channel2, channelTypeNewUID, "Number", "typeLabel", null); assertThat(channel2.getConfiguration().getProperties(), is(anEmptyMap())); } @@ -235,10 +235,10 @@ public void testMultipleChannelUpdates() { assertThat(updatedThing.getChannels(), hasSize(2)); Channel channel1 = updatedThing.getChannel("testChannel1"); - assertChannel(channel1, channelTypeNewUID, "Test Label", null); + assertChannel(channel1, channelTypeNewUID, "Number", "Test Label", null); Channel channel2 = updatedThing.getChannel("testChannel2"); - assertChannel(channel2, channelTypeOldUID, "TestLabel", null); + assertChannel(channel2, channelTypeOldUID, "Switch", "TestLabel", null); } @Test @@ -253,8 +253,10 @@ public void testOnlyMatchingInstructionsUpdate() { ChannelUID channelUID1 = new ChannelUID(thingUID, "testChannel1"); Thing thing = ThingBuilder.create(MULTIPLE_CHANNEL_THING_TYPE_UID, thingUID) - .withChannel(ChannelBuilder.create(channelUID0).withType(channelTypeOldUID).build()) - .withChannel(ChannelBuilder.create(channelUID1).withType(channelTypeOldUID).build()) + .withChannel(ChannelBuilder.create(channelUID0).withType(channelTypeOldUID) + .withAcceptedItemType("Switch").build()) + .withChannel(ChannelBuilder.create(channelUID1).withType(channelTypeOldUID) + .withAcceptedItemType("Switch").build()) .withProperty(PROPERTY_THING_TYPE_VERSION, "2").build(); managedThingProvider.add(thing); @@ -263,7 +265,7 @@ public void testOnlyMatchingInstructionsUpdate() { assertThat(updatedThing.getChannels(), hasSize(1)); Channel channel1 = updatedThing.getChannel("testChannel1"); - assertChannel(channel1, channelTypeOldUID, null, null); + assertChannel(channel1, channelTypeOldUID, "Switch", null, null); } @Test @@ -283,11 +285,11 @@ public void testSingleChannelAdditionGroup() { List channels1 = updatedThing.getChannelsOfGroup("group1"); assertThat(channels1, hasSize(1)); - assertChannel(channels1.get(0), channelTypeUID, null, null); + assertChannel(channels1.get(0), channelTypeUID, "Switch", "typeLabel", null); List channels2 = updatedThing.getChannelsOfGroup("group2"); assertThat(channels2, hasSize(1)); - assertChannel(channels2.get(0), channelTypeUID, null, null); + assertChannel(channels2.get(0), channelTypeUID, "Switch", "typeLabel", null); } @Test @@ -328,10 +330,11 @@ private Thing assertThing(Thing oldThing, int expectedNewThingTypeVersion) { return updatedThing; } - private void assertChannel(@Nullable Channel channel, ChannelTypeUID channelTypeUID, @Nullable String label, - @Nullable String description) { + private void assertChannel(@Nullable Channel channel, ChannelTypeUID channelTypeUID, String expectedItemType, + @Nullable String label, @Nullable String description) { assertThat(channel, is(notNullValue())); assertThat(channel.getChannelTypeUID(), is(channelTypeUID)); + assertThat(channel.getAcceptedItemType(), is(expectedItemType)); if (label != null) { assertThat(channel.getLabel(), is(label)); } else { @@ -361,7 +364,8 @@ private void registerChannelTypes(ChannelTypeUID... channelTypeUIDs) { ChannelTypeRegistry channelTypeRegistry = mock(ChannelTypeRegistry.class); for (ChannelTypeUID channelTypeUID : channelTypeUIDs) { - ChannelType channelType = ChannelTypeBuilder.state(channelTypeUID, "label", "Number").build(); + String itemType = channelTypeUID.getId().contains("New") ? "Number" : "Switch"; + ChannelType channelType = ChannelTypeBuilder.state(channelTypeUID, "typeLabel", itemType).build(); when(channelTypeProvider.getChannelType(eq(channelTypeUID), nullable(Locale.class))) .thenReturn(channelType); when(channelTypeRegistry.getChannelType(eq(channelTypeUID))).thenReturn(channelType); From 8e1a2cfd0a848ff1e4e9da7d8b9d554dcf0774d0 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 7 May 2023 21:51:10 +0200 Subject: [PATCH 067/126] Add an AbstractStorageBasedTypeProvider (#3407) * Add an AbstractDynamicTypeProvider Signed-off-by: Jan N. Klug --- .../AbstractStorageBasedTypeProvider.java | 399 ++++++++++++++++++ .../AbstractStorageBasedTypeProviderTest.java | 186 ++++++++ ...tractStorageBasedTypeProviderOSGiTest.java | 228 ++++++++++ 3 files changed, 813 insertions(+) create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProvider.java create mode 100644 bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProviderTest.java create mode 100644 itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProviderOSGiTest.java diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProvider.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProvider.java new file mode 100644 index 00000000000..9142371a9ce --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProvider.java @@ -0,0 +1,399 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding; + +import java.net.URI; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.storage.Storage; +import org.openhab.core.storage.StorageService; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.internal.type.TriggerChannelTypeBuilderImpl; +import org.openhab.core.thing.type.AutoUpdatePolicy; +import org.openhab.core.thing.type.BridgeType; +import org.openhab.core.thing.type.ChannelDefinition; +import org.openhab.core.thing.type.ChannelDefinitionBuilder; +import org.openhab.core.thing.type.ChannelGroupDefinition; +import org.openhab.core.thing.type.ChannelGroupType; +import org.openhab.core.thing.type.ChannelGroupTypeBuilder; +import org.openhab.core.thing.type.ChannelGroupTypeProvider; +import org.openhab.core.thing.type.ChannelGroupTypeUID; +import org.openhab.core.thing.type.ChannelKind; +import org.openhab.core.thing.type.ChannelType; +import org.openhab.core.thing.type.ChannelTypeBuilder; +import org.openhab.core.thing.type.ChannelTypeProvider; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.thing.type.StateChannelTypeBuilder; +import org.openhab.core.thing.type.ThingType; +import org.openhab.core.thing.type.ThingTypeBuilder; +import org.openhab.core.types.CommandDescription; +import org.openhab.core.types.EventDescription; +import org.openhab.core.types.StateDescription; +import org.openhab.core.types.StateDescriptionFragment; +import org.openhab.core.types.StateDescriptionFragmentBuilder; + +/** + * The {@link AbstractStorageBasedTypeProvider} is the base class for the implementation of a {@link Storage} based + * {@link ThingTypeProvider}, {@link ChannelTypeProvider} and {@link ChannelGroupTypeProvider} + * + * It can be subclassed by bindings that create {@link ThingType}s and {@link ChannelType}s on-the-fly and need to + * persist those for future thing initializations + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public abstract class AbstractStorageBasedTypeProvider + implements ThingTypeProvider, ChannelTypeProvider, ChannelGroupTypeProvider { + + private final Storage thingTypeEntityStorage; + private final Storage channelTypeEntityStorage; + private final Storage channelGroupTypeEntityStorage; + + /** + * Instantiate a new storage based type provider. The subclass needs to be a + * {@link org.osgi.service.component.annotations.Component} and declare itself as {@link ThingTypeProvider} and/or + * {@link ChannelTypeProvider} and/or {@link ChannelGroupTypeProvider}. + * + * @param storageService a persistent {@link StorageService} + */ + public AbstractStorageBasedTypeProvider(StorageService storageService) { + String thingTypeStorageName = getClass().getName() + "-ThingType"; + String channelTypeStorageName = getClass().getName() + "-ChannelType"; + String channelGroupTypeStorageName = getClass().getName() + "-ChannelGroupType"; + ClassLoader classLoader = getClass().getClassLoader(); + thingTypeEntityStorage = storageService.getStorage(thingTypeStorageName, classLoader); + channelTypeEntityStorage = storageService.getStorage(channelTypeStorageName, classLoader); + channelGroupTypeEntityStorage = storageService.getStorage(channelGroupTypeStorageName, classLoader); + } + + /** + * Add or update a {@link ThingType} to the storage + * + * @param thingType the {@link ThingType} that needs to be stored + */ + public void putThingType(ThingType thingType) { + thingTypeEntityStorage.put(thingType.getUID().toString(), mapToEntity(thingType)); + } + + /** + * Remove a {@link ThingType} from the storage + * + * @param thingTypeUID the {@link ThingTypeUID} of the thing type + */ + public void removeThingType(ThingTypeUID thingTypeUID) { + thingTypeEntityStorage.remove(thingTypeUID.toString()); + } + + /** + * Add or update a {@link ChannelType} to the storage + * + * @param channelType the {@link ChannelType} that needs to be stored + */ + public void putChannelType(ChannelType channelType) { + channelTypeEntityStorage.put(channelType.getUID().toString(), mapToEntity(channelType)); + } + + /** + * Remove a {@link ChannelType} from the storage + * + * @param channelTypeUID the {@link ChannelTypeUID} of the channel type + */ + public void removeChannelType(ChannelTypeUID channelTypeUID) { + channelTypeEntityStorage.remove(channelTypeUID.toString()); + } + + /** + * Add or update a {@link ChannelGroupType} to the storage + * + * @param channelGroupType the {@link ChannelType} that needs to be stored + */ + public void putChannelGroupType(ChannelGroupType channelGroupType) { + channelGroupTypeEntityStorage.put(channelGroupType.getUID().toString(), mapToEntity(channelGroupType)); + } + + /** + * Remove a {@link ChannelGroupType} from the storage + * + * @param channelGroupTypeUID the {@link ChannelGroupTypeUID} of the channel type + */ + public void removeChannelGroupType(ChannelGroupTypeUID channelGroupTypeUID) { + channelGroupTypeEntityStorage.remove(channelGroupTypeUID.toString()); + } + + @Override + public Collection getThingTypes(@Nullable Locale locale) { + return thingTypeEntityStorage.stream().map(Map.Entry::getValue).filter(Objects::nonNull) + .map(Objects::requireNonNull).map(AbstractStorageBasedTypeProvider::mapFromEntity).toList(); + } + + @Override + public @Nullable ThingType getThingType(ThingTypeUID thingTypeUID, @Nullable Locale locale) { + ThingTypeEntity entity = thingTypeEntityStorage.get(thingTypeUID.toString()); + if (entity != null) { + return mapFromEntity(entity); + } else { + return null; + } + } + + @Override + public Collection getChannelTypes(@Nullable Locale locale) { + return channelTypeEntityStorage.stream().map(Map.Entry::getValue).filter(Objects::nonNull) + .map(Objects::requireNonNull).map(AbstractStorageBasedTypeProvider::mapFromEntity).toList(); + } + + @Override + public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) { + ChannelTypeEntity entity = channelTypeEntityStorage.get(channelTypeUID.toString()); + if (entity != null) { + return mapFromEntity(entity); + } else { + return null; + } + } + + @Override + public Collection getChannelGroupTypes(@Nullable Locale locale) { + return channelGroupTypeEntityStorage.stream().map(Map.Entry::getValue).filter(Objects::nonNull) + .map(Objects::requireNonNull).map(AbstractStorageBasedTypeProvider::mapFromEntity).toList(); + } + + @Override + public @Nullable ChannelGroupType getChannelGroupType(ChannelGroupTypeUID channelGroupTypeUID, + @Nullable Locale locale) { + ChannelGroupTypeEntity entity = channelGroupTypeEntityStorage.get(channelGroupTypeUID.toString()); + if (entity != null) { + return mapFromEntity(entity); + } else { + return null; + } + } + + static ThingTypeEntity mapToEntity(ThingType thingType) { + ThingTypeEntity entity = new ThingTypeEntity(); + entity.uid = thingType.getUID(); + entity.label = thingType.getLabel(); + entity.description = thingType.getDescription(); + + entity.supportedBridgeTypeRefs = thingType.getSupportedBridgeTypeUIDs(); + entity.configDescriptionUri = thingType.getConfigDescriptionURI(); + entity.category = thingType.getCategory(); + entity.channelGroupDefinitions = thingType.getChannelGroupDefinitions().stream() + .map(AbstractStorageBasedTypeProvider::mapToEntity).collect(Collectors.toList()); + entity.channelDefinitions = thingType.getChannelDefinitions().stream() + .map(AbstractStorageBasedTypeProvider::mapToEntity).toList(); + entity.representationProperty = thingType.getRepresentationProperty(); + entity.properties = thingType.getProperties(); + entity.isListed = thingType.isListed(); + entity.extensibleChannelTypeIds = thingType.getExtensibleChannelTypeIds(); + entity.isBridge = thingType instanceof BridgeType; + + return entity; + } + + static ChannelDefinitionEntity mapToEntity(ChannelDefinition channelDefinition) { + ChannelDefinitionEntity entity = new ChannelDefinitionEntity(); + entity.id = channelDefinition.getId(); + entity.uid = channelDefinition.getChannelTypeUID(); + entity.label = channelDefinition.getLabel(); + entity.description = channelDefinition.getDescription(); + entity.properties = channelDefinition.getProperties(); + entity.autoUpdatePolicy = channelDefinition.getAutoUpdatePolicy(); + return entity; + } + + static ChannelGroupDefinitionEntity mapToEntity(ChannelGroupDefinition channelGroupDefinition) { + ChannelGroupDefinitionEntity entity = new ChannelGroupDefinitionEntity(); + entity.id = channelGroupDefinition.getId(); + entity.typeUid = channelGroupDefinition.getTypeUID(); + entity.label = channelGroupDefinition.getLabel(); + entity.description = channelGroupDefinition.getDescription(); + return entity; + } + + static ChannelTypeEntity mapToEntity(ChannelType channelType) { + ChannelTypeEntity entity = new ChannelTypeEntity(); + entity.uid = channelType.getUID(); + entity.label = channelType.getLabel(); + entity.description = channelType.getDescription(); + entity.configDescriptionURI = channelType.getConfigDescriptionURI(); + entity.advanced = channelType.isAdvanced(); + entity.itemType = channelType.getItemType(); + entity.kind = channelType.getKind(); + entity.tags = channelType.getTags(); + entity.category = channelType.getCategory(); + StateDescription stateDescription = channelType.getState(); + entity.stateDescriptionFragment = stateDescription == null ? null + : StateDescriptionFragmentBuilder.create(stateDescription).build(); + entity.commandDescription = channelType.getCommandDescription(); + entity.event = channelType.getEvent(); + entity.autoUpdatePolicy = channelType.getAutoUpdatePolicy(); + return entity; + } + + static ChannelGroupTypeEntity mapToEntity(ChannelGroupType channelGroupType) { + ChannelGroupTypeEntity entity = new ChannelGroupTypeEntity(); + entity.uid = channelGroupType.getUID(); + entity.label = channelGroupType.getLabel(); + entity.description = channelGroupType.getDescription(); + entity.category = channelGroupType.getCategory(); + entity.channelDefinitions = channelGroupType.getChannelDefinitions().stream() + .map(AbstractStorageBasedTypeProvider::mapToEntity).toList(); + return entity; + } + + static ThingType mapFromEntity(ThingTypeEntity entity) { + ThingTypeBuilder builder = ThingTypeBuilder.instance(entity.uid, entity.label) + .withSupportedBridgeTypeUIDs(entity.supportedBridgeTypeRefs).withProperties(entity.properties) + .withChannelDefinitions(entity.channelDefinitions.stream() + .map(AbstractStorageBasedTypeProvider::mapFromEntity).toList()) + .withChannelGroupDefinitions(entity.channelGroupDefinitions.stream() + .map(AbstractStorageBasedTypeProvider::mapFromEntity).toList()) + .isListed(entity.isListed).withExtensibleChannelTypeIds(entity.extensibleChannelTypeIds); + if (entity.description != null) { + builder.withDescription(Objects.requireNonNull(entity.description)); + } + if (entity.category != null) { + builder.withCategory(Objects.requireNonNull(entity.category)); + } + if (entity.configDescriptionUri != null) { + builder.withConfigDescriptionURI(Objects.requireNonNull(entity.configDescriptionUri)); + } + if (entity.representationProperty != null) { + builder.withRepresentationProperty(Objects.requireNonNull(entity.representationProperty)); + } + return entity.isBridge ? builder.buildBridge() : builder.build(); + } + + static ChannelDefinition mapFromEntity(ChannelDefinitionEntity entity) { + return new ChannelDefinitionBuilder(entity.id, entity.uid).withLabel(entity.label) + .withDescription(entity.description).withProperties(entity.properties) + .withAutoUpdatePolicy(entity.autoUpdatePolicy).build(); + } + + static ChannelGroupDefinition mapFromEntity(ChannelGroupDefinitionEntity entity) { + return new ChannelGroupDefinition(entity.id, entity.typeUid, entity.label, entity.description); + } + + static ChannelType mapFromEntity(ChannelTypeEntity entity) { + ChannelTypeBuilder builder = (entity.kind == ChannelKind.STATE) + ? ChannelTypeBuilder.state(entity.uid, entity.label, Objects.requireNonNull(entity.itemType)) + : ChannelTypeBuilder.trigger(entity.uid, entity.label); + builder.isAdvanced(entity.advanced).withTags(entity.tags); + if (entity.description != null) { + builder.withDescription(Objects.requireNonNull(entity.description)); + } + if (entity.configDescriptionURI != null) { + builder.withConfigDescriptionURI(Objects.requireNonNull(entity.configDescriptionURI)); + } + if (entity.category != null) { + builder.withCategory(Objects.requireNonNull(entity.category)); + } + if (builder instanceof StateChannelTypeBuilder stateBuilder) { + if (entity.stateDescriptionFragment != null) { + stateBuilder.withStateDescriptionFragment(Objects.requireNonNull(entity.stateDescriptionFragment)); + } + if (entity.commandDescription != null) { + stateBuilder.withCommandDescription(Objects.requireNonNull(entity.commandDescription)); + } + if (entity.autoUpdatePolicy != null) { + stateBuilder.withAutoUpdatePolicy(Objects.requireNonNull(entity.autoUpdatePolicy)); + } + } + if (builder instanceof TriggerChannelTypeBuilderImpl triggerBuilder) { + if (entity.event != null) { + triggerBuilder.withEventDescription(Objects.requireNonNull(entity.event)); + } + } + return builder.build(); + } + + static ChannelGroupType mapFromEntity(ChannelGroupTypeEntity entity) { + ChannelGroupTypeBuilder builder = ChannelGroupTypeBuilder.instance(entity.uid, entity.label) + .withChannelDefinitions(entity.channelDefinitions.stream() + .map(AbstractStorageBasedTypeProvider::mapFromEntity).toList()); + if (entity.description != null) { + builder.withDescription(Objects.requireNonNull(entity.description)); + } + if (entity.category != null) { + builder.withCategory(Objects.requireNonNull(entity.category)); + } + return builder.build(); + } + + static class ThingTypeEntity { + public @NonNullByDefault({}) ThingTypeUID uid; + public @NonNullByDefault({}) String label; + public @Nullable String description; + public @Nullable String category; + public @Nullable String representationProperty; + public List supportedBridgeTypeRefs = List.of(); + public @Nullable URI configDescriptionUri; + public List extensibleChannelTypeIds = List.of(); + public List channelGroupDefinitions = List.of(); + public List channelDefinitions = List.of(); + public Map properties = Map.of(); + public boolean isListed = false; + public boolean isBridge = false; + } + + static class ChannelDefinitionEntity { + public @NonNullByDefault({}) String id; + public @NonNullByDefault({}) ChannelTypeUID uid; + public @Nullable String label; + public @Nullable String description; + public Map properties = Map.of(); + public @Nullable AutoUpdatePolicy autoUpdatePolicy; + } + + static class ChannelGroupDefinitionEntity { + public @NonNullByDefault({}) String id; + public @NonNullByDefault({}) ChannelGroupTypeUID typeUid; + public @Nullable String label; + public @Nullable String description; + } + + static class ChannelTypeEntity { + public @NonNullByDefault({}) ChannelTypeUID uid; + public @NonNullByDefault({}) String label; + public @Nullable String description; + public @Nullable URI configDescriptionURI; + + public boolean advanced; + public @Nullable String itemType; + public @NonNullByDefault({}) ChannelKind kind; + public Set tags = Set.of(); + public @Nullable String category; + public @Nullable StateDescriptionFragment stateDescriptionFragment; + public @Nullable CommandDescription commandDescription; + public @Nullable EventDescription event; + public @Nullable AutoUpdatePolicy autoUpdatePolicy; + } + + static class ChannelGroupTypeEntity { + public @NonNullByDefault({}) ChannelGroupTypeUID uid; + public @NonNullByDefault({}) String label; + public @Nullable String description; + + public List channelDefinitions = List.of(); + private @Nullable String category; + } +} diff --git a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProviderTest.java b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProviderTest.java new file mode 100644 index 00000000000..3030111e732 --- /dev/null +++ b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProviderTest.java @@ -0,0 +1,186 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.type.AutoUpdatePolicy; +import org.openhab.core.thing.type.ChannelDefinition; +import org.openhab.core.thing.type.ChannelDefinitionBuilder; +import org.openhab.core.thing.type.ChannelGroupDefinition; +import org.openhab.core.thing.type.ChannelGroupType; +import org.openhab.core.thing.type.ChannelGroupTypeBuilder; +import org.openhab.core.thing.type.ChannelGroupTypeUID; +import org.openhab.core.thing.type.ChannelType; +import org.openhab.core.thing.type.ChannelTypeBuilder; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.thing.type.ThingType; +import org.openhab.core.thing.type.ThingTypeBuilder; +import org.openhab.core.types.CommandDescriptionBuilder; +import org.openhab.core.types.StateDescriptionFragmentBuilder; + +/** + * The {@link AbstractStorageBasedTypeProviderTest} contains tests for the static mapping-methods + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class AbstractStorageBasedTypeProviderTest { + + @Test + public void testStateChannelTypeProperlyMappedToEntityAndBack() { + ChannelTypeUID channelTypeUID = new ChannelTypeUID("TestBinding:testChannelType"); + + ChannelType expected = ChannelTypeBuilder.state(channelTypeUID, "testLabel", "Switch") + .withDescription("testDescription").withCategory("testCategory") + .withConfigDescriptionURI(URI.create("testBinding:testConfig")) + .withAutoUpdatePolicy(AutoUpdatePolicy.VETO).isAdvanced(true).withTag("testTag") + .withCommandDescription(CommandDescriptionBuilder.create().build()) + .withStateDescriptionFragment(StateDescriptionFragmentBuilder.create().build()).build(); + AbstractStorageBasedTypeProvider.ChannelTypeEntity entity = AbstractStorageBasedTypeProvider + .mapToEntity(expected); + ChannelType actual = AbstractStorageBasedTypeProvider.mapFromEntity(entity); + + assertThat(actual.getUID(), is(expected.getUID())); + assertThat(actual.getKind(), is(expected.getKind())); + assertThat(actual.getLabel(), is(expected.getLabel())); + assertThat(actual.getDescription(), is(expected.getDescription())); + assertThat(actual.getConfigDescriptionURI(), is(expected.getConfigDescriptionURI())); + assertThat(actual.isAdvanced(), is(expected.isAdvanced())); + assertThat(actual.getAutoUpdatePolicy(), is(expected.getAutoUpdatePolicy())); + assertThat(actual.getCategory(), is(expected.getCategory())); + assertThat(actual.getEvent(), is(expected.getEvent())); + assertThat(actual.getCommandDescription(), is(expected.getCommandDescription())); + assertThat(actual.getState(), is(expected.getState())); + assertThat(actual.getItemType(), is(expected.getItemType())); + assertThat(actual.getTags(), hasItems(expected.getTags().toArray(String[]::new))); + } + + @Test + public void testChannelGroupTypeProperlyMappedToEntityAndBack() { + ChannelGroupTypeUID groupTypeUID = new ChannelGroupTypeUID("testBinding:testGroupType"); + + ChannelDefinition channelDefinition = new ChannelDefinitionBuilder("channelName", + new ChannelTypeUID("system:color")).withLabel("label").withDescription("description") + .withProperties(Map.of("key", "value")).withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build(); + ChannelGroupType expected = ChannelGroupTypeBuilder.instance(groupTypeUID, "testLabel") + .withDescription("testDescription").withCategory("testCategory") + .withChannelDefinitions(List.of(channelDefinition)).build(); + + AbstractStorageBasedTypeProvider.ChannelGroupTypeEntity entity = AbstractStorageBasedTypeProvider + .mapToEntity(expected); + ChannelGroupType actual = AbstractStorageBasedTypeProvider.mapFromEntity(entity); + + assertThat(actual.getUID(), is(expected.getUID())); + assertThat(actual.getLabel(), is(expected.getLabel())); + assertThat(actual.getDescription(), is(expected.getDescription())); + assertThat(actual.getCategory(), is(expected.getCategory())); + List expectedChannelDefinitions = expected.getChannelDefinitions(); + List actualChannelDefinitions = actual.getChannelDefinitions(); + assertThat(actualChannelDefinitions.size(), is(expectedChannelDefinitions.size())); + for (ChannelDefinition expectedChannelDefinition : expectedChannelDefinitions) { + ChannelDefinition actualChannelDefinition = actualChannelDefinitions.stream() + .filter(d -> d.getId().equals(expectedChannelDefinition.getId())).findFirst().orElse(null); + assertThat(actualChannelDefinition, is(notNullValue())); + assertChannelDefinition(actualChannelDefinition, expectedChannelDefinition); + } + } + + @Test + public void testThingTypeProperlyMappedToEntityAndBack() { + ThingTypeUID thingTypeUID = new ThingTypeUID("testBinding:testThingType"); + + ChannelDefinition channelDefinition = new ChannelDefinitionBuilder("channelName", + new ChannelTypeUID("system:color")).withLabel("label").withDescription("description") + .withProperties(Map.of("key", "value")).withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build(); + ChannelGroupDefinition channelGroupDefinition = new ChannelGroupDefinition("groupName", + new ChannelGroupTypeUID("testBinding:channelGroupType"), "label", "description"); + ThingType expected = ThingTypeBuilder.instance(thingTypeUID, "testLabel").withDescription("description") + .withCategory("category").withExtensibleChannelTypeIds(List.of("ch1", "ch2")) + .withConfigDescriptionURI(URI.create("testBinding:testConfig")) + .withChannelDefinitions(List.of(channelDefinition)) + .withChannelGroupDefinitions(List.of(channelGroupDefinition)).isListed(true) + .withProperties(Map.of("key", "value")).withSupportedBridgeTypeUIDs(List.of("bridge1", "bridge2")) + .build(); + + AbstractStorageBasedTypeProvider.ThingTypeEntity entity = AbstractStorageBasedTypeProvider + .mapToEntity(expected); + ThingType actual = AbstractStorageBasedTypeProvider.mapFromEntity(entity); + + assertThat(actual.getUID(), is(expected.getUID())); + assertThat(actual.getLabel(), is(expected.getLabel())); + assertThat(actual.getDescription(), is(expected.getDescription())); + assertThat(actual.getExtensibleChannelTypeIds(), + containsInAnyOrder(expected.getExtensibleChannelTypeIds().toArray(String[]::new))); + assertThat(actual.getSupportedBridgeTypeUIDs(), + containsInAnyOrder(expected.getSupportedBridgeTypeUIDs().toArray(String[]::new))); + assertThat(actual.getCategory(), is(expected.getCategory())); + assertThat(actual.isListed(), is(expected.isListed())); + assertThat(actual.getRepresentationProperty(), is(expected.getRepresentationProperty())); + assertThat(actual.getConfigDescriptionURI(), is(expected.getConfigDescriptionURI())); + assertMap(actual.getProperties(), expected.getProperties()); + List expectedChannelDefinitions = expected.getChannelDefinitions(); + List actualChannelDefinitions = actual.getChannelDefinitions(); + assertThat(actualChannelDefinitions.size(), is(expectedChannelDefinitions.size())); + for (ChannelDefinition expectedChannelDefinition : expectedChannelDefinitions) { + ChannelDefinition actualChannelDefinition = actualChannelDefinitions.stream() + .filter(d -> d.getId().equals(expectedChannelDefinition.getId())).findFirst().orElse(null); + assertThat(actualChannelDefinition, is(notNullValue())); + assertChannelDefinition(actualChannelDefinition, expectedChannelDefinition); + } + List expectedChannelGroupDefinitions = expected.getChannelGroupDefinitions(); + List actualChannelGroupDefinitions = actual.getChannelGroupDefinitions(); + assertThat(actualChannelGroupDefinitions.size(), is(expectedChannelGroupDefinitions.size())); + for (ChannelGroupDefinition expectedChannelGroupDefinition : expectedChannelGroupDefinitions) { + ChannelGroupDefinition actualChannelGroupDefinition = actualChannelGroupDefinitions.stream() + .filter(d -> d.getId().equals(expectedChannelGroupDefinition.getId())).findFirst().orElse(null); + assertThat(actualChannelGroupDefinition, is(notNullValue())); + assertChannelGroupDefinition(actualChannelGroupDefinition, expectedChannelGroupDefinition); + } + } + + private void assertChannelDefinition(ChannelDefinition actual, ChannelDefinition expected) { + assertThat(actual.getId(), is(expected.getId())); + assertThat(actual.getChannelTypeUID(), is(expected.getChannelTypeUID())); + assertThat(actual.getLabel(), is(expected.getLabel())); + assertThat(actual.getDescription(), is(expected.getDescription())); + assertThat(actual.getAutoUpdatePolicy(), is(expected.getAutoUpdatePolicy())); + assertMap(actual.getProperties(), expected.getProperties()); + } + + private void assertChannelGroupDefinition(ChannelGroupDefinition actual, ChannelGroupDefinition expected) { + assertThat(actual.getId(), is(expected.getId())); + assertThat(actual.getTypeUID(), is(expected.getTypeUID())); + assertThat(actual.getLabel(), is(expected.getLabel())); + assertThat(actual.getDescription(), is(expected.getDescription())); + } + + private void assertMap(Map actual, Map expected) { + assertThat(actual.size(), is(expected.size())); + for (Map.Entry entry : expected.entrySet()) { + assertThat(actual, hasEntry(entry.getKey(), entry.getValue())); + } + } +} diff --git a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProviderOSGiTest.java b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProviderOSGiTest.java new file mode 100644 index 00000000000..5093159484e --- /dev/null +++ b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProviderOSGiTest.java @@ -0,0 +1,228 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openhab.core.storage.StorageService; +import org.openhab.core.test.java.JavaOSGiTest; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.type.BridgeType; +import org.openhab.core.thing.type.ChannelGroupType; +import org.openhab.core.thing.type.ChannelGroupTypeBuilder; +import org.openhab.core.thing.type.ChannelGroupTypeUID; +import org.openhab.core.thing.type.ChannelKind; +import org.openhab.core.thing.type.ChannelType; +import org.openhab.core.thing.type.ChannelTypeBuilder; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.thing.type.ThingType; +import org.openhab.core.thing.type.ThingTypeBuilder; + +/** + * The {@link AbstractStorageBasedTypeProviderOSGiTest} contains tests for storing and providing {@link ChannelType}s + * and {@link ThingType}s + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class AbstractStorageBasedTypeProviderOSGiTest extends JavaOSGiTest { + + private static final String BINDING_ID = "testBinding"; + private static final ThingTypeUID THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "testThingType"); + private static final ChannelTypeUID CHANNEL_TYPE_UID = new ChannelTypeUID(BINDING_ID, "testChannelType"); + private static final ChannelGroupTypeUID CHANNEL_GROUP_TYPE_UID = new ChannelGroupTypeUID(BINDING_ID, + "testChannelGroupType"); + + private @NonNullByDefault({}) AbstractStorageBasedTypeProvider typeProvider; + + @BeforeEach + public void setup() { + registerVolatileStorageService(); + + StorageService storageService = getService(StorageService.class); + assertThat(storageService, is(notNullValue())); + + typeProvider = new AbstractStorageBasedTypeProvider(storageService) { + }; + + assertThat(typeProvider.getThingTypes(null), hasSize(0)); + assertThat(typeProvider.getChannelTypes(null), hasSize(0)); + } + + @Test + public void testSingleThingTypeIsRestoredAsThingType() { + ThingType thingType = ThingTypeBuilder.instance(THING_TYPE_UID, "label").build(); + + typeProvider.putThingType(thingType); + assertThat(typeProvider.getThingTypes(null), hasSize(1)); + + ThingType registryThingType = typeProvider.getThingType(THING_TYPE_UID, null); + assertThat(registryThingType, is(notNullValue())); + + assertThat(registryThingType, not(instanceOf(BridgeType.class))); + } + + @Test + public void testSingleBridgeTypeIsRestoredAsBridgeType() { + BridgeType bridgeType = ThingTypeBuilder.instance(THING_TYPE_UID, "label").buildBridge(); + + typeProvider.putThingType(bridgeType); + assertThat(typeProvider.getThingTypes(null), hasSize(1)); + + ThingType registryThingType = typeProvider.getThingType(THING_TYPE_UID, null); + assertThat(registryThingType, is(notNullValue())); + + assertThat(registryThingType, is(instanceOf(BridgeType.class))); + } + + @Test + public void testTwoThingTypesCanBeRemovedIndependently() { + ThingType thingType1 = ThingTypeBuilder.instance(BINDING_ID, "type1", "label1").build(); + ThingType thingType2 = ThingTypeBuilder.instance(BINDING_ID, "type2", "label2").build(); + + typeProvider.putThingType(thingType1); + typeProvider.putThingType(thingType2); + assertThat(typeProvider.getThingType(thingType1.getUID(), null), is(notNullValue())); + assertThat(typeProvider.getThingType(thingType2.getUID(), null), is(notNullValue())); + + typeProvider.removeThingType(thingType1.getUID()); + assertThat(typeProvider.getThingType(thingType1.getUID(), null), is(nullValue())); + assertThat(typeProvider.getThingType(thingType2.getUID(), null), is(notNullValue())); + } + + @Test + public void testThingTypeCanBeUpdated() { + String originalLabel = "original Label"; + String updatedLabel = "updated Label"; + ThingType thingTypeOriginal = ThingTypeBuilder.instance(THING_TYPE_UID, originalLabel).build(); + ThingType thingTypeUpdated = ThingTypeBuilder.instance(THING_TYPE_UID, updatedLabel).build(); + + typeProvider.putThingType(thingTypeOriginal); + ThingType registryThingType = typeProvider.getThingType(THING_TYPE_UID, null); + assertThat(registryThingType, is(notNullValue())); + assertThat(registryThingType.getLabel(), is(originalLabel)); + + typeProvider.putThingType(thingTypeUpdated); + registryThingType = typeProvider.getThingType(THING_TYPE_UID, null); + assertThat(registryThingType, is(notNullValue())); + assertThat(registryThingType.getLabel(), is(updatedLabel)); + } + + @Test + public void testSingleChannelTypeIsAddedAndRestored() { + ChannelType channelType = ChannelTypeBuilder.state(CHANNEL_TYPE_UID, "label", "Switch").build(); + + typeProvider.putChannelType(channelType); + assertThat(typeProvider.getChannelTypes(null), hasSize(1)); + + ChannelType registryChannelType = typeProvider.getChannelType(CHANNEL_TYPE_UID, null); + assertThat(registryChannelType, is(notNullValue())); + assertThat(registryChannelType.getKind(), is(ChannelKind.STATE)); + } + + @Test + public void testTwoChannelTypesAreAddedAndCanBeRemovedIndependently() { + ChannelType channelType1 = ChannelTypeBuilder.trigger(new ChannelTypeUID(BINDING_ID, "type1"), "label1") + .build(); + ChannelType channelType2 = ChannelTypeBuilder.trigger(new ChannelTypeUID(BINDING_ID, "type2"), "label2") + .build(); + + typeProvider.putChannelType(channelType1); + typeProvider.putChannelType(channelType2); + + assertThat(typeProvider.getChannelType(channelType1.getUID(), null), is(notNullValue())); + assertThat(typeProvider.getChannelType(channelType2.getUID(), null), is(notNullValue())); + + typeProvider.removeChannelType(channelType1.getUID()); + + assertThat(typeProvider.getChannelType(channelType1.getUID(), null), is(nullValue())); + assertThat(typeProvider.getChannelType(channelType2.getUID(), null), is(notNullValue())); + } + + @Test + public void testChannelTypeCanBeUpdated() { + String originalLabel = "original Label"; + String updatedLabel = "updated Label"; + ChannelType channelTypeOriginal = ChannelTypeBuilder.trigger(CHANNEL_TYPE_UID, originalLabel).build(); + ChannelType channelTypeUpdated = ChannelTypeBuilder.trigger(CHANNEL_TYPE_UID, updatedLabel).build(); + + typeProvider.putChannelType(channelTypeOriginal); + ChannelType registryChannelType = typeProvider.getChannelType(CHANNEL_TYPE_UID, null); + assertThat(registryChannelType, is(notNullValue())); + assertThat(registryChannelType.getLabel(), is(originalLabel)); + + typeProvider.putChannelType(channelTypeUpdated); + registryChannelType = typeProvider.getChannelType(CHANNEL_TYPE_UID, null); + assertThat(registryChannelType, is(notNullValue())); + assertThat(registryChannelType.getLabel(), is(updatedLabel)); + } + + @Test + public void testSingleChannelGroupTypeIsAddedAndRestored() { + ChannelGroupType channelGroupType = ChannelGroupTypeBuilder.instance(CHANNEL_GROUP_TYPE_UID, "label").build(); + + typeProvider.putChannelGroupType(channelGroupType); + assertThat(typeProvider.getChannelGroupTypes(null), hasSize(1)); + + ChannelGroupType registryChannelGroupType = typeProvider.getChannelGroupType(CHANNEL_GROUP_TYPE_UID, null); + assertThat(registryChannelGroupType, is(notNullValue())); + } + + @Test + public void testTwoChannelGroupTypesAreAddedAndCanBeRemovedIndependently() { + ChannelGroupType channelGroupType1 = ChannelGroupTypeBuilder + .instance(new ChannelGroupTypeUID(BINDING_ID, "type1"), "label1").build(); + ChannelGroupType channelGroupType2 = ChannelGroupTypeBuilder + .instance(new ChannelGroupTypeUID(BINDING_ID, "type2"), "label2").build(); + + typeProvider.putChannelGroupType(channelGroupType1); + typeProvider.putChannelGroupType(channelGroupType2); + + assertThat(typeProvider.getChannelGroupType(channelGroupType1.getUID(), null), is(notNullValue())); + assertThat(typeProvider.getChannelGroupType(channelGroupType2.getUID(), null), is(notNullValue())); + + typeProvider.removeChannelGroupType(channelGroupType1.getUID()); + + assertThat(typeProvider.getChannelGroupType(channelGroupType1.getUID(), null), is(nullValue())); + assertThat(typeProvider.getChannelGroupType(channelGroupType2.getUID(), null), is(notNullValue())); + } + + @Test + public void testChannelGroupTypeCanBeUpdated() { + String originalLabel = "original Label"; + String updatedLabel = "updated Label"; + ChannelGroupType channelGroupTypeOriginal = ChannelGroupTypeBuilder + .instance(CHANNEL_GROUP_TYPE_UID, originalLabel).build(); + ChannelGroupType channelGroupTypeUpdated = ChannelGroupTypeBuilder + .instance(CHANNEL_GROUP_TYPE_UID, updatedLabel).build(); + + typeProvider.putChannelGroupType(channelGroupTypeOriginal); + ChannelGroupType registryChannelGroupType = typeProvider.getChannelGroupType(CHANNEL_GROUP_TYPE_UID, null); + assertThat(registryChannelGroupType, is(notNullValue())); + assertThat(registryChannelGroupType.getLabel(), is(originalLabel)); + + typeProvider.putChannelGroupType(channelGroupTypeUpdated); + registryChannelGroupType = typeProvider.getChannelGroupType(CHANNEL_GROUP_TYPE_UID, null); + assertThat(registryChannelGroupType, is(notNullValue())); + assertThat(registryChannelGroupType.getLabel(), is(updatedLabel)); + } +} From 6af9870c506f62d74aad46bfcf7d6c22f1353843 Mon Sep 17 00:00:00 2001 From: lolodomo Date: Mon, 8 May 2023 11:02:36 +0200 Subject: [PATCH 068/126] Fix build (AbstractStorageBasedTypeProviderTest) (#3596) Fix #3594 Signed-off-by: Laurent Garnier --- .../thing/binding/AbstractStorageBasedTypeProviderTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProviderTest.java b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProviderTest.java index 3030111e732..22ac0a2e56a 100644 --- a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProviderTest.java +++ b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProviderTest.java @@ -84,7 +84,7 @@ public void testChannelGroupTypeProperlyMappedToEntityAndBack() { ChannelDefinition channelDefinition = new ChannelDefinitionBuilder("channelName", new ChannelTypeUID("system:color")).withLabel("label").withDescription("description") - .withProperties(Map.of("key", "value")).withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build(); + .withProperties(Map.of("key", "value")).withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build(); ChannelGroupType expected = ChannelGroupTypeBuilder.instance(groupTypeUID, "testLabel") .withDescription("testDescription").withCategory("testCategory") .withChannelDefinitions(List.of(channelDefinition)).build(); @@ -114,7 +114,7 @@ public void testThingTypeProperlyMappedToEntityAndBack() { ChannelDefinition channelDefinition = new ChannelDefinitionBuilder("channelName", new ChannelTypeUID("system:color")).withLabel("label").withDescription("description") - .withProperties(Map.of("key", "value")).withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build(); + .withProperties(Map.of("key", "value")).withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build(); ChannelGroupDefinition channelGroupDefinition = new ChannelGroupDefinition("groupName", new ChannelGroupTypeUID("testBinding:channelGroupType"), "label", "description"); ThingType expected = ThingTypeBuilder.instance(thingTypeUID, "testLabel").withDescription("description") From 67b80af8720e3d46cb86f43359e41a41fc30d7af Mon Sep 17 00:00:00 2001 From: J-N-K Date: Tue, 9 May 2023 18:10:47 +0200 Subject: [PATCH 069/126] Fix storing of StateDescriptionFragment in AbstractStorageBasedTypeProvider (#3598) Signed-off-by: Jan N. Klug --- .../AbstractStorageBasedTypeProvider.java | 56 +++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProvider.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProvider.java index 9142371a9ce..1a646a5389d 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProvider.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/AbstractStorageBasedTypeProvider.java @@ -12,6 +12,7 @@ */ package org.openhab.core.thing.binding; +import java.math.BigDecimal; import java.net.URI; import java.util.Collection; import java.util.List; @@ -49,6 +50,7 @@ import org.openhab.core.types.StateDescription; import org.openhab.core.types.StateDescriptionFragment; import org.openhab.core.types.StateDescriptionFragmentBuilder; +import org.openhab.core.types.StateOption; /** * The {@link AbstractStorageBasedTypeProvider} is the base class for the implementation of a {@link Storage} based @@ -241,8 +243,12 @@ static ChannelTypeEntity mapToEntity(ChannelType channelType) { entity.tags = channelType.getTags(); entity.category = channelType.getCategory(); StateDescription stateDescription = channelType.getState(); - entity.stateDescriptionFragment = stateDescription == null ? null - : StateDescriptionFragmentBuilder.create(stateDescription).build(); + if (stateDescription != null) { + StateDescriptionFragment fragment = StateDescriptionFragmentBuilder.create(stateDescription).build(); + entity.stateDescriptionFragment = mapToEntity(fragment); + } else { + entity.stateDescriptionFragment = null; + } entity.commandDescription = channelType.getCommandDescription(); entity.event = channelType.getEvent(); entity.autoUpdatePolicy = channelType.getAutoUpdatePolicy(); @@ -260,6 +266,17 @@ static ChannelGroupTypeEntity mapToEntity(ChannelGroupType channelGroupType) { return entity; } + static StateDescriptionFragmentEntity mapToEntity(StateDescriptionFragment fragment) { + StateDescriptionFragmentEntity entity = new StateDescriptionFragmentEntity(); + entity.maximum = fragment.getMaximum(); + entity.minimum = fragment.getMinimum(); + entity.step = fragment.getStep(); + entity.options = fragment.getOptions(); + entity.pattern = fragment.getPattern(); + entity.isReadOnly = fragment.isReadOnly(); + return entity; + } + static ThingType mapFromEntity(ThingTypeEntity entity) { ThingTypeBuilder builder = ThingTypeBuilder.instance(entity.uid, entity.label) .withSupportedBridgeTypeUIDs(entity.supportedBridgeTypeRefs).withProperties(entity.properties) @@ -309,7 +326,8 @@ static ChannelType mapFromEntity(ChannelTypeEntity entity) { } if (builder instanceof StateChannelTypeBuilder stateBuilder) { if (entity.stateDescriptionFragment != null) { - stateBuilder.withStateDescriptionFragment(Objects.requireNonNull(entity.stateDescriptionFragment)); + stateBuilder.withStateDescriptionFragment( + mapFromEntity(Objects.requireNonNull(entity.stateDescriptionFragment))); } if (entity.commandDescription != null) { stateBuilder.withCommandDescription(Objects.requireNonNull(entity.commandDescription)); @@ -339,6 +357,27 @@ static ChannelGroupType mapFromEntity(ChannelGroupTypeEntity entity) { return builder.build(); } + static StateDescriptionFragment mapFromEntity(StateDescriptionFragmentEntity entity) { + StateDescriptionFragmentBuilder builder = StateDescriptionFragmentBuilder.create(); + if (entity.maximum != null) { + builder.withMaximum(Objects.requireNonNull(entity.maximum)); + } + if (entity.minimum != null) { + builder.withMinimum(Objects.requireNonNull(entity.minimum)); + } + if (entity.step != null) { + builder.withStep(Objects.requireNonNull(entity.step)); + } + if (entity.options != null) { + builder.withOptions(Objects.requireNonNull(entity.options)); + } + if (entity.pattern != null) { + builder.withPattern(Objects.requireNonNull(entity.pattern)); + } + builder.withReadOnly(entity.isReadOnly); + return builder.build(); + } + static class ThingTypeEntity { public @NonNullByDefault({}) ThingTypeUID uid; public @NonNullByDefault({}) String label; @@ -382,7 +421,7 @@ static class ChannelTypeEntity { public @NonNullByDefault({}) ChannelKind kind; public Set tags = Set.of(); public @Nullable String category; - public @Nullable StateDescriptionFragment stateDescriptionFragment; + public @Nullable StateDescriptionFragmentEntity stateDescriptionFragment; public @Nullable CommandDescription commandDescription; public @Nullable EventDescription event; public @Nullable AutoUpdatePolicy autoUpdatePolicy; @@ -396,4 +435,13 @@ static class ChannelGroupTypeEntity { public List channelDefinitions = List.of(); private @Nullable String category; } + + static class StateDescriptionFragmentEntity { + public @Nullable BigDecimal maximum; + public @Nullable BigDecimal minimum; + public @Nullable BigDecimal step; + public @Nullable List options; + public @Nullable String pattern; + public boolean isReadOnly = false; + } } From 9ef076dc6aec00cf93449131567532e006e34a81 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Tue, 9 May 2023 22:42:25 +0200 Subject: [PATCH 070/126] [uom] Add `unit` metadata for NumberItem (#3481) * Add defaultUnit metadata for NumberItem Signed-off-by: Jan N. Klug --- .../ItemStateConditionHandlerTest.java | 26 +- .../core/item/EnrichedItemDTOMapperTest.java | 4 +- .../model/script/actions/SemanticsTest.java | 32 +- .../extensions/PersistenceExtensionsTest.java | 3 +- .../core/semantics/SemanticTagsTest.java | 4 +- .../semantics/SemanticsPredicatesTest.java | 6 +- .../internal/SemanticsServiceImplTest.java | 4 +- .../thing/internal/CommunicationManager.java | 250 +++++--------- .../CommunicationManagerConversionTest.java | 141 ++++++++ .../org/openhab/core/i18n/UnitProvider.java | 6 +- .../core/internal/i18n/I18nProviderImpl.java | 120 +++---- .../core/internal/items/ItemRegistryImpl.java | 113 ++++--- .../org/openhab/core/items/GenericItem.java | 8 - .../org/openhab/core/items/GroupItem.java | 32 +- .../openhab/core/items/MetadataAwareItem.java | 48 +++ .../openhab/core/library/CoreItemFactory.java | 52 ++- .../core/library/items/NumberItem.java | 189 +++++------ .../core/library/types/QuantityType.java | 4 +- .../openhab/core/types/util/UnitUtils.java | 8 +- .../core/internal/i18n/TestUnitProvider.java | 48 +++ .../internal/items/ExpireManagerTest.java | 7 +- .../openhab/core/items/GenericItemTest.java | 3 - .../org/openhab/core/items/GroupItemTest.java | 56 ++++ .../core/library/CoreItemFactoryTest.java | 20 +- .../core/library/items/NumberItemTest.java | 309 ++++++++---------- ...antityTypeArithmeticGroupFunctionTest.java | 11 +- .../itest.bndrun | 7 +- .../script/engine/ScriptEngineOSGiTest.java | 15 +- .../openhab/core/items/GroupItemOSGiTest.java | 11 +- .../core/items/ItemRegistryImplTest.java | 18 +- .../CommunicationManagerOSGiTest.java | 56 ++-- .../openhab/core/tools/internal/Upgrader.java | 6 +- 32 files changed, 921 insertions(+), 696 deletions(-) create mode 100644 bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/CommunicationManagerConversionTest.java create mode 100644 bundles/org.openhab.core/src/main/java/org/openhab/core/items/MetadataAwareItem.java create mode 100644 bundles/org.openhab.core/src/test/java/org/openhab/core/internal/i18n/TestUnitProvider.java create mode 100644 bundles/org.openhab.core/src/test/java/org/openhab/core/items/GroupItemTest.java diff --git a/bundles/org.openhab.core.automation/src/test/java/org/openhab/core/automation/internal/module/handler/ItemStateConditionHandlerTest.java b/bundles/org.openhab.core.automation/src/test/java/org/openhab/core/automation/internal/module/handler/ItemStateConditionHandlerTest.java index 969e57c1b54..1b2638718d2 100644 --- a/bundles/org.openhab.core.automation/src/test/java/org/openhab/core/automation/internal/module/handler/ItemStateConditionHandlerTest.java +++ b/bundles/org.openhab.core.automation/src/test/java/org/openhab/core/automation/internal/module/handler/ItemStateConditionHandlerTest.java @@ -13,6 +13,7 @@ package org.openhab.core.automation.internal.module.handler; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.time.ZoneId; @@ -20,6 +21,8 @@ import java.util.Collection; import java.util.Map; +import javax.measure.quantity.Temperature; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -34,6 +37,7 @@ import org.openhab.core.automation.util.ConditionBuilder; import org.openhab.core.config.core.Configuration; import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.Item; import org.openhab.core.items.ItemNotFoundException; import org.openhab.core.items.ItemRegistry; @@ -77,7 +81,9 @@ public ParameterSet(String itemType, String comparisonState, State itemState, bo ((NumberItem) item).setState(itemState); break; case "Number:Temperature": - item = new NumberItem("Number:Temperature", ITEM_NAME); + UnitProvider unitProviderMock = mock(UnitProvider.class); + when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS); + item = new NumberItem("Number:Temperature", ITEM_NAME, unitProviderMock); ((NumberItem) item).setState(itemState); break; case "Dimmer": @@ -102,8 +108,8 @@ public static Collection equalsParameters() { { new ParameterSet("Number", "5", new DecimalType(23), false) }, // { new ParameterSet("Number", "5", new DecimalType(5), true) }, // { new ParameterSet("Number:Temperature", "5 °C", new DecimalType(23), false) }, // - { new ParameterSet("Number:Temperature", "5 °C", new DecimalType(5), false) }, // - { new ParameterSet("Number:Temperature", "0", new QuantityType<>(), true) }, // + { new ParameterSet("Number:Temperature", "5 °C", new DecimalType(5), true) }, // + { new ParameterSet("Number:Temperature", "0", new DecimalType(), false) }, // { new ParameterSet("Number:Temperature", "5", new QuantityType<>(23, SIUnits.CELSIUS), false) }, // { new ParameterSet("Number:Temperature", "5", new QuantityType<>(5, SIUnits.CELSIUS), false) }, // { new ParameterSet("Number:Temperature", "5 °C", new QuantityType<>(23, SIUnits.CELSIUS), false) }, // @@ -119,7 +125,7 @@ public static Collection greaterThanParameters() { { new ParameterSet("Number", "5", new DecimalType(5), false) }, // { new ParameterSet("Number", "5 °C", new DecimalType(23), true) }, // { new ParameterSet("Number", "5 °C", new DecimalType(5), false) }, // - { new ParameterSet("Number:Temperature", "0", new QuantityType<>(), false) }, // + { new ParameterSet("Number:Temperature", "0", new DecimalType(), false) }, // { new ParameterSet("Number:Temperature", "5", new QuantityType<>(23, SIUnits.CELSIUS), true) }, // { new ParameterSet("Number:Temperature", "5", new QuantityType<>(5, SIUnits.CELSIUS), false) }, // { new ParameterSet("Number:Temperature", "5 °C", new QuantityType<>(23, SIUnits.CELSIUS), true) }, // @@ -138,7 +144,7 @@ public static Collection greaterThanOrEqualsParameters() { { new ParameterSet("Number", "5 °C", new DecimalType(23), true) }, // { new ParameterSet("Number", "5 °C", new DecimalType(5), true) }, // { new ParameterSet("Number", "5 °C", new DecimalType(4), false) }, // - { new ParameterSet("Number:Temperature", "0", new QuantityType<>(), true) }, // + { new ParameterSet("Number:Temperature", "0", new DecimalType(), true) }, // { new ParameterSet("Number:Temperature", "5", new QuantityType<>(23, SIUnits.CELSIUS), true) }, // { new ParameterSet("Number:Temperature", "5", new QuantityType<>(5, SIUnits.CELSIUS), true) }, // { new ParameterSet("Number:Temperature", "5", new QuantityType<>(4, SIUnits.CELSIUS), false) }, // @@ -159,7 +165,7 @@ public static Collection lessThanParameters() { { new ParameterSet("Number", "5", new DecimalType(4), true) }, // { new ParameterSet("Number", "5 °C", new DecimalType(23), false) }, // { new ParameterSet("Number", "5 °C", new DecimalType(4), true) }, // - { new ParameterSet("Number:Temperature", "0", new QuantityType<>(), false) }, // + { new ParameterSet("Number:Temperature", "0", new DecimalType(), false) }, // { new ParameterSet("Number:Temperature", "5", new QuantityType<>(23, SIUnits.CELSIUS), false) }, // { new ParameterSet("Number:Temperature", "5", new QuantityType<>(4, SIUnits.CELSIUS), true) }, // { new ParameterSet("Number:Temperature", "5 °C", new QuantityType<>(23, SIUnits.CELSIUS), false) }, // @@ -179,7 +185,7 @@ public static Collection lessThanOrEqualsParameters() { { new ParameterSet("Number", "5 °C", new DecimalType(23), false) }, // { new ParameterSet("Number", "5 °C", new DecimalType(5), true) }, // { new ParameterSet("Number", "5 °C", new DecimalType(4), true) }, // - { new ParameterSet("Number:Temperature", "0", new QuantityType<>(), true) }, // + { new ParameterSet("Number:Temperature", "0", new DecimalType(), true) }, // { new ParameterSet("Number:Temperature", "5", new QuantityType<>(23, SIUnits.CELSIUS), false) }, // { new ParameterSet("Number:Temperature", "5", new QuantityType<>(5, SIUnits.CELSIUS), true) }, // { new ParameterSet("Number:Temperature", "5", new QuantityType<>(4, SIUnits.CELSIUS), true) }, // @@ -220,9 +226,11 @@ public void testEqualsCondition(ParameterSet parameterSet) { ItemStateConditionHandler handler = initItemStateConditionHandler("=", parameterSet.comparisonState); if (parameterSet.expectedResult) { - assertTrue(handler.isSatisfied(Map.of())); + assertTrue(handler.isSatisfied(Map.of()), + parameterSet.item + ", comparisonState=" + parameterSet.comparisonState); } else { - assertFalse(handler.isSatisfied(Map.of())); + assertFalse(handler.isSatisfied(Map.of()), + parameterSet.item + ", comparisonState=" + parameterSet.comparisonState); } } diff --git a/bundles/org.openhab.core.io.rest.core/src/test/java/org/openhab/core/io/rest/core/item/EnrichedItemDTOMapperTest.java b/bundles/org.openhab.core.io.rest.core/src/test/java/org/openhab/core/io/rest/core/item/EnrichedItemDTOMapperTest.java index 0b0d0a144af..9b022f92197 100644 --- a/bundles/org.openhab.core.io.rest.core/src/test/java/org/openhab/core/io/rest/core/item/EnrichedItemDTOMapperTest.java +++ b/bundles/org.openhab.core.io.rest.core/src/test/java/org/openhab/core/io/rest/core/item/EnrichedItemDTOMapperTest.java @@ -15,10 +15,12 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.mock; import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.GenericItem; import org.openhab.core.items.GroupItem; import org.openhab.core.library.CoreItemFactory; @@ -38,7 +40,7 @@ public void setup() { @Test public void testFiltering() { - CoreItemFactory itemFactory = new CoreItemFactory(); + CoreItemFactory itemFactory = new CoreItemFactory(mock(UnitProvider.class)); GroupItem group = new GroupItem("TestGroup"); GroupItem subGroup = new GroupItem("TestSubGroup"); diff --git a/bundles/org.openhab.core.model.script/src.moved/test/java/org/openhab/core/model/script/actions/SemanticsTest.java b/bundles/org.openhab.core.model.script/src.moved/test/java/org/openhab/core/model/script/actions/SemanticsTest.java index 9e934ed887a..cfd575d6fa6 100644 --- a/bundles/org.openhab.core.model.script/src.moved/test/java/org/openhab/core/model/script/actions/SemanticsTest.java +++ b/bundles/org.openhab.core.model.script/src.moved/test/java/org/openhab/core/model/script/actions/SemanticsTest.java @@ -18,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.when; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -25,6 +26,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.GenericItem; import org.openhab.core.items.GroupItem; import org.openhab.core.items.ItemNotFoundException; @@ -43,20 +45,22 @@ */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) +@NonNullByDefault public class SemanticsTest { - private @Mock ItemRegistry mockedItemRegistry; + private @Mock @NonNullByDefault({}) ItemRegistry itemRegistryMock; + private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock; - private GroupItem indoorLocationItem; - private GroupItem bathroomLocationItem; - private GroupItem equipmentItem; - private GenericItem temperaturePointItem; - private GenericItem humidityPointItem; - private GenericItem subEquipmentItem; + private @NonNullByDefault({}) GroupItem indoorLocationItem; + private @NonNullByDefault({}) GroupItem bathroomLocationItem; + private @NonNullByDefault({}) GroupItem equipmentItem; + private @NonNullByDefault({}) GenericItem temperaturePointItem; + private @NonNullByDefault({}) GenericItem humidityPointItem; + private @NonNullByDefault({}) GenericItem subEquipmentItem; @BeforeEach public void setup() throws ItemNotFoundException { - CoreItemFactory itemFactory = new CoreItemFactory(); + CoreItemFactory itemFactory = new CoreItemFactory(unitProviderMock); indoorLocationItem = new GroupItem("TestHouse"); indoorLocationItem.addTag("Indoor"); @@ -94,13 +98,13 @@ public void setup() throws ItemNotFoundException { equipmentItem.addMember(subEquipmentItem); subEquipmentItem.addGroupName(equipmentItem.getName()); - when(mockedItemRegistry.getItem("TestHouse")).thenReturn(indoorLocationItem); - when(mockedItemRegistry.getItem("TestBathRoom")).thenReturn(bathroomLocationItem); - when(mockedItemRegistry.getItem("Test08")).thenReturn(equipmentItem); - when(mockedItemRegistry.getItem("TestTemperature")).thenReturn(temperaturePointItem); - when(mockedItemRegistry.getItem("TestHumidity")).thenReturn(humidityPointItem); + when(itemRegistryMock.getItem("TestHouse")).thenReturn(indoorLocationItem); + when(itemRegistryMock.getItem("TestBathRoom")).thenReturn(bathroomLocationItem); + when(itemRegistryMock.getItem("Test08")).thenReturn(equipmentItem); + when(itemRegistryMock.getItem("TestTemperature")).thenReturn(temperaturePointItem); + when(itemRegistryMock.getItem("TestHumidity")).thenReturn(humidityPointItem); - new SemanticsActionService(mockedItemRegistry); + new SemanticsActionService(itemRegistryMock); } @Test diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/PersistenceExtensionsTest.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/PersistenceExtensionsTest.java index 9730380063f..df375db1650 100644 --- a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/PersistenceExtensionsTest.java +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/PersistenceExtensionsTest.java @@ -82,11 +82,10 @@ public class PersistenceExtensionsTest { public void setUp() { when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS); - CoreItemFactory itemFactory = new CoreItemFactory(); + CoreItemFactory itemFactory = new CoreItemFactory(unitProviderMock); numberItem = itemFactory.createItem(CoreItemFactory.NUMBER, TEST_NUMBER); quantityItem = itemFactory.createItem(CoreItemFactory.NUMBER + ItemUtil.EXTENSION_SEPARATOR + "Temperature", TEST_QUANTITY_NUMBER); - quantityItem.setUnitProvider(unitProviderMock); switchItem = itemFactory.createItem(CoreItemFactory.SWITCH, TEST_SWITCH); when(itemRegistryMock.get(TEST_NUMBER)).thenReturn(numberItem); diff --git a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticTagsTest.java b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticTagsTest.java index 67c44ce5b6f..56043f29e34 100644 --- a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticTagsTest.java +++ b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticTagsTest.java @@ -15,12 +15,14 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; import java.util.Locale; import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.GenericItem; import org.openhab.core.items.GroupItem; import org.openhab.core.library.CoreItemFactory; @@ -49,7 +51,7 @@ public class SemanticTagsTest { @BeforeEach public void setup() { - CoreItemFactory itemFactory = new CoreItemFactory(); + CoreItemFactory itemFactory = new CoreItemFactory(mock(UnitProvider.class)); locationItem = new GroupItem("TestBathRoom"); locationItem.addTag("Bathroom"); diff --git a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticsPredicatesTest.java b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticsPredicatesTest.java index ae1d5e964f6..988a5a46c56 100644 --- a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticsPredicatesTest.java +++ b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticsPredicatesTest.java @@ -13,10 +13,12 @@ package org.openhab.core.semantics; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.GenericItem; import org.openhab.core.items.GroupItem; import org.openhab.core.library.CoreItemFactory; @@ -24,7 +26,7 @@ import org.openhab.core.semantics.model.property.Temperature; /** - * This are tests for {@link SemanticsPredicates}. + * These are tests for {@link SemanticsPredicates}. * * @author Christoph Weitkamp - Initial contribution */ @@ -37,7 +39,7 @@ public class SemanticsPredicatesTest { @BeforeEach public void setup() { - CoreItemFactory itemFactory = new CoreItemFactory(); + CoreItemFactory itemFactory = new CoreItemFactory(mock(UnitProvider.class)); locationItem = new GroupItem("TestBathRoom"); locationItem.addTag("Bathroom"); diff --git a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/internal/SemanticsServiceImplTest.java b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/internal/SemanticsServiceImplTest.java index 9cb87fe6701..3ff77cb163d 100644 --- a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/internal/SemanticsServiceImplTest.java +++ b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/internal/SemanticsServiceImplTest.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.GenericItem; import org.openhab.core.items.GroupItem; import org.openhab.core.items.Item; @@ -43,6 +44,7 @@ public class SemanticsServiceImplTest { private @Mock @NonNullByDefault({}) ItemRegistry itemRegistryMock; private @Mock @NonNullByDefault({}) MetadataRegistry metadataRegistryMock; + private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock; private @NonNullByDefault({}) GroupItem locationItem; private @NonNullByDefault({}) GroupItem equipmentItem; @@ -52,7 +54,7 @@ public class SemanticsServiceImplTest { @BeforeEach public void setup() throws Exception { - CoreItemFactory itemFactory = new CoreItemFactory(); + CoreItemFactory itemFactory = new CoreItemFactory(unitProviderMock); locationItem = new GroupItem("TestBathRoom"); locationItem.addTag("Bathroom"); locationItem.setLabel("Joe's Room"); diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java index 85e05e0f257..8b2bf6ba0a6 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/CommunicationManager.java @@ -24,7 +24,7 @@ import java.util.function.Consumer; import java.util.function.Function; -import javax.measure.Quantity; +import javax.measure.Unit; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -35,6 +35,7 @@ import org.openhab.core.events.Event; import org.openhab.core.events.EventPublisher; import org.openhab.core.events.EventSubscriber; +import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.Item; import org.openhab.core.items.ItemFactory; import org.openhab.core.items.ItemRegistry; @@ -44,8 +45,10 @@ import org.openhab.core.items.events.GroupStateUpdatedEvent; import org.openhab.core.items.events.ItemCommandEvent; import org.openhab.core.items.events.ItemStateUpdatedEvent; +import org.openhab.core.library.CoreItemFactory; import org.openhab.core.library.items.NumberItem; import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.QuantityType; @@ -70,13 +73,10 @@ import org.openhab.core.thing.profiles.ProfileTypeUID; import org.openhab.core.thing.profiles.StateProfile; import org.openhab.core.thing.profiles.TriggerProfile; -import org.openhab.core.thing.type.ChannelType; import org.openhab.core.thing.type.ChannelTypeRegistry; -import org.openhab.core.thing.type.ChannelTypeUID; import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.openhab.core.types.Type; -import org.openhab.core.types.util.UnitUtils; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; @@ -131,6 +131,7 @@ public void onStateUpdateFromItem(State state) { private final EventPublisher eventPublisher; private final SafeCaller safeCaller; private final ThingRegistry thingRegistry; + private final UnitProvider unitProvider; private final ExpiringCacheMap profileSafeCallCache = new ExpiringCacheMap<>(CACHE_EXPIRATION); @@ -143,7 +144,7 @@ public CommunicationManager(final @Reference AutoUpdateManager autoUpdateManager final @Reference ItemStateConverter itemStateConverter, // final @Reference EventPublisher eventPublisher, // final @Reference SafeCaller safeCaller, // - final @Reference ThingRegistry thingRegistry) { + final @Reference ThingRegistry thingRegistry, final @Reference UnitProvider unitProvider) { this.autoUpdateManager = autoUpdateManager; this.channelTypeRegistry = channelTypeRegistry; this.defaultProfileFactory = defaultProfileFactory; @@ -153,6 +154,7 @@ public CommunicationManager(final @Reference AutoUpdateManager autoUpdateManager this.eventPublisher = eventPublisher; this.safeCaller = safeCaller; this.thingRegistry = thingRegistry; + this.unitProvider = unitProvider; itemChannelLinkRegistry.addRegistryChangeListener(this); } @@ -203,10 +205,6 @@ public void receive(Event event) { } } - private @Nullable Thing getThing(ThingUID thingUID) { - return thingRegistry.get(thingUID); - } - private Profile getProfile(ItemChannelLink link, Item item, @Nullable Thing thing) { synchronized (profiles) { Profile profile = profiles.get(link.getUID()); @@ -228,8 +226,8 @@ private Profile getProfile(ItemChannelLink link, Item item, @Nullable Thing thin } private ProfileCallback createCallback(ItemChannelLink link) { - return new ProfileCallbackImpl(eventPublisher, safeCaller, itemStateConverter, link, - thingUID -> getThing(thingUID), itemName -> getItem(itemName)); + return new ProfileCallbackImpl(eventPublisher, safeCaller, itemStateConverter, link, thingRegistry::get, + this::getItem); } private @Nullable ProfileTypeUID determineProfileTypeUID(ItemChannelLink link, Item item, @Nullable Thing thing) { @@ -272,25 +270,21 @@ private ProfileCallback createCallback(ItemChannelLink link) { String profileName = (String) link.getConfiguration() .get(ItemChannelLinkConfigDescriptionProvider.PARAM_PROFILE); if (profileName != null && !profileName.trim().isEmpty()) { - profileName = normalizeProfileName(profileName); + if (!profileName.contains(AbstractUID.SEPARATOR)) { + profileName = ProfileTypeUID.SYSTEM_SCOPE + AbstractUID.SEPARATOR + profileName; + } return new ProfileTypeUID(profileName); } return null; } - private String normalizeProfileName(String profileName) { - if (!profileName.contains(AbstractUID.SEPARATOR)) { - return ProfileTypeUID.SYSTEM_SCOPE + AbstractUID.SEPARATOR + profileName; - } - return profileName; - } - private @Nullable Profile getProfileFromFactories(ProfileTypeUID profileTypeUID, ItemChannelLink link, ProfileCallback callback) { ProfileContext context = null; Item item = getItem(link.getItemName()); - Thing thing = getThing(link.getLinkedUID().getThingUID()); + ThingUID thingUID = link.getLinkedUID().getThingUID(); + Thing thing = thingRegistry.get(thingUID); if (item != null && thing != null) { Channel channel = thing.getChannel(link.getLinkedUID()); if (channel != null) { @@ -341,48 +335,51 @@ private void receiveCommand(ItemCommandEvent commandEvent) { autoUpdateManager.receiveCommand(commandEvent, item); } - handleEvent(itemName, command, commandEvent.getSource(), s -> acceptedCommandTypeMap.get(s), - (profile, thing, convertedCommand) -> { - if (profile instanceof StateProfile stateProfile) { - int key = Objects.hash("COMMAND", profile, thing); - Profile p = profileSafeCallCache.putIfAbsentAndGet(key, - () -> safeCaller.create(stateProfile, StateProfile.class) // - .withAsync() // - .withIdentifier(thing) // - .withTimeout(THINGHANDLER_EVENT_TIMEOUT) // - .build()); - if (p instanceof StateProfile profileP) { - profileP.onCommandFromItem(convertedCommand); - } else { - throw new IllegalStateException("ExpiringCache didn't provide a StateProfile instance!"); - } - } - }); + handleEvent(itemName, command, commandEvent.getSource(), acceptedCommandTypeMap::get, + this::applyProfileForCommand); } private void receiveUpdate(ItemStateUpdatedEvent updateEvent) { final String itemName = updateEvent.getItemName(); final State newState = updateEvent.getItemState(); - handleEvent(itemName, newState, updateEvent.getSource(), s -> acceptedStateTypeMap.get(s), - (profile, thing, convertedState) -> { - int key = Objects.hash("UPDATE", profile, thing); - Profile p = profileSafeCallCache.putIfAbsentAndGet(key, - () -> safeCaller.create(profile, Profile.class) // - .withAsync() // - .withIdentifier(thing) // - .withTimeout(THINGHANDLER_EVENT_TIMEOUT) // - .build()); - if (p != null) { - p.onStateUpdateFromItem(convertedState); - } else { - throw new IllegalStateException("ExpiringCache didn't provide a Profile instance!"); - } - }); + handleEvent(itemName, newState, updateEvent.getSource(), acceptedStateTypeMap::get, + this::applyProfileForUpdate); } @FunctionalInterface - private static interface ProfileAction { - void handle(Profile profile, Thing thing, T type); + private interface ProfileAction { + void applyProfile(Profile profile, Thing thing, T type); + } + + private void applyProfileForUpdate(Profile profile, Thing thing, State convertedState) { + int key = Objects.hash("UPDATE", profile, thing); + Profile p = profileSafeCallCache.putIfAbsentAndGet(key, () -> safeCaller.create(profile, Profile.class) // + .withAsync() // + .withIdentifier(thing) // + .withTimeout(THINGHANDLER_EVENT_TIMEOUT) // + .build()); + if (p != null) { + p.onStateUpdateFromItem(convertedState); + } else { + throw new IllegalStateException("ExpiringCache didn't provide a Profile instance!"); + } + } + + private void applyProfileForCommand(Profile profile, Thing thing, Command convertedCommand) { + if (profile instanceof StateProfile stateProfile) { + int key = Objects.hash("COMMAND", profile, thing); + Profile p = profileSafeCallCache.putIfAbsentAndGet(key, + () -> safeCaller.create(stateProfile, StateProfile.class) // + .withAsync() // + .withIdentifier(thing) // + .withTimeout(THINGHANDLER_EVENT_TIMEOUT) // + .build()); + if (p instanceof StateProfile profileP) { + profileP.onCommandFromItem(convertedCommand); + } else { + throw new IllegalStateException("ExpiringCache didn't provide a StateProfile instance!"); + } + } } private void handleEvent(String itemName, T type, @Nullable String source, @@ -399,7 +396,8 @@ private void handleEvent(String itemName, T type, @Nullable Str return !link.getLinkedUID().toString().equals(source); }).forEach(link -> { ChannelUID channelUID = link.getLinkedUID(); - Thing thing = getThing(channelUID.getThingUID()); + ThingUID thingUID = channelUID.getThingUID(); + Thing thing = thingRegistry.get(thingUID); if (thing != null) { Channel channel = thing.getChannel(channelUID.getId()); if (channel != null) { @@ -408,7 +406,7 @@ private void handleEvent(String itemName, T type, @Nullable Str if (convertedType != null) { if (thing.getHandler() != null) { Profile profile = getProfile(link, item, thing); - action.handle(profile, thing, convertedType); + action.applyProfile(profile, thing, convertedType); } } else { logger.debug( @@ -429,45 +427,37 @@ private void handleEvent(String itemName, T type, @Nullable Str @SuppressWarnings("unchecked") private @Nullable T toAcceptedType(T originalType, Channel channel, Function<@Nullable String, @Nullable List>> acceptedTypesFunction, Item item) { - String acceptedItemType = channel.getAcceptedItemType(); - - // DecimalType command sent to a NumberItem with dimension defined: - if (originalType instanceof DecimalType type && hasDimension(item, acceptedItemType)) { - @Nullable - QuantityType quantityType = convertToQuantityType(type, item, acceptedItemType); - if (quantityType != null) { - return (T) quantityType; - } + String channelAcceptedItemType = channel.getAcceptedItemType(); + + if (channelAcceptedItemType == null) { + return originalType; } - // The command is sent to an item w/o dimension defined and the channel is legacy (created from a ThingType - // definition before UoM was introduced to the binding). The dimension information might now be defined on the - // current ThingType. The binding might expect us to provide a QuantityType so try to convert to the dimension - // the ChannelType provides. - // This can be removed once a suitable solution for https://github.com/eclipse/smarthome/issues/2555 (Thing - // migration) is found. - if (originalType instanceof DecimalType type && !hasDimension(item, acceptedItemType) - && channelTypeDefinesDimension(channel.getChannelTypeUID())) { - ChannelType channelType = channelTypeRegistry.getChannelType(channel.getChannelTypeUID()); - - String acceptedItemTypeFromChannelType = channelType != null ? channelType.getItemType() : null; - @Nullable - QuantityType quantityType = convertToQuantityType(type, item, acceptedItemTypeFromChannelType); - if (quantityType != null) { - return (T) quantityType; - } + // handle Number-Channels for backward compatibility + if (CoreItemFactory.NUMBER.equals(channelAcceptedItemType) + && originalType instanceof QuantityType quantityType) { + // strip unit from QuantityType for channels that accept plain number + return (T) new DecimalType(quantityType.toBigDecimal()); } - if (acceptedItemType == null) { - return originalType; + String itemDimension = ItemUtil.getItemTypeExtension(item.getType()); + String channelDimension = ItemUtil.getItemTypeExtension(channelAcceptedItemType); + + if (originalType instanceof DecimalType decimalType && channelDimension != null + && channelDimension.equals(itemDimension)) { + // Add unit from item to DecimalType when dimensions are equal + Unit unit = Objects.requireNonNull(((NumberItem) item).getUnit()); + return (T) new QuantityType<>(decimalType.toBigDecimal(), unit); } - List> acceptedTypes = acceptedTypesFunction.apply(acceptedItemType); - if (acceptedTypes == null) { - return originalType; + // handle HSBType/PercentType + if (CoreItemFactory.DIMMER.equals(channelAcceptedItemType) && originalType instanceof HSBType hsb) { + return (T) (hsb.as(PercentType.class)); } - if (acceptedTypes.contains(originalType.getClass())) { + // check for other cases if the type is acceptable + List> acceptedTypes = acceptedTypesFunction.apply(channelAcceptedItemType); + if (acceptedTypes == null || acceptedTypes.contains(originalType.getClass())) { return originalType; } else if (acceptedTypes.contains(PercentType.class) && originalType instanceof State state && PercentType.class.isAssignableFrom(originalType.getClass())) { @@ -476,77 +466,10 @@ && channelTypeDefinesDimension(channel.getChannelTypeUID())) { && PercentType.class.isAssignableFrom(originalType.getClass())) { return (@Nullable T) state.as(OnOffType.class); } else { - // Look for class hierarchy and convert appropriately - for (Class typeClass : acceptedTypes) { - if (!typeClass.isEnum() && typeClass.isAssignableFrom(originalType.getClass()) // - && State.class.isAssignableFrom(typeClass) && originalType instanceof State state) { - T ret = (T) state.as((Class) typeClass); - if (logger.isDebugEnabled()) { - logger.debug("Converted '{}' ({}) to accepted type '{}' ({}) for channel '{}' ", originalType, - originalType.getClass().getSimpleName(), ret, ret.getClass().getName(), - channel.getUID()); - } - return ret; - } - } - } - logger.debug("Received not accepted type '{}' for channel '{}'", originalType.getClass().getSimpleName(), - channel.getUID()); - return null; - } - - private boolean channelTypeDefinesDimension(@Nullable ChannelTypeUID channelTypeUID) { - if (channelTypeUID == null) { - return false; - } - - ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeUID); - return channelType != null && getDimension(channelType.getItemType()) != null; - } - - private boolean hasDimension(Item item, @Nullable String acceptedItemType) { - return (item instanceof NumberItem ni && ni.getDimension() != null) || getDimension(acceptedItemType) != null; - } - - private @Nullable QuantityType convertToQuantityType(DecimalType originalType, Item item, - @Nullable String acceptedItemType) { - if (!(item instanceof NumberItem)) { - // PercentType command sent via DimmerItem to a channel that's dimensioned - // (such as Number:Dimensionless, expecting a %). - // We can't know the proper units to add, so just pass it through and assume - // The binding can deal with it. - return null; - } - - NumberItem numberItem = (NumberItem) item; - - // DecimalType command sent via a NumberItem with dimension: - Class> dimension = numberItem.getDimension(); - - if (dimension == null) { - // DecimalType command sent via a plain NumberItem w/o dimension. - // We try to guess the correct unit from the channel-type's expected item dimension - // or from the item's state description. - dimension = getDimension(acceptedItemType); - } - - if (dimension != null) { - return numberItem.toQuantityType(originalType, dimension); - } - - return null; - } - - private @Nullable Class> getDimension(@Nullable String acceptedItemType) { - if (acceptedItemType == null || acceptedItemType.isEmpty()) { - return null; - } - String itemTypeExtension = ItemUtil.getItemTypeExtension(acceptedItemType); - if (itemTypeExtension == null) { + logger.debug("Received not accepted type '{}' for channel '{}'", originalType.getClass().getSimpleName(), + channel.getUID()); return null; } - - return UnitUtils.parseDimension(itemTypeExtension); } private @Nullable Item getItem(final String itemName) { @@ -556,7 +479,8 @@ private boolean hasDimension(Item item, @Nullable String acceptedItemType) { private void receiveTrigger(ChannelTriggeredEvent channelTriggeredEvent) { final ChannelUID channelUID = channelTriggeredEvent.getChannel(); final String event = channelTriggeredEvent.getEvent(); - final Thing thing = getThing(channelUID.getThingUID()); + ThingUID thingUID = channelUID.getThingUID(); + final Thing thing = thingRegistry.get(thingUID); handleCallFromHandler(channelUID, thing, profile -> { if (profile instanceof TriggerProfile triggerProfile) { @@ -566,7 +490,8 @@ private void receiveTrigger(ChannelTriggeredEvent channelTriggeredEvent) { } public void stateUpdated(ChannelUID channelUID, State state) { - final Thing thing = getThing(channelUID.getThingUID()); + ThingUID thingUID = channelUID.getThingUID(); + final Thing thing = thingRegistry.get(thingUID); handleCallFromHandler(channelUID, thing, profile -> { if (profile instanceof StateProfile stateProfile) { @@ -576,7 +501,8 @@ public void stateUpdated(ChannelUID channelUID, State state) { } public void postCommand(ChannelUID channelUID, Command command) { - final Thing thing = getThing(channelUID.getThingUID()); + ThingUID thingUID = channelUID.getThingUID(); + final Thing thing = thingRegistry.get(thingUID); handleCallFromHandler(channelUID, thing, profile -> { if (profile instanceof StateProfile stateProfile) { @@ -585,7 +511,7 @@ public void postCommand(ChannelUID channelUID, Command command) { }); } - void handleCallFromHandler(ChannelUID channelUID, @Nullable Thing thing, Consumer action) { + private void handleCallFromHandler(ChannelUID channelUID, @Nullable Thing thing, Consumer action) { itemChannelLinkRegistry.getLinks(channelUID).forEach(link -> { final Item item = getItem(link.getItemName()); if (item != null) { @@ -630,9 +556,7 @@ protected void addProfileFactory(ProfileFactory profileFactory) { protected void removeProfileFactory(ProfileFactory profileFactory) { Set links = profileFactories.remove(profileFactory); synchronized (profiles) { - links.forEach(link -> { - profiles.remove(link); - }); + links.forEach(profiles::remove); } } diff --git a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/CommunicationManagerConversionTest.java b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/CommunicationManagerConversionTest.java new file mode 100644 index 00000000000..f191b1b19da --- /dev/null +++ b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/CommunicationManagerConversionTest.java @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing; + +import static org.junit.jupiter.api.Assertions.*; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.openhab.core.items.Item; +import org.openhab.core.library.items.CallItem; +import org.openhab.core.library.items.ColorItem; +import org.openhab.core.library.items.ContactItem; +import org.openhab.core.library.items.DateTimeItem; +import org.openhab.core.library.items.DimmerItem; +import org.openhab.core.library.items.ImageItem; +import org.openhab.core.library.items.LocationItem; +import org.openhab.core.library.items.PlayerItem; +import org.openhab.core.library.items.RollershutterItem; +import org.openhab.core.library.items.StringItem; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.HSBType; +import org.openhab.core.library.types.IncreaseDecreaseType; +import org.openhab.core.library.types.NextPreviousType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.OpenClosedType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.PlayPauseType; +import org.openhab.core.library.types.PointType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.RawType; +import org.openhab.core.library.types.RewindFastforwardType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.types.UpDownType; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.Type; +import org.openhab.core.types.UnDefType; + +/** + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class CommunicationManagerConversionTest { + // TODO: remove test - only to show CommunicationManager is too complex + + private static final List> itemTypes = List.of(CallItem.class, ColorItem.class, + ContactItem.class, DateTimeItem.class, DimmerItem.class, ImageItem.class, LocationItem.class, + PlayerItem.class, RollershutterItem.class, StringItem.class); + + private static final List> types = List.of(DateTimeType.class, DecimalType.class, + HSBType.class, IncreaseDecreaseType.class, NextPreviousType.class, OnOffType.class, OpenClosedType.class, + PercentType.class, PlayPauseType.class, PointType.class, QuantityType.class, RawType.class, + RewindFastforwardType.class, StringType.class, UpDownType.class, UnDefType.class); + + private static Stream arguments() + throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + List arguments = new ArrayList<>(); + for (Class itemType : itemTypes) { + Item item = itemType.getDeclaredConstructor(String.class).newInstance("testItem"); + for (Class type : types) { + if (type.isEnum()) { + arguments.add(Arguments.of(item, type.getEnumConstants()[0])); + } else if (type == RawType.class) { + arguments.add(Arguments.of(item, new RawType(new byte[] {}, "mimeType"))); + } else { + arguments.add(Arguments.of(item, type.getDeclaredConstructor().newInstance())); + } + } + } + return arguments.stream(); + } + + @Disabled + @MethodSource("arguments") + @ParameterizedTest + public void testCommand(Item item, Type originalType) { + Type returnType = null; + + List> acceptedTypes = item.getAcceptedCommandTypes(); + if (acceptedTypes.contains(originalType.getClass())) { + returnType = originalType; + } else { + // Look for class hierarchy and convert appropriately + for (Class typeClass : acceptedTypes) { + if (!typeClass.isEnum() && typeClass.isAssignableFrom(originalType.getClass()) // + && State.class.isAssignableFrom(typeClass) && originalType instanceof State state) { + returnType = state.as((Class) typeClass); + } + } + } + + if (returnType != null && !returnType.getClass().equals(originalType.getClass())) { + fail("CommunicationManager did a conversion for target item " + item.getType() + " from " + + originalType.getClass() + " to " + returnType.getClass()); + } + } + + @MethodSource("arguments") + @ParameterizedTest + public void testState(Item item, Type originalType) { + Type returnType = null; + + List> acceptedTypes = item.getAcceptedDataTypes(); + if (acceptedTypes.contains(originalType.getClass())) { + returnType = originalType; + } else { + // Look for class hierarchy and convert appropriately + for (Class typeClass : acceptedTypes) { + if (!typeClass.isEnum() && typeClass.isAssignableFrom(originalType.getClass()) // + && State.class.isAssignableFrom(typeClass) && originalType instanceof State state) { + returnType = state.as((Class) typeClass); + + } + } + } + + if (returnType != null && !returnType.equals(originalType)) { + fail("CommunicationManager did a conversion for target item " + item.getType() + " from " + + originalType.getClass() + " to " + returnType.getClass()); + } + } +} diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/i18n/UnitProvider.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/i18n/UnitProvider.java index 59f02f882d7..72a61ec941d 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/i18n/UnitProvider.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/i18n/UnitProvider.java @@ -17,7 +17,6 @@ import javax.measure.spi.SystemOfUnits; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; /** * Provides {@link Unit}s and the current {@link SystemOfUnits}. @@ -34,9 +33,10 @@ public interface UnitProvider { * @param dimension The {@link Quantity}, called dimension here, defines the base unit for the retrieved unit. E.g. * call {@code getUnit(javax.measure.quantity.Temperature.class)} to retrieve the temperature unit * according to the current {@link SystemOfUnits}. - * @return The {@link Unit} matching the given {@link Quantity}, {@code null} otherwise. + * @return The {@link Unit} matching the given {@link Quantity} + * @throws IllegalArgumentException when the dimension is unknown */ - > @Nullable Unit getUnit(@Nullable Class dimension); + > Unit getUnit(Class dimension) throws IllegalArgumentException; /** * Returns the {@link SystemOfUnits} which is currently set, must not be null. diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java index 0e2ce22fa2b..1839bd79c89 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java @@ -142,12 +142,12 @@ public class I18nProviderImpl // UnitProvider static final String MEASUREMENT_SYSTEM = "measurementSystem"; private @Nullable SystemOfUnits measurementSystem; - private final Map>, Map>>> dimensionMap = new HashMap<>(); + private static final Map>, Map>>> DIMENSION_MAP = getDimensionMap(); @Activate @SuppressWarnings("unchecked") public I18nProviderImpl(ComponentContext componentContext) { - initDimensionMap(); + getDimensionMap(); modified((Map) componentContext.getProperties()); this.resourceBundleTracker = new ResourceBundleTracker(componentContext.getBundleContext(), this); @@ -187,16 +187,12 @@ private void setMeasurementSystem(@Nullable String measurementSystem) { final SystemOfUnits newMeasurementSystem; switch (ms) { - case SIUnits.MEASUREMENT_SYSTEM_NAME: - newMeasurementSystem = SIUnits.getInstance(); - break; - case ImperialUnits.MEASUREMENT_SYSTEM_NAME: - newMeasurementSystem = ImperialUnits.getInstance(); - break; - default: + case SIUnits.MEASUREMENT_SYSTEM_NAME -> newMeasurementSystem = SIUnits.getInstance(); + case ImperialUnits.MEASUREMENT_SYSTEM_NAME -> newMeasurementSystem = ImperialUnits.getInstance(); + default -> { logger.debug("Error setting measurement system for value '{}'.", measurementSystem); newMeasurementSystem = null; - break; + } } this.measurementSystem = newMeasurementSystem; @@ -358,12 +354,14 @@ public Locale getLocale() { @Override @SuppressWarnings("unchecked") - public > @Nullable Unit getUnit(@Nullable Class dimension) { - Map>> map = dimensionMap.get(dimension); + public > Unit getUnit(Class dimension) { + Map>> map = DIMENSION_MAP.get(dimension); if (map == null) { - return null; + throw new IllegalArgumentException("Dimension " + dimension.getName() + " is unknown. This is a bug."); } - return (Unit) map.get(getMeasurementSystem()); + Unit unit = (Unit) map.get(getMeasurementSystem()); + assert unit != null; + return unit; } @Override @@ -380,54 +378,62 @@ public SystemOfUnits getMeasurementSystem() { return SIUnits.getInstance(); } - private void initDimensionMap() { - addDefaultUnit(Acceleration.class, Units.METRE_PER_SQUARE_SECOND); - addDefaultUnit(AmountOfSubstance.class, Units.MOLE); - addDefaultUnit(Angle.class, Units.DEGREE_ANGLE, Units.DEGREE_ANGLE); - addDefaultUnit(Area.class, SIUnits.SQUARE_METRE, ImperialUnits.SQUARE_FOOT); - addDefaultUnit(ArealDensity.class, Units.DOBSON_UNIT); - addDefaultUnit(CatalyticActivity.class, Units.KATAL); - addDefaultUnit(DataAmount.class, Units.BYTE); - addDefaultUnit(DataTransferRate.class, Units.MEGABIT_PER_SECOND); - addDefaultUnit(Density.class, Units.KILOGRAM_PER_CUBICMETRE); - addDefaultUnit(Dimensionless.class, Units.ONE); - addDefaultUnit(ElectricCapacitance.class, Units.FARAD); - addDefaultUnit(ElectricCharge.class, Units.COULOMB); - addDefaultUnit(ElectricConductance.class, Units.SIEMENS); - addDefaultUnit(ElectricConductivity.class, Units.SIEMENS_PER_METRE); - addDefaultUnit(ElectricCurrent.class, Units.AMPERE); - addDefaultUnit(ElectricInductance.class, Units.HENRY); - addDefaultUnit(ElectricPotential.class, Units.VOLT); - addDefaultUnit(ElectricResistance.class, Units.OHM); - addDefaultUnit(Energy.class, Units.KILOWATT_HOUR); - addDefaultUnit(Force.class, Units.NEWTON); - addDefaultUnit(Frequency.class, Units.HERTZ); - addDefaultUnit(Illuminance.class, Units.LUX); - addDefaultUnit(Intensity.class, Units.IRRADIANCE); - addDefaultUnit(Length.class, SIUnits.METRE, ImperialUnits.INCH); - addDefaultUnit(LuminousFlux.class, Units.LUMEN); - addDefaultUnit(LuminousIntensity.class, Units.CANDELA); - addDefaultUnit(MagneticFlux.class, Units.WEBER); - addDefaultUnit(MagneticFluxDensity.class, Units.TESLA); - addDefaultUnit(Mass.class, SIUnits.KILOGRAM, ImperialUnits.POUND); - addDefaultUnit(Power.class, Units.WATT); - addDefaultUnit(Pressure.class, HECTO(SIUnits.PASCAL), ImperialUnits.INCH_OF_MERCURY); - addDefaultUnit(RadiationDoseAbsorbed.class, Units.GRAY); - addDefaultUnit(RadiationDoseEffective.class, Units.SIEVERT); - addDefaultUnit(Radioactivity.class, Units.BECQUEREL); - addDefaultUnit(SolidAngle.class, Units.STERADIAN); - addDefaultUnit(Speed.class, SIUnits.KILOMETRE_PER_HOUR, ImperialUnits.MILES_PER_HOUR); - addDefaultUnit(Temperature.class, SIUnits.CELSIUS, ImperialUnits.FAHRENHEIT); - addDefaultUnit(Time.class, Units.SECOND); - addDefaultUnit(Volume.class, SIUnits.CUBIC_METRE, ImperialUnits.GALLON_LIQUID_US); - addDefaultUnit(VolumetricFlowRate.class, Units.LITRE_PER_MINUTE, ImperialUnits.GALLON_PER_MINUTE); + public static Map>, Map>>> getDimensionMap() { + Map>, Map>>> dimensionMap = new HashMap<>(); + + addDefaultUnit(dimensionMap, Acceleration.class, Units.METRE_PER_SQUARE_SECOND); + addDefaultUnit(dimensionMap, AmountOfSubstance.class, Units.MOLE); + addDefaultUnit(dimensionMap, Angle.class, Units.DEGREE_ANGLE, Units.DEGREE_ANGLE); + addDefaultUnit(dimensionMap, Area.class, SIUnits.SQUARE_METRE, ImperialUnits.SQUARE_FOOT); + addDefaultUnit(dimensionMap, ArealDensity.class, Units.DOBSON_UNIT); + addDefaultUnit(dimensionMap, CatalyticActivity.class, Units.KATAL); + addDefaultUnit(dimensionMap, DataAmount.class, Units.BYTE); + addDefaultUnit(dimensionMap, DataTransferRate.class, Units.MEGABIT_PER_SECOND); + addDefaultUnit(dimensionMap, Density.class, Units.KILOGRAM_PER_CUBICMETRE); + addDefaultUnit(dimensionMap, Dimensionless.class, Units.ONE); + addDefaultUnit(dimensionMap, ElectricCapacitance.class, Units.FARAD); + addDefaultUnit(dimensionMap, ElectricCharge.class, Units.COULOMB); + addDefaultUnit(dimensionMap, ElectricConductance.class, Units.SIEMENS); + addDefaultUnit(dimensionMap, ElectricConductivity.class, Units.SIEMENS_PER_METRE); + addDefaultUnit(dimensionMap, ElectricCurrent.class, Units.AMPERE); + addDefaultUnit(dimensionMap, ElectricInductance.class, Units.HENRY); + addDefaultUnit(dimensionMap, ElectricPotential.class, Units.VOLT); + addDefaultUnit(dimensionMap, ElectricResistance.class, Units.OHM); + addDefaultUnit(dimensionMap, Energy.class, Units.KILOWATT_HOUR); + addDefaultUnit(dimensionMap, Force.class, Units.NEWTON); + addDefaultUnit(dimensionMap, Frequency.class, Units.HERTZ); + addDefaultUnit(dimensionMap, Illuminance.class, Units.LUX); + addDefaultUnit(dimensionMap, Intensity.class, Units.IRRADIANCE); + addDefaultUnit(dimensionMap, Length.class, SIUnits.METRE, ImperialUnits.INCH); + addDefaultUnit(dimensionMap, LuminousFlux.class, Units.LUMEN); + addDefaultUnit(dimensionMap, LuminousIntensity.class, Units.CANDELA); + addDefaultUnit(dimensionMap, MagneticFlux.class, Units.WEBER); + addDefaultUnit(dimensionMap, MagneticFluxDensity.class, Units.TESLA); + addDefaultUnit(dimensionMap, Mass.class, SIUnits.KILOGRAM, ImperialUnits.POUND); + addDefaultUnit(dimensionMap, Power.class, Units.WATT); + addDefaultUnit(dimensionMap, Pressure.class, HECTO(SIUnits.PASCAL), ImperialUnits.INCH_OF_MERCURY); + addDefaultUnit(dimensionMap, RadiationDoseAbsorbed.class, Units.GRAY); + addDefaultUnit(dimensionMap, RadiationDoseEffective.class, Units.SIEVERT); + addDefaultUnit(dimensionMap, Radioactivity.class, Units.BECQUEREL); + addDefaultUnit(dimensionMap, SolidAngle.class, Units.STERADIAN); + addDefaultUnit(dimensionMap, Speed.class, SIUnits.KILOMETRE_PER_HOUR, ImperialUnits.MILES_PER_HOUR); + addDefaultUnit(dimensionMap, Temperature.class, SIUnits.CELSIUS, ImperialUnits.FAHRENHEIT); + addDefaultUnit(dimensionMap, Time.class, Units.SECOND); + addDefaultUnit(dimensionMap, Volume.class, SIUnits.CUBIC_METRE, ImperialUnits.GALLON_LIQUID_US); + addDefaultUnit(dimensionMap, VolumetricFlowRate.class, Units.LITRE_PER_MINUTE, ImperialUnits.GALLON_PER_MINUTE); + + return dimensionMap; } - private > void addDefaultUnit(Class dimension, Unit siUnit, Unit imperialUnit) { + private static > void addDefaultUnit( + Map>, Map>>> dimensionMap, + Class dimension, Unit siUnit, Unit imperialUnit) { dimensionMap.put(dimension, Map.of(SIUnits.getInstance(), siUnit, ImperialUnits.getInstance(), imperialUnit)); } - private > void addDefaultUnit(Class dimension, Unit unit) { + private static > void addDefaultUnit( + Map>, Map>>> dimensionMap, + Class dimension, Unit unit) { dimensionMap.put(dimension, Map.of(SIUnits.getInstance(), unit, ImperialUnits.getInstance(), unit)); } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemRegistryImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemRegistryImpl.java index f3e33dced26..30c01685af6 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemRegistryImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemRegistryImpl.java @@ -23,8 +23,8 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.common.registry.AbstractRegistry; import org.openhab.core.common.registry.Provider; +import org.openhab.core.common.registry.RegistryChangeListener; import org.openhab.core.events.EventPublisher; -import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.GenericItem; import org.openhab.core.items.GroupItem; import org.openhab.core.items.Item; @@ -35,6 +35,8 @@ import org.openhab.core.items.ItemStateConverter; import org.openhab.core.items.ItemUtil; import org.openhab.core.items.ManagedItemProvider; +import org.openhab.core.items.Metadata; +import org.openhab.core.items.MetadataAwareItem; import org.openhab.core.items.MetadataRegistry; import org.openhab.core.items.RegistryHook; import org.openhab.core.items.events.ItemEventFactory; @@ -55,14 +57,15 @@ * This is the main implementing class of the {@link ItemRegistry} interface. It * keeps track of all declared items of all item providers and keeps their * current state in memory. This is the central point where states are kept and - * thus it is a core part for all stateful services. + * thus is a core part for all stateful services. * * @author Kai Kreuzer - Initial contribution * @author Stefan Bußweiler - Migration to new event mechanism */ @NonNullByDefault @Component(immediate = true) -public class ItemRegistryImpl extends AbstractRegistry implements ItemRegistry { +public class ItemRegistryImpl extends AbstractRegistry + implements ItemRegistry, RegistryChangeListener { private final Logger logger = LoggerFactory.getLogger(ItemRegistryImpl.class); @@ -70,7 +73,7 @@ public class ItemRegistryImpl extends AbstractRegistry groupItemNames) { for (String groupName : groupItemNames) { if (groupName != null) { try { - Item groupItem = getItem(groupName); - if (groupItem instanceof GroupItem groupItem1) { - groupItem1.addMember(item); + if (getItem(groupName) instanceof GroupItem groupItem) { + groupItem.addMember(item); } } catch (ItemNotFoundException e) { // the group might not yet be registered, let's ignore this @@ -161,9 +170,8 @@ private void replaceInGroupItems(Item oldItem, Item newItem, List groupI for (String groupName : groupItemNames) { if (groupName != null) { try { - Item groupItem = getItem(groupName); - if (groupItem instanceof GroupItem item) { - item.replaceMember(oldItem, newItem); + if (getItem(groupName) instanceof GroupItem groupItem) { + groupItem.replaceMember(oldItem, newItem); } } catch (ItemNotFoundException e) { // the group might not yet be registered, let's ignore this @@ -199,9 +207,12 @@ private void injectServices(Item item) { genericItem.setEventPublisher(getEventPublisher()); genericItem.setStateDescriptionService(stateDescriptionService); genericItem.setCommandDescriptionService(commandDescriptionService); - genericItem.setUnitProvider(unitProvider); genericItem.setItemStateConverter(itemStateConverter); } + if (item instanceof MetadataAwareItem metadataAwareItem) { + metadataRegistry.stream().filter(m -> m.getUID().getItemName().equals(item.getName())) + .forEach(metadataAwareItem::addedMetadata); + } } private void addMembersToGroupItem(GroupItem groupItem) { @@ -216,9 +227,8 @@ private void removeFromGroupItems(Item item, List groupItemNames) { for (String groupName : groupItemNames) { if (groupName != null) { try { - Item groupItem = getItem(groupName); - if (groupItem instanceof GroupItem groupItem1) { - groupItem1.removeMember(item); + if (getItem(groupName) instanceof GroupItem groupItem) { + groupItem.removeMember(item); } } catch (ItemNotFoundException e) { // the group might not yet be registered, let's ignore this @@ -234,16 +244,16 @@ protected void onAddElement(Item element) throws IllegalArgumentException { @Override protected void onRemoveElement(Item element) { - if (element instanceof GenericItem item) { - item.dispose(); + if (element instanceof GenericItem genericItem) { + genericItem.dispose(); } removeFromGroupItems(element, element.getGroupNames()); } @Override protected void beforeUpdateElement(Item existingElement) { - if (existingElement instanceof GenericItem item) { - item.dispose(); + if (existingElement instanceof GenericItem genericItem) { + genericItem.dispose(); } } @@ -291,21 +301,6 @@ protected void unsetReadyService(ReadyService readyService) { super.unsetReadyService(readyService); } - @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) - public void setUnitProvider(UnitProvider unitProvider) { - this.unitProvider = unitProvider; - for (Item item : getItems()) { - ((GenericItem) item).setUnitProvider(unitProvider); - } - } - - public void unsetUnitProvider(UnitProvider unitProvider) { - this.unitProvider = null; - for (Item item : getItems()) { - ((GenericItem) item).setUnitProvider(null); - } - } - @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) protected void setItemStateConverter(ItemStateConverter itemStateConverter) { this.itemStateConverter = itemStateConverter; @@ -442,17 +437,6 @@ public void removeRegistryHook(RegistryHook hook) { registryHooks.remove(hook); } - @Activate - protected void activate(final ComponentContext componentContext) { - super.activate(componentContext.getBundleContext()); - } - - @Override - @Deactivate - protected void deactivate() { - super.deactivate(); - } - @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) public void setStateDescriptionService(StateDescriptionService stateDescriptionService) { this.stateDescriptionService = stateDescriptionService; @@ -495,4 +479,31 @@ protected void setManagedProvider(ManagedItemProvider provider) { protected void unsetManagedProvider(ManagedItemProvider provider) { super.unsetManagedProvider(provider); } + + @Override + public void added(Metadata element) { + String itemName = element.getUID().getItemName(); + Item item = get(itemName); + if (item instanceof MetadataAwareItem metadataAwareItem) { + metadataAwareItem.addedMetadata(element); + } + } + + @Override + public void removed(Metadata element) { + String itemName = element.getUID().getItemName(); + Item item = get(itemName); + if (item instanceof MetadataAwareItem metadataAwareItem) { + metadataAwareItem.removedMetadata(element); + } + } + + @Override + public void updated(Metadata oldElement, Metadata element) { + String itemName = element.getUID().getItemName(); + Item item = get(itemName); + if (item instanceof MetadataAwareItem metadataAwareItem) { + metadataAwareItem.updatedMetadata(oldElement, element); + } + } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GenericItem.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GenericItem.java index dd0a53ec29e..f7ab33b0311 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GenericItem.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GenericItem.java @@ -29,7 +29,6 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.events.EventPublisher; -import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.events.ItemEventFactory; import org.openhab.core.service.CommandDescriptionService; import org.openhab.core.service.StateDescriptionService; @@ -83,8 +82,6 @@ public abstract class GenericItem implements ActiveItem { private @Nullable CommandDescriptionService commandDescriptionService; - protected @Nullable UnitProvider unitProvider; - protected @Nullable ItemStateConverter itemStateConverter; public GenericItem(String type, String name) { @@ -176,7 +173,6 @@ public void dispose() { this.eventPublisher = null; this.stateDescriptionService = null; this.commandDescriptionService = null; - this.unitProvider = null; this.itemStateConverter = null; } @@ -192,10 +188,6 @@ public void setCommandDescriptionService(@Nullable CommandDescriptionService com this.commandDescriptionService = commandDescriptionService; } - public void setUnitProvider(@Nullable UnitProvider unitProvider) { - this.unitProvider = unitProvider; - } - public void setItemStateConverter(@Nullable ItemStateConverter itemStateConverter) { this.itemStateConverter = itemStateConverter; } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GroupItem.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GroupItem.java index 742ca416b99..72d9e3ea497 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GroupItem.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/GroupItem.java @@ -26,7 +26,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.events.EventPublisher; -import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.events.ItemEventFactory; import org.openhab.core.service.CommandDescriptionService; import org.openhab.core.service.StateDescriptionService; @@ -40,7 +39,7 @@ * @author Kai Kreuzer - Initial contribution */ @NonNullByDefault -public class GroupItem extends GenericItem implements StateChangeListener { +public class GroupItem extends GenericItem implements StateChangeListener, MetadataAwareItem { public static final String TYPE = "Group"; @@ -405,14 +404,6 @@ public void setCommandDescriptionService(@Nullable CommandDescriptionService com } } - @Override - public void setUnitProvider(@Nullable UnitProvider unitProvider) { - super.setUnitProvider(unitProvider); - if (baseItem instanceof GenericItem item) { - item.setUnitProvider(unitProvider); - } - } - private void sendGroupStateUpdatedEvent(String memberName, State state) { EventPublisher eventPublisher1 = this.eventPublisher; if (eventPublisher1 != null) { @@ -457,4 +448,25 @@ private boolean isGroupItem(Item item) { private boolean hasOwnState(GroupItem item) { return item.getFunction() != null && item.getBaseItem() != null; } + + @Override + public void addedMetadata(Metadata metadata) { + if (baseItem instanceof MetadataAwareItem metadataAwareItem) { + metadataAwareItem.addedMetadata(metadata); + } + } + + @Override + public void updatedMetadata(Metadata oldMetadata, Metadata newMetadata) { + if (baseItem instanceof MetadataAwareItem metadataAwareItem) { + metadataAwareItem.updatedMetadata(oldMetadata, newMetadata); + } + } + + @Override + public void removedMetadata(Metadata metadata) { + if (baseItem instanceof MetadataAwareItem metadataAwareItem) { + metadataAwareItem.removedMetadata(metadata); + } + } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/items/MetadataAwareItem.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/MetadataAwareItem.java new file mode 100644 index 00000000000..dba3323d3c4 --- /dev/null +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/items/MetadataAwareItem.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.items; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link MetadataAwareItem} is an interface that can be implemented by {@link Item}s that need to be notified of + * metadata changes. + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public interface MetadataAwareItem { + + /** + * Can be implemented by subclasses to be informed about added metadata + * + * @param metadata the added {@link Metadata} object for this {@link Item} + */ + void addedMetadata(Metadata metadata); + + /** + * Can be implemented by subclasses to be informed about updated metadata + * + * @param oldMetadata the old {@link Metadata} object for this {@link Item} + * @param newMetadata the new {@link Metadata} object for this {@link Item} + * + */ + void updatedMetadata(Metadata oldMetadata, Metadata newMetadata); + + /** + * Can be implemented by subclasses to be informed about removed metadata + * + * @param metadata the removed {@link Metadata} object for this {@link Item} + */ + void removedMetadata(Metadata metadata); +} diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/CoreItemFactory.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/CoreItemFactory.java index 63390bcc7e4..8bef6bd054b 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/CoreItemFactory.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/CoreItemFactory.java @@ -14,6 +14,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.GenericItem; import org.openhab.core.items.ItemFactory; import org.openhab.core.items.ItemUtil; @@ -29,7 +30,9 @@ import org.openhab.core.library.items.RollershutterItem; import org.openhab.core.library.items.StringItem; import org.openhab.core.library.items.SwitchItem; +import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; /** * {@link CoreItemFactory}-Implementation for the core ItemTypes @@ -54,6 +57,12 @@ public class CoreItemFactory implements ItemFactory { public static final String ROLLERSHUTTER = "Rollershutter"; public static final String STRING = "String"; public static final String SWITCH = "Switch"; + private final UnitProvider unitProvider; + + @Activate + public CoreItemFactory(final @Reference UnitProvider unitProvider) { + this.unitProvider = unitProvider; + } @Override public @Nullable GenericItem createItem(@Nullable String itemTypeName, String itemName) { @@ -62,34 +71,21 @@ public class CoreItemFactory implements ItemFactory { } String itemType = ItemUtil.getMainItemType(itemTypeName); - switch (itemType) { - case CALL: - return new CallItem(itemName); - case COLOR: - return new ColorItem(itemName); - case CONTACT: - return new ContactItem(itemName); - case DATETIME: - return new DateTimeItem(itemName); - case DIMMER: - return new DimmerItem(itemName); - case IMAGE: - return new ImageItem(itemName); - case LOCATION: - return new LocationItem(itemName); - case NUMBER: - return new NumberItem(itemTypeName, itemName); - case PLAYER: - return new PlayerItem(itemName); - case ROLLERSHUTTER: - return new RollershutterItem(itemName); - case STRING: - return new StringItem(itemName); - case SWITCH: - return new SwitchItem(itemName); - default: - return null; - } + return switch (itemType) { + case CALL -> new CallItem(itemName); + case COLOR -> new ColorItem(itemName); + case CONTACT -> new ContactItem(itemName); + case DATETIME -> new DateTimeItem(itemName); + case DIMMER -> new DimmerItem(itemName); + case IMAGE -> new ImageItem(itemName); + case LOCATION -> new LocationItem(itemName); + case NUMBER -> new NumberItem(itemTypeName, itemName, unitProvider); + case PLAYER -> new PlayerItem(itemName); + case ROLLERSHUTTER -> new RollershutterItem(itemName); + case STRING -> new StringItem(itemName); + case SWITCH -> new SwitchItem(itemName); + default -> null; + }; } @Override diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/NumberItem.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/NumberItem.java index b1c78381222..43727408989 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/NumberItem.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/NumberItem.java @@ -21,11 +21,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.GenericItem; import org.openhab.core.items.ItemUtil; +import org.openhab.core.items.Metadata; +import org.openhab.core.items.MetadataAwareItem; import org.openhab.core.library.CoreItemFactory; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.openhab.core.types.State; @@ -33,6 +37,8 @@ import org.openhab.core.types.StateDescriptionFragmentBuilder; import org.openhab.core.types.UnDefType; import org.openhab.core.types.util.UnitUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A NumberItem has a decimal value and is usually used for all kinds @@ -43,26 +49,40 @@ * @author Kai Kreuzer - Initial contribution */ @NonNullByDefault -public class NumberItem extends GenericItem { - +public class NumberItem extends GenericItem implements MetadataAwareItem { + public static final String UNIT_METADATA_NAMESPACE = "unit"; private static final List> ACCEPTED_DATA_TYPES = List.of(DecimalType.class, QuantityType.class, UnDefType.class); private static final List> ACCEPTED_COMMAND_TYPES = List.of(DecimalType.class, QuantityType.class, RefreshType.class); - @Nullable - private Class> dimension; + private final Logger logger = LoggerFactory.getLogger(NumberItem.class); + + private final @Nullable Class> dimension; + private Unit unit = Units.ONE; + private final @Nullable UnitProvider unitProvider; public NumberItem(String name) { - this(CoreItemFactory.NUMBER, name); + this(CoreItemFactory.NUMBER, name, null); } - public NumberItem(String type, String name) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + public NumberItem(String type, String name, @Nullable UnitProvider unitProvider) { super(type, name); + this.unitProvider = unitProvider; String itemTypeExtension = ItemUtil.getItemTypeExtension(getType()); if (itemTypeExtension != null) { dimension = UnitUtils.parseDimension(itemTypeExtension); + if (dimension == null) { + throw new IllegalArgumentException("The given dimension " + itemTypeExtension + " is unknown."); + } else if (unitProvider == null) { + throw new IllegalArgumentException("A unit provider is required for items with a dimension."); + } + this.unit = unitProvider.getUnit((Class) dimension); + logger.trace("Item '{}' now has unit '{}'", name, unit); + } else { + dimension = null; } } @@ -85,7 +105,12 @@ public void send(QuantityType command) { DecimalType strippedCommand = new DecimalType(command.toBigDecimal()); internalSend(strippedCommand); } else { - internalSend(command); + if (command.getUnit().isCompatible(unit) || command.getUnit().inverse().isCompatible(unit)) { + internalSend(command); + } else { + logger.warn("Command '{}' to item '{}' was rejected because it is incompatible with the item unit '{}'", + command, name, unit); + } } } @@ -114,41 +139,34 @@ public void send(QuantityType command) { @Override public void setState(State state) { - // QuantityType update to a NumberItem without, strip unit - if (state instanceof QuantityType quantityType && dimension == null) { - DecimalType plainState = new DecimalType(quantityType.toBigDecimal()); - super.setState(plainState); - return; - } - - // DecimalType update for a NumberItem with dimension, convert to QuantityType: - if (state instanceof DecimalType decimalType && dimension != null) { - Unit unit = getUnit(dimension, false); - if (unit != null) { - super.setState(new QuantityType<>(decimalType.doubleValue(), unit)); - return; - } - } - - // QuantityType update, check unit and convert if necessary: - if (state instanceof QuantityType quantityType) { - Unit itemUnit = getUnit(dimension, true); - Unit stateUnit = quantityType.getUnit(); - if (itemUnit != null && (!stateUnit.getSystemUnit().equals(itemUnit.getSystemUnit()) - || UnitUtils.isDifferentMeasurementSystem(itemUnit, stateUnit))) { - QuantityType convertedState = quantityType.toInvertibleUnit(itemUnit); + if (state instanceof QuantityType quantityType) { + if (dimension == null) { + // QuantityType update to a NumberItem without unit, strip unit + DecimalType plainState = new DecimalType(quantityType.toBigDecimal()); + super.applyState(plainState); + } else { + // QuantityType update to a NumberItem with unit, convert to item unit (if possible) + Unit stateUnit = quantityType.getUnit(); + State convertedState = (stateUnit.isCompatible(unit) || stateUnit.inverse().isCompatible(unit)) + ? quantityType.toInvertibleUnit(unit) + : null; if (convertedState != null) { - super.setState(convertedState); - return; + super.applyState(convertedState); + } else { + logger.warn("Failed to update item '{}' because '{}' could not be converted to the item unit '{}'", + name, state, unit); } - - // the state could not be converted to an accepted unit. - return; } - } - - if (isAcceptedState(ACCEPTED_DATA_TYPES, state)) { - super.setState(state); + } else if (state instanceof DecimalType decimalType) { + if (dimension == null) { + // DecimalType update to NumberItem with unit + super.applyState(decimalType); + } else { + // DecimalType update for a NumberItem with dimension, convert to QuantityType + super.applyState(new QuantityType<>(decimalType.doubleValue(), unit)); + } + } else if (state instanceof UnDefType) { + super.applyState(state); } else { logSetTypeError(state); } @@ -160,83 +178,54 @@ public void setState(State state) { * @return the optional unit symbol for this {@link NumberItem}. */ public @Nullable String getUnitSymbol() { - Unit unit = getUnit(dimension, true); - return unit != null ? unit.toString() : null; + return (dimension != null) ? unit.toString() : null; } /** - * Derive the unit for this item by the following priority: + * Get the unit for this item, either: + * *

    - *
  • the unit parsed from the state description
  • - *
  • no unit if state description contains %unit%
  • - *
  • the default system unit from the item's dimension
  • + *
  • the unit retrieved from the unit namespace in the item's metadata
  • + *
  • the default system unit for the item's dimension
  • *
* * @return the {@link Unit} for this item if available, {@code null} otherwise. */ public @Nullable Unit> getUnit() { - return getUnit(dimension, true); + return (dimension != null) ? unit : null; } - /** - * Try to convert a {@link DecimalType} into a new {@link QuantityType}. The unit for the new - * type is derived either from the state description (which might also give a hint on items w/o dimension) or from - * the system default unit of the given dimension. - * - * @param originalType the source {@link DecimalType}. - * @param dimension the dimension to which the new {@link QuantityType} should adhere. - * @return the new {@link QuantityType} from the given originalType, {@code null} if a unit could not be calculated. - */ - public @Nullable QuantityType toQuantityType(DecimalType originalType, - @Nullable Class> dimension) { - Unit> itemUnit = getUnit(dimension, false); - if (itemUnit != null) { - return new QuantityType<>(originalType.toBigDecimal(), itemUnit); + @Override + public void addedMetadata(Metadata metadata) { + if (dimension != null && UNIT_METADATA_NAMESPACE.equals(metadata.getUID().getNamespace())) { + Unit unit = UnitUtils.parseUnit(metadata.getValue()); + if (unit == null) { + logger.warn("Unit '{}' could not be parsed to a known unit. Keeping old unit '{}' for item '{}'.", + metadata.getValue(), this.unit, name); + return; + } + if (!unit.isCompatible(this.unit) && !unit.inverse().isCompatible(this.unit)) { + logger.warn("Unit '{}' could not be parsed to a known unit. Keeping old unit '{}' for item '{}'.", + metadata.getValue(), this.unit, name); + return; + } + this.unit = unit; + logger.trace("Item '{}' now has unit '{}'", name, unit); } + } - return null; + @Override + public void updatedMetadata(Metadata oldMetadata, Metadata newMetadata) { + addedMetadata(newMetadata); } - /** - * Derive the unit for this item by the following priority: - *
    - *
  • the unit parsed from the state description
  • - *
  • the unit from the value if hasUnit = true and state description has unit - * %unit%
  • - *
  • the default system unit from the (optional) dimension parameter
  • - *
- * - * @param dimension the (optional) dimension - * @param hasUnit if the value has a unit - * @return the {@link Unit} for this item if available, {@code null} otherwise. - */ + @Override @SuppressWarnings({ "unchecked", "rawtypes" }) - private @Nullable Unit> getUnit(@Nullable Class> dimension, - boolean hasUnit) { - if (dimension == null) { - // if it is a plain number without dimension, we do not have a unit. - return null; + public void removedMetadata(Metadata metadata) { + if (dimension != null && UNIT_METADATA_NAMESPACE.equals(metadata.getUID().getNamespace())) { + assert unitProvider != null; + unit = unitProvider.getUnit((Class) dimension); + logger.trace("Item '{}' now has unit '{}'", name, unit); } - StateDescription stateDescription = getStateDescription(); - if (stateDescription != null) { - String pattern = stateDescription.getPattern(); - if (pattern != null) { - if (hasUnit && pattern.contains(UnitUtils.UNIT_PLACEHOLDER)) { - // use provided unit if present - return null; - } - Unit stateDescriptionUnit = UnitUtils.parseUnit(pattern); - if (stateDescriptionUnit != null) { - return stateDescriptionUnit; - } - } - } - - if (unitProvider != null) { - // explicit cast to Class as JDK compiler complains - return unitProvider.getUnit((Class) dimension); - } - - return null; } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/QuantityType.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/QuantityType.java index bfb209026e4..7f983f2e9b5 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/QuantityType.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/QuantityType.java @@ -298,7 +298,9 @@ public Dimension getDimension() { * @return the new {@link QuantityType} in the given {@link Unit} or {@code null} in case of an erro. */ public @Nullable QuantityType toInvertibleUnit(Unit targetUnit) { - if (!targetUnit.equals(getUnit()) && getUnit().inverse().isCompatible(targetUnit)) { + // only invert if unit is not equal and inverse is compatible and targetUnit is not ONE + if (!targetUnit.equals(getUnit()) && !targetUnit.isCompatible(AbstractUnit.ONE) + && getUnit().inverse().isCompatible(targetUnit)) { return inverse().toUnit(targetUnit); } return toUnit(targetUnit); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/types/util/UnitUtils.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/types/util/UnitUtils.java index b72ba56ddda..be835a64a64 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/types/util/UnitUtils.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/types/util/UnitUtils.java @@ -76,8 +76,8 @@ public class UnitUtils { * @return the {@link Class} instance of the interface or {@code null} if the given dimension is blank. * @throws IllegalArgumentException in case no class instance could be parsed from the given dimension. */ - public static @Nullable Class> parseDimension(String dimension) { - if (dimension.isBlank()) { + public static @Nullable Class> parseDimension(@Nullable String dimension) { + if (dimension == null || dimension.isBlank()) { return null; } @@ -149,7 +149,7 @@ public class UnitUtils { * label). In the latter case, the unit is expected to be the last part of the pattern separated by " " (e.g. "%.2f * °C" for °C). * - * @param stringWithUnit the string to extract the unit symbol from + * @param pattern the string to extract the unit symbol from * @return the unit symbol extracted from the string or {@code null} if no unit could be parsed * */ @@ -173,7 +173,7 @@ public class UnitUtils { return quantity.getUnit(); } catch (IllegalArgumentException | MeasurementParseException e) { // we expect this exception in case the extracted string does not match any known unit - LOGGER.debug("Unknown unit from pattern: {}", unitSymbol); + LOGGER.error("Unknown unit from pattern: {}", unitSymbol); } } diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/i18n/TestUnitProvider.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/i18n/TestUnitProvider.java new file mode 100644 index 00000000000..19aeaca401d --- /dev/null +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/i18n/TestUnitProvider.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.internal.i18n; + +import java.util.Map; + +import javax.measure.Quantity; +import javax.measure.Unit; +import javax.measure.spi.SystemOfUnits; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.i18n.UnitProvider; +import org.openhab.core.library.unit.SIUnits; + +/** + * The {@link TestUnitProvider} implements a {@link UnitProvider} for testing purposes + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class TestUnitProvider implements UnitProvider { + + private final Map>, Map>>> dimensionMap = I18nProviderImpl + .getDimensionMap(); + + @Override + @SuppressWarnings("unchecked") + public > Unit getUnit(Class dimension) { + Unit unit = (Unit) dimensionMap.getOrDefault(dimension, Map.of()).get(SIUnits.getInstance()); + assert unit != null; + return unit; + } + + @Override + public SystemOfUnits getMeasurementSystem() { + return SIUnits.getInstance(); + } +} diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/items/ExpireManagerTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/items/ExpireManagerTest.java index aa057792c80..e7c8bb083b3 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/items/ExpireManagerTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/items/ExpireManagerTest.java @@ -30,6 +30,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.openhab.core.events.Event; import org.openhab.core.events.EventPublisher; +import org.openhab.core.i18n.UnitProvider; import org.openhab.core.internal.items.ExpireManager.ExpireConfig; import org.openhab.core.items.Item; import org.openhab.core.items.ItemNotFoundException; @@ -47,6 +48,8 @@ import org.openhab.core.library.types.StringType; import org.openhab.core.types.UnDefType; +import tech.units.indriya.unit.Units; + /** * The {@link ExpireManagerTest} tests the {@link ExpireManager}. * @@ -341,7 +344,9 @@ void testExpireConfig() { // expected as state is invalid } - testItem = new NumberItem("Number:Temperature", ITEMNAME); + UnitProvider unitProviderMock = mock(UnitProvider.class); + when(unitProviderMock.getUnit(Temperature.class)).thenReturn(Units.CELSIUS); + testItem = new NumberItem("Number:Temperature", ITEMNAME, unitProviderMock); cfg = new ExpireManager.ExpireConfig(testItem, "1h,15 °C", Map.of()); assertEquals(Duration.ofHours(1), cfg.duration); assertEquals(new QuantityType("15 °C"), cfg.expireState); diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/items/GenericItemTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/items/GenericItemTest.java index 8a60f9ec776..96aebcedc5a 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/items/GenericItemTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/items/GenericItemTest.java @@ -26,7 +26,6 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.openhab.core.events.EventPublisher; -import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.events.ItemEvent; import org.openhab.core.items.events.ItemStateChangedEvent; import org.openhab.core.items.events.ItemStateUpdatedEvent; @@ -160,7 +159,6 @@ public void testDispose() { item.setEventPublisher(mock(EventPublisher.class)); item.setItemStateConverter(mock(ItemStateConverter.class)); item.setStateDescriptionService(null); - item.setUnitProvider(mock(UnitProvider.class)); item.addStateChangeListener(mock(StateChangeListener.class)); @@ -170,7 +168,6 @@ public void testDispose() { assertNull(item.itemStateConverter); // can not be tested as stateDescriptionProviders is private in GenericItem // assertThat(item.stateDescriptionProviders, is(nullValue())); - assertNull(item.unitProvider); assertEquals(0, item.listeners.size()); } diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/items/GroupItemTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/items/GroupItemTest.java new file mode 100644 index 00000000000..8a683c0144a --- /dev/null +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/items/GroupItemTest.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.items; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.openhab.core.library.items.NumberItem; + +/** + * The {@link GroupItemTest} contains tests for {@link GroupItem} + * + * @author Jan N. Klug - Initial contribution + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@NonNullByDefault +public class GroupItemTest { + private static final String ITEM_NAME = "test"; + + private @Mock @NonNullByDefault({}) NumberItem baseItemMock; + + @Test + public void testMetadataIsPropagatedToBaseItem() { + GroupItem groupItem = new GroupItem(ITEM_NAME, baseItemMock, new GroupFunction.Equality()); + + Metadata metadata = new Metadata(new MetadataKey("foo", ITEM_NAME), "foo", null); + Metadata updatedMetadata = new Metadata(new MetadataKey("foo", ITEM_NAME), "bar", null); + + groupItem.addedMetadata(metadata); + verify(baseItemMock).addedMetadata(eq(metadata)); + + groupItem.updatedMetadata(metadata, updatedMetadata); + verify(baseItemMock).updatedMetadata(eq(metadata), eq(updatedMetadata)); + + groupItem.removedMetadata(updatedMetadata); + verify(baseItemMock).removedMetadata(eq(updatedMetadata)); + } +} diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/CoreItemFactoryTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/CoreItemFactoryTest.java index f802e462633..d5481fbceb9 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/CoreItemFactoryTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/CoreItemFactoryTest.java @@ -15,6 +15,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; +import static org.mockito.Mockito.when; import java.util.List; @@ -22,18 +23,30 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.GenericItem; import org.openhab.core.library.items.NumberItem; +import tech.units.indriya.unit.Units; + /** * @author Henning Treu - Initial contribution */ @NonNullByDefault +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) public class CoreItemFactoryTest { + private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock; + @Test public void shouldCreateItems() { - CoreItemFactory coreItemFactory = new CoreItemFactory(); + CoreItemFactory coreItemFactory = new CoreItemFactory(unitProviderMock); List itemTypeNames = List.of(coreItemFactory.getSupportedItemTypes()); for (String itemTypeName : itemTypeNames) { GenericItem item = coreItemFactory.createItem(itemTypeName, itemTypeName.toLowerCase()); @@ -45,7 +58,8 @@ public void shouldCreateItems() { @Test public void createNumberItemWithDimension() { - CoreItemFactory coreItemFactory = new CoreItemFactory(); + CoreItemFactory coreItemFactory = new CoreItemFactory(unitProviderMock); + when(unitProviderMock.getUnit(Temperature.class)).thenReturn(Units.CELSIUS); NumberItem numberItem = (NumberItem) coreItemFactory.createItem(CoreItemFactory.NUMBER + ":Temperature", "myNumberItem"); @@ -54,7 +68,7 @@ public void createNumberItemWithDimension() { @Test public void shouldReturnNullForUnsupportedItemTypeName() { - CoreItemFactory coreItemFactory = new CoreItemFactory(); + CoreItemFactory coreItemFactory = new CoreItemFactory(unitProviderMock); GenericItem item = coreItemFactory.createItem("NoValidItemTypeName", "IWantMyItem"); assertThat(item, is(nullValue())); diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/items/NumberItemTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/items/NumberItemTest.java index 6d4612ac10d..c69f3e95d1c 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/items/NumberItemTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/items/NumberItemTest.java @@ -14,18 +14,15 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.instanceOf; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.*; -import java.util.List; +import java.util.Objects; -import javax.measure.quantity.Energy; +import javax.measure.Unit; +import javax.measure.quantity.Mass; import javax.measure.quantity.Temperature; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -34,16 +31,17 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; -import org.openhab.core.events.Event; import org.openhab.core.events.EventPublisher; import org.openhab.core.i18n.UnitProvider; +import org.openhab.core.internal.i18n.TestUnitProvider; +import org.openhab.core.items.Metadata; +import org.openhab.core.items.MetadataKey; import org.openhab.core.items.events.ItemCommandEvent; -import org.openhab.core.items.events.ItemStateUpdatedEvent; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.QuantityType; -import org.openhab.core.library.unit.ImperialUnits; +import org.openhab.core.library.unit.MetricPrefix; import org.openhab.core.library.unit.SIUnits; import org.openhab.core.library.unit.Units; import org.openhab.core.service.StateDescriptionService; @@ -67,7 +65,10 @@ public class NumberItemTest { private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock; private @Mock @NonNullByDefault({}) EventPublisher eventPublisherMock; + private final UnitProvider unitProvider = new TestUnitProvider(); + @BeforeEach + @SuppressWarnings("unchecked") public void setup() { when(stateDescriptionServiceMock.getStateDescription(ITEM_NAME, null)) .thenReturn(StateDescriptionFragmentBuilder.create().withPattern("%.1f " + UnitUtils.UNIT_PLACEHOLDER) @@ -75,83 +76,92 @@ public void setup() { when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS); } + /* + * State handling + */ @Test - public void setDecimalType() { + public void testUndefType() { NumberItem item = new NumberItem(ITEM_NAME); - State decimal = new DecimalType("23"); - item.setState(decimal); - assertEquals(decimal, item.getState()); + StateUtil.testUndefStates(item); } @Test - public void setPercentType() { + public void testAcceptedStates() { NumberItem item = new NumberItem(ITEM_NAME); - State percent = new PercentType(50); - item.setState(percent); - assertEquals(percent, item.getState()); + StateUtil.testAcceptedStates(item); } @Test - public void setHSBType() { + public void testSetDecimalTypeToPlainItem() { NumberItem item = new NumberItem(ITEM_NAME); - State hsb = new HSBType("5,23,42"); - item.setState(hsb); - assertEquals(hsb, item.getState()); + State decimal = new DecimalType("23"); + item.setState(decimal); + assertThat(item.getState(), is(decimal)); } @Test - public void testUndefType() { - NumberItem item = new NumberItem(ITEM_NAME); - StateUtil.testUndefStates(item); + public void testSetDecimalTypeToDimensionItem() { + NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider); + State decimal = new DecimalType("23"); + item.setState(decimal); + assertThat(item.getState(), is(new QuantityType<>("23 °C"))); } @Test - public void testAcceptedStates() { + public void testSetQuantityTypeToPlainItem() { NumberItem item = new NumberItem(ITEM_NAME); - StateUtil.testAcceptedStates(item); + State quantity = new QuantityType<>("23 °C"); + item.setState(quantity); + assertThat(item.getState(), is(new DecimalType("23"))); } @Test - public void testSetQuantityTypeAccepted() { - NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME); - item.setState(new QuantityType<>("20 °C")); - - assertThat(item.getState(), is(new QuantityType<>("20 °C"))); + public void testSetValidQuantityTypeWithSameUnitToDimensionItem() { + NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider); + State quantity = new QuantityType<>("23 °C"); + item.setState(quantity); + assertThat(item.getState(), is(quantity)); } @Test - public void testSetQuantityOnPlainNumberStripsUnit() { - NumberItem item = new NumberItem(ITEM_NAME); - item.setState(new QuantityType<>("20 °C")); - - assertThat(item.getState(), is(new DecimalType("20"))); + public void testSetValidQuantityTypeWithDifferentUnitToDimensionItem() { + NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider); + QuantityType quantity = new QuantityType<>("23 K"); + item.setState(quantity); + assertThat(item.getState(), + is(quantity.toUnit(Objects.requireNonNull(unitProvider.getUnit(Temperature.class))))); } @Test - public void testSetQuantityTypeConverted() { - NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME); - item.setState(new QuantityType<>(68, ImperialUnits.FAHRENHEIT)); - - assertThat(item.getState(), is(new QuantityType<>("20 °C"))); + public void testSetInvalidQuantityTypeToDimensionItemIsRejected() { + NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider); + QuantityType quantity = new QuantityType<>("23 N"); + item.setState(quantity); + assertThat(item.getState(), is(UnDefType.NULL)); } @Test - public void testSetQuantityTypeUnconverted() { - NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME); - UnitProvider unitProvider = mock(UnitProvider.class); - when(unitProvider.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS); - item.setUnitProvider(unitProvider); - item.setState(new QuantityType<>("10 A")); // should not be accepted as valid state + public void testSetPercentType() { + NumberItem item = new NumberItem(ITEM_NAME); + State percent = new PercentType(50); + item.setState(percent); + assertThat(item.getState(), is(percent)); + } - assertThat(item.getState(), is(UnDefType.NULL)); + @Test + public void testSetHSBType() { + NumberItem item = new NumberItem(ITEM_NAME); + State hsb = new HSBType("5,23,42"); + item.setState(hsb); + assertThat(item.getState(), is(hsb)); } + /* + * Command handling + */ @Test - public void testCommandUnitIsPassedForDimensionItem() { - NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME); - UnitProvider unitProvider = mock(UnitProvider.class); - when(unitProvider.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS); - item.setUnitProvider(unitProvider); + public void testValidCommandUnitIsPassedForDimensionItem() { + NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider); EventPublisher eventPublisher = mock(EventPublisher.class); item.setEventPublisher(eventPublisher); @@ -166,177 +176,128 @@ public void testCommandUnitIsPassedForDimensionItem() { } @Test - public void testCommandUnitIsStrippedForDimensionlessItem() { - NumberItem item = new NumberItem("Number", ITEM_NAME); + public void testValidCommandDifferentUnitIsPassedForDimensionItem() { + NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider); EventPublisher eventPublisher = mock(EventPublisher.class); item.setEventPublisher(eventPublisher); - item.send(new QuantityType<>("15 °C")); + QuantityType command = new QuantityType<>("15 K"); + item.send(command); ArgumentCaptor captor = ArgumentCaptor.forClass(ItemCommandEvent.class); verify(eventPublisher).post(captor.capture()); ItemCommandEvent event = captor.getValue(); - assertThat(event.getItemCommand(), is(new DecimalType("15"))); + assertThat(event.getItemCommand(), is(command)); } - @SuppressWarnings("null") @Test - public void testStripUnitPlaceholderFromPlainNumberItem() { - NumberItem item = new NumberItem("Number", ITEM_NAME); - item.setStateDescriptionService(stateDescriptionServiceMock); + public void testInvalidCommandUnitIsRejectedForDimensionItem() { + NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider); + EventPublisher eventPublisher = mock(EventPublisher.class); + item.setEventPublisher(eventPublisher); - assertThat(item.getStateDescription().getPattern(), is("%.1f")); + QuantityType command = new QuantityType<>("15 N"); + item.send(command); + + verify(eventPublisher, never()).post(any()); } - @SuppressWarnings("null") @Test - public void testLeaveUnitPlaceholderOnDimensionNumberItem() { - NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME); - item.setStateDescriptionService(stateDescriptionServiceMock); + public void testCommandUnitIsStrippedForDimensionlessItem() { + NumberItem item = new NumberItem(ITEM_NAME); + EventPublisher eventPublisher = mock(EventPublisher.class); + item.setEventPublisher(eventPublisher); - assertThat(item.getStateDescription().getPattern(), is("%.1f " + UnitUtils.UNIT_PLACEHOLDER)); - } + item.send(new QuantityType<>("15 °C")); - @SuppressWarnings("null") - @Test - public void testMiredToKelvin() { - NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME); - when(stateDescriptionServiceMock.getStateDescription(ITEM_NAME, null)).thenReturn( - StateDescriptionFragmentBuilder.create().withPattern("%.0f K").build().toStateDescription()); - item.setStateDescriptionService(stateDescriptionServiceMock); - item.setState(new QuantityType<>("370 mired")); + ArgumentCaptor captor = ArgumentCaptor.forClass(ItemCommandEvent.class); + verify(eventPublisher).post(captor.capture()); - assertThat(item.getState().format("%.0f K"), is("2703 K")); + ItemCommandEvent event = captor.getValue(); + assertThat(event.getItemCommand(), is(new DecimalType("15"))); } + /* + * + State description handling + */ @SuppressWarnings("null") @Test - public void testKelvinToMired() { - NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME); - when(stateDescriptionServiceMock.getStateDescription(ITEM_NAME, null)).thenReturn( - StateDescriptionFragmentBuilder.create().withPattern("%.0f mired").build().toStateDescription()); + public void testStripUnitPlaceholderInStateDescriptionFromPlainNumberItem() { + NumberItem item = new NumberItem(ITEM_NAME); item.setStateDescriptionService(stateDescriptionServiceMock); - item.setState(new QuantityType<>("2700 K")); - assertThat(item.getState().format("%.0f mired"), is("370 mired")); + assertThat(item.getStateDescription().getPattern(), is("%.1f")); } + @SuppressWarnings("null") @Test - void testStateDescriptionUnitUsedWhenStateDescriptionPresent() { - UnitProvider unitProviderMock = mock(UnitProvider.class); - when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS); - when(stateDescriptionServiceMock.getStateDescription(ITEM_NAME, null)).thenReturn( - StateDescriptionFragmentBuilder.create().withPattern("%.0f °F").build().toStateDescription()); - - NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME); + public void testLeaveUnitPlaceholderInStateDescriptionOnDimensionNumberItem() { + NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider); item.setStateDescriptionService(stateDescriptionServiceMock); - item.setUnitProvider(unitProviderMock); - - assertThat(item.getUnit(), is(ImperialUnits.FAHRENHEIT)); - item.setState(new QuantityType<>("429 °F")); - assertThat(item.getState(), is(new QuantityType<>("429 °F"))); - - item.setState(new QuantityType<>("165 °C")); - assertThat(item.getState(), is(new QuantityType<>("329 °F"))); + assertThat(item.getStateDescription().getPattern(), is("%.1f " + UnitUtils.UNIT_PLACEHOLDER)); } + /* + * Unit / metadata handling + */ @Test - void testPreservedWhenStateDescriptionContainsWildCard() { - UnitProvider unitProviderMock = mock(UnitProvider.class); - when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS); - when(stateDescriptionServiceMock.getStateDescription(ITEM_NAME, null)) - .thenReturn(StateDescriptionFragmentBuilder.create().withPattern("%.0f " + UnitUtils.UNIT_PLACEHOLDER) - .build().toStateDescription()); - - NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME); - item.setStateDescriptionService(stateDescriptionServiceMock); - item.setUnitProvider(unitProviderMock); - - assertThat(item.getUnit(), is(nullValue())); - - item.setState(new QuantityType<>("329 °F")); - assertThat(item.getState(), is(new QuantityType<>("329 °F"))); - - item.setState(new QuantityType<>("100 °C")); - assertThat(item.getState(), is(new QuantityType<>("100 °C"))); + void testSystemDefaultUnitIsUsedWithoutMetadata() { + final NumberItem item = new NumberItem("Number:Mass", ITEM_NAME, unitProvider); + assertThat(item.getUnit(), is(unitProvider.getUnit(Mass.class))); } @Test - void testDefaultUnitUsedWhenStateDescriptionEmpty() { - UnitProvider unitProviderMock = mock(UnitProvider.class); - when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS); - - NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME); - item.setUnitProvider(unitProviderMock); + void testMetadataUnitLifecycleIsObserved() { + final NumberItem item = new NumberItem("Number:Mass", ITEM_NAME, unitProvider); - assertThat(item.getUnit(), is(SIUnits.CELSIUS)); + Metadata initialMetadata = getUnitMetadata(MetricPrefix.MEGA(SIUnits.GRAM)); + item.addedMetadata(initialMetadata); + assertThat(item.getUnit(), is(MetricPrefix.MEGA(SIUnits.GRAM))); - item.setState(new QuantityType<>("329 °F")); - assertThat(item.getState(), is(new QuantityType<>("165 °C"))); + Metadata updatedMetadata = getUnitMetadata(MetricPrefix.MILLI(SIUnits.GRAM)); + item.updatedMetadata(initialMetadata, updatedMetadata); + assertThat(item.getUnit(), is(MetricPrefix.MILLI(SIUnits.GRAM))); - item.setState(new QuantityType<>("100 °C")); - assertThat(item.getState(), is(new QuantityType<>("100 °C"))); + item.removedMetadata(updatedMetadata); + assertThat(item.getUnit(), is(unitProvider.getUnit(Mass.class))); } @Test - void testNoUnitWhenUnitPlaceholderUsed() { - final UnitProvider unitProviderMock = mock(UnitProvider.class); - when(unitProviderMock.getUnit(Energy.class)).thenReturn(Units.JOULE); - - final NumberItem item = new NumberItem("Number:Energy", ITEM_NAME); - item.setUnitProvider(unitProviderMock); - - assertThat(item.getUnit(), is(Units.JOULE)); - - item.setStateDescriptionService(stateDescriptionServiceMock); - item.setState(new QuantityType<>("329 kWh")); - - assertThat(item.getState(), is(new QuantityType<>("329 kWh"))); - assertThat(item.getUnit(), is(nullValue())); + void testInvalidMetadataUnitIsRejected() { + final NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider); + item.addedMetadata(getUnitMetadata(MetricPrefix.MEGA(SIUnits.GRAM))); + assertThat(item.getUnit(), is(unitProvider.getUnit(Temperature.class))); } - public void quantityTypeCorrectlySetWithDifferentUnit() { - NumberItem numberItem = new NumberItem("Number:Temperature", ITEM_NAME); - numberItem.setUnitProvider(unitProviderMock); - numberItem.setEventPublisher(eventPublisherMock); - numberItem.setState(new QuantityType<>("140 °F")); - - assertThat(numberItem.getState(), Matchers.is(new QuantityType<>("60 °C"))); - - ArgumentCaptor captor = ArgumentCaptor.forClass(Event.class); - verify(eventPublisherMock, times(2)).post(captor.capture()); + /* + * Other tests + */ - List events = captor.getAllValues(); - assertThat(events, hasSize(2)); - - assertThat(events.get(0), Matchers.is(instanceOf(ItemStateUpdatedEvent.class))); + @SuppressWarnings("null") + @Test + public void testMiredToKelvin() { + NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider); + item.addedMetadata(getUnitMetadata(Units.KELVIN)); + item.setState(new QuantityType<>("370 mired")); - ItemStateUpdatedEvent updatedEvent = (ItemStateUpdatedEvent) events.get(0); - assertThat(updatedEvent.getItemName(), Matchers.is(ITEM_NAME)); - assertThat(updatedEvent.getItemState(), Matchers.is(new QuantityType<>("60°C"))); + assertThat(item.getState().format("%.0f K"), is("2703 K")); } + @SuppressWarnings("null") @Test - public void decimalTypeCorrectlySetWithUnit() { - NumberItem numberItem = new NumberItem("Number:Temperature", ITEM_NAME); - numberItem.setUnitProvider(unitProviderMock); - numberItem.setEventPublisher(eventPublisherMock); - numberItem.setState(new DecimalType(10)); - - assertThat(numberItem.getState(), Matchers.is(new QuantityType<>("10 °C"))); - - ArgumentCaptor captor = ArgumentCaptor.forClass(Event.class); - verify(eventPublisherMock, times(2)).post(captor.capture()); + public void testKelvinToMired() { + NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider); + item.addedMetadata(getUnitMetadata(Units.MIRED)); - List events = captor.getAllValues(); - assertThat(events, hasSize(2)); + item.setState(new QuantityType<>("2700 K")); - assertThat(events.get(0), Matchers.is(instanceOf(ItemStateUpdatedEvent.class))); + assertThat(item.getState().format("%.0f mired"), is("370 mired")); + } - ItemStateUpdatedEvent updatedEvent = (ItemStateUpdatedEvent) events.get(0); - assertThat(updatedEvent.getItemName(), Matchers.is(ITEM_NAME)); - assertThat(updatedEvent.getItemState(), Matchers.is(new QuantityType<>("10°C"))); + private Metadata getUnitMetadata(Unit unit) { + MetadataKey key = new MetadataKey(NumberItem.UNIT_METADATA_NAMESPACE, ITEM_NAME); + return new Metadata(key, unit.toString(), null); } } diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/QuantityTypeArithmeticGroupFunctionTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/QuantityTypeArithmeticGroupFunctionTest.java index 83ebffdde25..ed93f1305d5 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/QuantityTypeArithmeticGroupFunctionTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/QuantityTypeArithmeticGroupFunctionTest.java @@ -32,6 +32,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.openhab.core.i18n.UnitProvider; +import org.openhab.core.internal.i18n.TestUnitProvider; import org.openhab.core.items.GroupFunction; import org.openhab.core.items.GroupItem; import org.openhab.core.items.Item; @@ -39,6 +40,7 @@ import org.openhab.core.library.items.NumberItem; import org.openhab.core.types.State; import org.openhab.core.types.UnDefType; +import org.osgi.service.component.ComponentContext; /** * @author Henning Treu - Initial contribution @@ -47,7 +49,8 @@ @NonNullByDefault public class QuantityTypeArithmeticGroupFunctionTest { - private @NonNullByDefault({}) @Mock UnitProvider unitProvider; + private @Mock @NonNullByDefault({}) ComponentContext componentContext; + private final UnitProvider unitProvider = new TestUnitProvider(); /** * Locales having a different decimal and grouping separators to test string parsing and generation. @@ -313,16 +316,14 @@ public void testSumFunctionQuantityTypeWithGroups(Locale locale) { } private NumberItem createNumberItem(String name, Class> dimension, State state) { - NumberItem item = new NumberItem(CoreItemFactory.NUMBER + ":" + dimension.getSimpleName(), name); - item.setUnitProvider(unitProvider); + NumberItem item = new NumberItem(CoreItemFactory.NUMBER + ":" + dimension.getSimpleName(), name, unitProvider); item.setState(state); return item; } private GroupItem createGroupItem(String name, Class> dimension, State state) { GroupItem item = new GroupItem(name, - new NumberItem(CoreItemFactory.NUMBER + ":" + dimension.getSimpleName(), name)); - item.setUnitProvider(unitProvider); + new NumberItem(CoreItemFactory.NUMBER + ":" + dimension.getSimpleName(), name, unitProvider)); item.setState(state); return item; } diff --git a/itests/org.openhab.core.model.script.tests/itest.bndrun b/itests/org.openhab.core.model.script.tests/itest.bndrun index 6c54b99e210..b9de3468ee1 100644 --- a/itests/org.openhab.core.model.script.tests/itest.bndrun +++ b/itests/org.openhab.core.model.script.tests/itest.bndrun @@ -116,4 +116,9 @@ Fragment-Host: org.openhab.core.model.script junit-platform-commons;version='[1.9.2,1.9.3)',\ junit-platform-engine;version='[1.9.2,1.9.3)',\ junit-platform-launcher;version='[1.9.2,1.9.3)',\ - org.openhab.core.model.thing.runtime;version='[4.0.0,4.0.1)' + org.openhab.core.model.thing.runtime;version='[4.0.0,4.0.1)',\ + net.bytebuddy.byte-buddy;version='[1.12.19,1.12.20)',\ + net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ + org.mockito.junit-jupiter;version='[4.11.0,4.11.1)',\ + org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ + org.objenesis;version='[3.3.0,3.3.1)' diff --git a/itests/org.openhab.core.model.script.tests/src/main/java/org/openhab/core/model/script/engine/ScriptEngineOSGiTest.java b/itests/org.openhab.core.model.script.tests/src/main/java/org/openhab/core/model/script/engine/ScriptEngineOSGiTest.java index 668f4fc0d51..94bd38211ad 100644 --- a/itests/org.openhab.core.model.script.tests/src/main/java/org/openhab/core/model/script/engine/ScriptEngineOSGiTest.java +++ b/itests/org.openhab.core.model.script.tests/src/main/java/org/openhab/core/model/script/engine/ScriptEngineOSGiTest.java @@ -15,6 +15,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; import java.util.Collection; import java.util.List; @@ -26,8 +27,14 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; import org.openhab.core.common.registry.ProviderChangeListener; import org.openhab.core.events.EventPublisher; +import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.Item; import org.openhab.core.items.ItemProvider; import org.openhab.core.items.ItemRegistry; @@ -48,6 +55,8 @@ * @author Henning Treu - Initial contribution */ @NonNullByDefault +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) public class ScriptEngineOSGiTest extends JavaOSGiTest { private static final String ITEM_NAME = "Switch1"; @@ -58,9 +67,13 @@ public class ScriptEngineOSGiTest extends JavaOSGiTest { private @NonNullByDefault({}) ItemProvider itemProvider; private @NonNullByDefault({}) ItemRegistry itemRegistry; private @NonNullByDefault({}) ScriptEngine scriptEngine; + private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock; @BeforeEach public void setup() { + when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS); + when(unitProviderMock.getUnit(Length.class)).thenReturn(SIUnits.METRE); + registerVolatileStorageService(); EventPublisher eventPublisher = event -> { @@ -351,7 +364,7 @@ public void testNoXbaseConflicts() throws ScriptParsingException, ScriptExecutio } private Item createNumberItem(String numberItemName, Class dimension) { - return new NumberItem("Number:" + dimension.getSimpleName(), numberItemName); + return new NumberItem("Number:" + dimension.getSimpleName(), numberItemName, unitProviderMock); } @SuppressWarnings("unchecked") diff --git a/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/GroupItemOSGiTest.java b/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/GroupItemOSGiTest.java index b9c1567dc16..f398598ded4 100644 --- a/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/GroupItemOSGiTest.java +++ b/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/GroupItemOSGiTest.java @@ -17,6 +17,7 @@ import static org.hamcrest.collection.IsCollectionWithSize.hasSize; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; +import static org.openhab.core.library.unit.Units.ONE; import java.util.HashSet; import java.util.LinkedList; @@ -64,6 +65,7 @@ import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.RawType; import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.SIUnits; import org.openhab.core.test.java.JavaOSGiTest; import org.openhab.core.types.RefreshType; import org.openhab.core.types.State; @@ -796,7 +798,6 @@ public void assertThatNumberGroupItemWithDimensionCalculatesCorrectState() { gfDTO.name = "sum"; GroupFunction function = groupFunctionHelper.createGroupFunction(gfDTO, baseItem); GroupItem groupItem = new GroupItem("number", baseItem, function); - groupItem.setUnitProvider(unitProviderMock); NumberItem celsius = createNumberItem("C", Temperature.class, new QuantityType<>("23 °C")); groupItem.addMember(celsius); @@ -820,12 +821,14 @@ public void assertThatNumberGroupItemWithDimensionCalculatesCorrectState() { @Test public void assertThatNumberGroupItemWithDifferentDimensionsCalculatesCorrectState() { + when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS); + when(unitProviderMock.getUnit(Pressure.class)).thenReturn(SIUnits.PASCAL); + when(unitProviderMock.getUnit(Dimensionless.class)).thenReturn(ONE); NumberItem baseItem = createNumberItem("baseItem", Temperature.class, UnDefType.NULL); GroupFunctionDTO gfDTO = new GroupFunctionDTO(); gfDTO.name = "sum"; GroupFunction function = groupFunctionHelper.createGroupFunction(gfDTO, baseItem); GroupItem groupItem = new GroupItem("number", baseItem, function); - groupItem.setUnitProvider(unitProviderMock); groupItem.setItemStateConverter(itemStateConverter); NumberItem celsius = createNumberItem("C", Temperature.class, new QuantityType<>("23 °C")); @@ -844,8 +847,8 @@ public void assertThatNumberGroupItemWithDifferentDimensionsCalculatesCorrectSta } private NumberItem createNumberItem(String name, Class> dimension, State state) { - NumberItem item = new NumberItem(CoreItemFactory.NUMBER + ":" + dimension.getSimpleName(), name); - item.setUnitProvider(unitProviderMock); + NumberItem item = new NumberItem(CoreItemFactory.NUMBER + ":" + dimension.getSimpleName(), name, + unitProviderMock); item.setState(state); return item; diff --git a/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/ItemRegistryImplTest.java b/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/ItemRegistryImplTest.java index c7d98955117..f435ce58cd1 100644 --- a/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/ItemRegistryImplTest.java +++ b/itests/org.openhab.core.tests/src/main/java/org/openhab/core/items/ItemRegistryImplTest.java @@ -77,6 +77,7 @@ public class ItemRegistryImplTest extends JavaTest { private @NonNullByDefault({}) ManagedItemProvider itemProvider; private @Mock @NonNullByDefault({}) EventPublisher eventPublisherMock; + private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock; @BeforeEach public void beforeEach() { @@ -92,7 +93,7 @@ public void beforeEach() { // setup ManageItemProvider with necessary dependencies: itemProvider = new ManagedItemProvider(new VolatileStorageService(), - new ItemBuilderFactoryImpl(new CoreItemFactory())); + new ItemBuilderFactoryImpl(new CoreItemFactory(unitProviderMock))); itemProvider.add(new SwitchItem(ITEM_NAME)); itemProvider.add(cameraItem1); @@ -107,7 +108,6 @@ public void beforeEach() { setManagedProvider(itemProvider); setEventPublisher(ItemRegistryImplTest.this.eventPublisherMock); setStateDescriptionService(mock(StateDescriptionService.class)); - setUnitProvider(mock(UnitProvider.class)); setItemStateConverter(mock(ItemStateConverter.class)); } }; @@ -369,13 +369,11 @@ public void assertOldItemIsBeingDisposedOnUpdate() { assertNotNull(item.eventPublisher); assertNotNull(item.itemStateConverter); - assertNotNull(item.unitProvider); itemProvider.update(new SwitchItem("Item1")); assertNull(item.eventPublisher); assertNull(item.itemStateConverter); - assertNull(item.unitProvider); assertEquals(0, item.listeners.size()); } @@ -391,18 +389,6 @@ public void assertStateDescriptionServiceGetsInjected() { verify(baseItem).setStateDescriptionService(any(StateDescriptionService.class)); } - @Test - public void assertUnitProviderGetsInjected() { - GenericItem item = spy(new SwitchItem("Item1")); - NumberItem baseItem = spy(new NumberItem("baseItem")); - GenericItem group = new GroupItem("Group", baseItem); - itemProvider.add(item); - itemProvider.add(group); - - verify(item).setUnitProvider(any(UnitProvider.class)); - verify(baseItem).setUnitProvider(any(UnitProvider.class)); - } - @Test public void assertCommandDescriptionServiceGetsInjected() { GenericItem item = spy(new SwitchItem("Item1")); diff --git a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/CommunicationManagerOSGiTest.java b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/CommunicationManagerOSGiTest.java index c4e2dcdac05..3bac9fc85f1 100644 --- a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/CommunicationManagerOSGiTest.java +++ b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/CommunicationManagerOSGiTest.java @@ -40,6 +40,8 @@ import org.openhab.core.items.Item; import org.openhab.core.items.ItemRegistry; import org.openhab.core.items.ItemStateConverter; +import org.openhab.core.items.Metadata; +import org.openhab.core.items.MetadataKey; import org.openhab.core.items.events.ItemCommandEvent; import org.openhab.core.items.events.ItemEventFactory; import org.openhab.core.library.CoreItemFactory; @@ -50,8 +52,8 @@ import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.ImperialUnits; import org.openhab.core.library.unit.SIUnits; -import org.openhab.core.service.StateDescriptionService; import org.openhab.core.test.java.JavaOSGiTest; import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; @@ -81,7 +83,6 @@ import org.openhab.core.thing.type.ChannelTypeUID; import org.openhab.core.types.Command; import org.openhab.core.types.State; -import org.openhab.core.types.StateDescriptionFragmentBuilder; /** * @@ -103,21 +104,27 @@ protected void addProvider(Provider provider) { } } + private static final UnitProvider unitProviderMock = mock(UnitProvider.class); + private static final String EVENT = "event"; private static final String ITEM_NAME_1 = "testItem1"; private static final String ITEM_NAME_2 = "testItem2"; private static final String ITEM_NAME_3 = "testItem3"; private static final String ITEM_NAME_4 = "testItem4"; + private static final String ITEM_NAME_5 = "testItem5"; private static final SwitchItem ITEM_1 = new SwitchItem(ITEM_NAME_1); private static final SwitchItem ITEM_2 = new SwitchItem(ITEM_NAME_2); private static final NumberItem ITEM_3 = new NumberItem(ITEM_NAME_3); private static final NumberItem ITEM_4 = new NumberItem(ITEM_NAME_4); + private static NumberItem ITEM_5 = new NumberItem(ITEM_NAME_5); // will be replaced later by dimension item + private static final ThingTypeUID THING_TYPE_UID = new ThingTypeUID("test", "type"); private static final ThingUID THING_UID = new ThingUID("test", "thing"); private static final ChannelUID STATE_CHANNEL_UID_1 = new ChannelUID(THING_UID, "state-channel1"); private static final ChannelUID STATE_CHANNEL_UID_2 = new ChannelUID(THING_UID, "state-channel2"); private static final ChannelUID STATE_CHANNEL_UID_3 = new ChannelUID(THING_UID, "state-channel3"); private static final ChannelUID STATE_CHANNEL_UID_4 = new ChannelUID(THING_UID, "state-channel4"); + private static final ChannelUID STATE_CHANNEL_UID_5 = new ChannelUID(THING_UID, "state-channel5"); private static final ChannelTypeUID CHANNEL_TYPE_UID_4 = new ChannelTypeUID("test", "channeltype"); private static final ChannelUID TRIGGER_CHANNEL_UID_1 = new ChannelUID(THING_UID, "trigger-channel1"); private static final ChannelUID TRIGGER_CHANNEL_UID_2 = new ChannelUID(THING_UID, "trigger-channel2"); @@ -126,6 +133,7 @@ protected void addProvider(Provider provider) { private static final ItemChannelLink LINK_2_S2 = new ItemChannelLink(ITEM_NAME_2, STATE_CHANNEL_UID_2); private static final ItemChannelLink LINK_3_S3 = new ItemChannelLink(ITEM_NAME_3, STATE_CHANNEL_UID_3); private static final ItemChannelLink LINK_4_S4 = new ItemChannelLink(ITEM_NAME_4, STATE_CHANNEL_UID_4); + private static final ItemChannelLink LINK_5_S5 = new ItemChannelLink(ITEM_NAME_5, STATE_CHANNEL_UID_5); private static final ItemChannelLink LINK_1_T1 = new ItemChannelLink(ITEM_NAME_1, TRIGGER_CHANNEL_UID_1); private static final ItemChannelLink LINK_1_T2 = new ItemChannelLink(ITEM_NAME_1, TRIGGER_CHANNEL_UID_2); private static final ItemChannelLink LINK_2_T2 = new ItemChannelLink(ITEM_NAME_2, TRIGGER_CHANNEL_UID_2); @@ -135,6 +143,7 @@ protected void addProvider(Provider provider) { ChannelBuilder.create(STATE_CHANNEL_UID_3, "Number:Temperature").withKind(ChannelKind.STATE).build(), ChannelBuilder.create(STATE_CHANNEL_UID_4, CoreItemFactory.NUMBER).withKind(ChannelKind.STATE) .withType(CHANNEL_TYPE_UID_4).build(), + ChannelBuilder.create(STATE_CHANNEL_UID_5, "Number:Temperature").withKind(ChannelKind.STATE).build(), ChannelBuilder.create(TRIGGER_CHANNEL_UID_1).withKind(ChannelKind.TRIGGER).build(), ChannelBuilder.create(TRIGGER_CHANNEL_UID_2).withKind(ChannelKind.TRIGGER).build()).build(); @@ -158,6 +167,9 @@ protected void addProvider(Provider provider) { @BeforeEach public void beforeEach() { + when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS); + ITEM_5 = new NumberItem("Number:Temperature", ITEM_NAME_5, unitProviderMock); + safeCaller = getService(SafeCaller.class); assertNotNull(safeCaller); @@ -166,7 +178,8 @@ public void beforeEach() { assertNotNull(profileFactory); manager = new CommunicationManager(autoUpdateManagerMock, channelTypeRegistryMock, profileFactory, iclRegistry, - itemRegistryMock, itemStateConverterMock, eventPublisherMock, safeCaller, thingRegistryMock); + itemRegistryMock, itemStateConverterMock, eventPublisherMock, safeCaller, thingRegistryMock, + unitProviderMock); doAnswer(invocation -> { switch (((Channel) invocation.getArguments()[0]).getKind()) { @@ -205,7 +218,8 @@ public void removeProviderChangeListener(ProviderChangeListener @Override public Collection getAll() { - return List.of(LINK_1_S1, LINK_1_S2, LINK_2_S2, LINK_1_T1, LINK_1_T2, LINK_2_T2, LINK_3_S3, LINK_4_S4); + return List.of(LINK_1_S1, LINK_1_S2, LINK_2_S2, LINK_1_T1, LINK_1_T2, LINK_2_T2, LINK_3_S3, LINK_4_S4, + LINK_5_S5); } }); @@ -213,6 +227,7 @@ public Collection getAll() { when(itemRegistryMock.get(eq(ITEM_NAME_2))).thenReturn(ITEM_2); when(itemRegistryMock.get(eq(ITEM_NAME_3))).thenReturn(ITEM_3); when(itemRegistryMock.get(eq(ITEM_NAME_4))).thenReturn(ITEM_4); + when(itemRegistryMock.get(eq(ITEM_NAME_5))).thenReturn(ITEM_5); ChannelType channelType4 = mock(ChannelType.class); when(channelType4.getItemType()).thenReturn("Number:Temperature"); @@ -222,12 +237,8 @@ public Collection getAll() { THING.setHandler(thingHandlerMock); when(thingRegistryMock.get(eq(THING_UID))).thenReturn(THING); - manager.addItemFactory(new CoreItemFactory()); - UnitProvider unitProvider = mock(UnitProvider.class); - when(unitProvider.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS); - ITEM_3.setUnitProvider(unitProvider); - ITEM_4.setUnitProvider(unitProvider); + manager.addItemFactory(new CoreItemFactory(unitProviderMock)); } @Test @@ -284,7 +295,7 @@ public void testItemCommandEventSingleLink() { @Test public void testItemCommandEventDecimal2Quantity() { // Take unit from accepted item type (see channel built from STATE_CHANNEL_UID_3) - manager.receive(ItemEventFactory.createCommandEvent(ITEM_NAME_3, DecimalType.valueOf("20"))); + manager.receive(ItemEventFactory.createCommandEvent(ITEM_NAME_5, DecimalType.valueOf("20"))); waitForAssert(() -> { verify(stateProfileMock).onCommandFromItem(eq(QuantityType.valueOf("20 °C"))); }); @@ -294,33 +305,16 @@ public void testItemCommandEventDecimal2Quantity() { @Test public void testItemCommandEventDecimal2Quantity2() { - // Take unit from state description - StateDescriptionService stateDescriptionService = mock(StateDescriptionService.class); - when(stateDescriptionService.getStateDescription(ITEM_NAME_3, null)).thenReturn( - StateDescriptionFragmentBuilder.create().withPattern("%.1f °F").build().toStateDescription()); - ITEM_3.setStateDescriptionService(stateDescriptionService); + MetadataKey key = new MetadataKey(NumberItem.UNIT_METADATA_NAMESPACE, ITEM_NAME_5); + Metadata metadata = new Metadata(key, ImperialUnits.FAHRENHEIT.toString(), null); + ITEM_5.addedMetadata(metadata); - manager.receive(ItemEventFactory.createCommandEvent(ITEM_NAME_3, DecimalType.valueOf("20"))); + manager.receive(ItemEventFactory.createCommandEvent(ITEM_NAME_5, DecimalType.valueOf("20"))); waitForAssert(() -> { verify(stateProfileMock).onCommandFromItem(eq(QuantityType.valueOf("20 °F"))); }); verifyNoMoreInteractions(stateProfileMock); verifyNoMoreInteractions(triggerProfileMock); - - ITEM_3.setStateDescriptionService(null); - } - - @Test - public void testItemCommandEventDecimal2QuantityChannelType() { - // The command is sent to an item w/o dimension defined and the channel is legacy (created from a ThingType - // definition before UoM was introduced to the binding). The dimension information might now be defined on the - // current ThingType. - manager.receive(ItemEventFactory.createCommandEvent(ITEM_NAME_4, DecimalType.valueOf("20"))); - waitForAssert(() -> { - verify(stateProfileMock).onCommandFromItem(eq(QuantityType.valueOf("20 °C"))); - }); - verifyNoMoreInteractions(stateProfileMock); - verifyNoMoreInteractions(triggerProfileMock); } @Test diff --git a/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java b/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java index 0c896ad248b..be4094ab6ae 100644 --- a/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java +++ b/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java @@ -25,6 +25,7 @@ import org.openhab.core.items.ManagedItemProvider; import org.openhab.core.items.Metadata; import org.openhab.core.items.MetadataKey; +import org.openhab.core.library.items.NumberItem; import org.openhab.core.storage.json.internal.JsonStorage; import org.openhab.core.thing.internal.link.ItemChannelLinkConfigDescriptionProvider; import org.openhab.core.thing.link.ItemChannelLink; @@ -91,7 +92,7 @@ public void itemCopyUnitToMetadata() { itemStorage.getKeys().forEach(itemName -> { ManagedItemProvider.PersistedItem item = itemStorage.get(itemName); if (item != null && item.itemType.startsWith("Number:")) { - if (metadataStorage.containsKey("unit" + ":" + itemName)) { + if (metadataStorage.containsKey(NumberItem.UNIT_METADATA_NAMESPACE + ":" + itemName)) { logger.debug("{}: already contains a 'unit' metadata, skipping it", itemName); } else { Metadata metadata = metadataStorage.get("stateDescription:" + itemName); @@ -107,7 +108,8 @@ public void itemCopyUnitToMetadata() { Unit stateDescriptionUnit = UnitUtils.parseUnit(pattern); if (stateDescriptionUnit != null) { String unit = stateDescriptionUnit.toString(); - MetadataKey defaultUnitMetadataKey = new MetadataKey("unit", itemName); + MetadataKey defaultUnitMetadataKey = new MetadataKey(NumberItem.UNIT_METADATA_NAMESPACE, + itemName); Metadata defaultUnitMetadata = new Metadata(defaultUnitMetadataKey, unit, null); metadataStorage.put(defaultUnitMetadataKey.toString(), defaultUnitMetadata); logger.info("{}: Wrote 'unit={}' to metadata.", itemName, unit); From e4e23e3ccb2a67aab305ac6d1de412226d004684 Mon Sep 17 00:00:00 2001 From: lolodomo Date: Wed, 10 May 2023 15:56:25 +0200 Subject: [PATCH 071/126] Fix failed persistence tests (#3601) --- .../PersistenceThresholdFilterTest.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceThresholdFilterTest.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceThresholdFilterTest.java index bcefefb6cfd..15cef6acc65 100644 --- a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceThresholdFilterTest.java +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceThresholdFilterTest.java @@ -14,30 +14,59 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.when; import java.math.BigDecimal; import java.util.stream.Stream; +import javax.measure.quantity.ElectricCurrent; +import javax.measure.quantity.ElectricPotential; +import javax.measure.quantity.Length; +import javax.measure.quantity.Pressure; +import javax.measure.quantity.Temperature; + import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.openhab.core.i18n.UnitProvider; import org.openhab.core.library.items.NumberItem; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.SIUnits; import org.openhab.core.types.State; import org.openhab.core.types.util.UnitUtils; +import tech.units.indriya.unit.Units; + /** * The {@link PersistenceThresholdFilterTest} contains tests for {@link PersistenceThresholdFilter} * * @author Jan N. Klug - Initial contribution */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) @NonNullByDefault public class PersistenceThresholdFilterTest { private static final String ITEM_NAME_1 = "itemName1"; private static final String ITEM_NAME_2 = "itemName2"; + private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock; + + @BeforeEach + public void setup() { + when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS); + when(unitProviderMock.getUnit(Pressure.class)).thenReturn(SIUnits.PASCAL); + when(unitProviderMock.getUnit(Length.class)).thenReturn(SIUnits.METRE); + when(unitProviderMock.getUnit(ElectricCurrent.class)).thenReturn(Units.AMPERE); + when(unitProviderMock.getUnit(ElectricPotential.class)).thenReturn(Units.VOLT); + } @Test public void differentItemSameValue() { @@ -82,8 +111,8 @@ private void filterTest(String item2name, State state1, State state2, String uni itemType += ":" + UnitUtils.getDimensionName(q.getUnit()); } - NumberItem item1 = new NumberItem(itemType, PersistenceThresholdFilterTest.ITEM_NAME_1); - NumberItem item2 = new NumberItem(itemType, item2name); + NumberItem item1 = new NumberItem(itemType, PersistenceThresholdFilterTest.ITEM_NAME_1, unitProviderMock); + NumberItem item2 = new NumberItem(itemType, item2name, unitProviderMock); item1.setState(state1); item2.setState(state2); From 09f3de38893f2abf2587cc76167068e5c9bcaba2 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Thu, 11 May 2023 07:00:34 +0200 Subject: [PATCH 072/126] Upgrade SAT to 0.14.0 (#3599) Signed-off-by: Holger Friedrich --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 89b310b402f..450971d0248 100644 --- a/pom.xml +++ b/pom.xml @@ -74,7 +74,7 @@ 2.3.0 4.4.0 4.4.3 - 0.13.0 + 0.14.0 1.7.32 2.29.0 2.35.0 From c2ce05a55003ed7376f781be1a4ff9f9a4221455 Mon Sep 17 00:00:00 2001 From: Matthew Skinner Date: Thu, 11 May 2023 15:03:37 +1000 Subject: [PATCH 073/126] Bump Netty version to 4.1.92.Final (#3577) Bump Netty to latest versions. Signed-off-by: Matthew Skinner --- .../openhab-tp/src/main/feature/feature.xml | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/features/karaf/openhab-tp/src/main/feature/feature.xml b/features/karaf/openhab-tp/src/main/feature/feature.xml index f64dc3dd573..96bfdbc5a43 100644 --- a/features/karaf/openhab-tp/src/main/feature/feature.xml +++ b/features/karaf/openhab-tp/src/main/feature/feature.xml @@ -155,23 +155,23 @@ - openhab.tp;feature=netty;version=4.1.72.Final - mvn:io.netty/netty-buffer/4.1.72.Final - mvn:io.netty/netty-common/4.1.72.Final - mvn:io.netty/netty-codec/4.1.72.Final - mvn:io.netty/netty-codec-http/4.1.72.Final - mvn:io.netty/netty-codec-http2/4.1.72.Final - mvn:io.netty/netty-codec-mqtt/4.1.72.Final - mvn:io.netty/netty-codec-socks/4.1.72.Final - mvn:io.netty/netty-handler/4.1.72.Final - mvn:io.netty/netty-handler-proxy/4.1.72.Final - mvn:io.netty/netty-resolver/4.1.72.Final - mvn:io.netty/netty-transport/4.1.72.Final - mvn:io.netty/netty-transport-native-epoll/4.1.72.Final - mvn:io.netty/netty-transport-native-kqueue/4.1.72.Final - mvn:io.netty/netty-transport-native-unix-common/4.1.72.Final - mvn:io.netty/netty-transport-classes-epoll/4.1.72.Final - mvn:io.netty/netty-tcnative-classes/2.0.46.Final + openhab.tp;feature=netty;version=4.1.92.Final + mvn:io.netty/netty-buffer/4.1.92.Final + mvn:io.netty/netty-common/4.1.92.Final + mvn:io.netty/netty-codec/4.1.92.Final + mvn:io.netty/netty-codec-http/4.1.92.Final + mvn:io.netty/netty-codec-http2/4.1.92.Final + mvn:io.netty/netty-codec-mqtt/4.1.92.Final + mvn:io.netty/netty-codec-socks/4.1.92.Final + mvn:io.netty/netty-handler/4.1.92.Final + mvn:io.netty/netty-handler-proxy/4.1.92.Final + mvn:io.netty/netty-resolver/4.1.92.Final + mvn:io.netty/netty-transport/4.1.92.Final + mvn:io.netty/netty-transport-native-epoll/4.1.92.Final + mvn:io.netty/netty-transport-native-kqueue/4.1.92.Final + mvn:io.netty/netty-transport-native-unix-common/4.1.92.Final + mvn:io.netty/netty-transport-classes-epoll/4.1.92.Final + mvn:io.netty/netty-tcnative-classes/2.0.60.Final From fbd7f9cbff4f22e855b1a9d8906bf0f186ed7b81 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Fri, 12 May 2023 22:22:38 +0200 Subject: [PATCH 074/126] Fix UpgradeTool and enable default=all policy (#3607) Signed-off-by: Jan N. Klug --- .../main/java/org/openhab/core/tools/UpgradeTool.java | 10 +++++++--- .../java/org/openhab/core/tools/internal/Upgrader.java | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java b/tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java index 7df66d741e5..b310d813979 100644 --- a/tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java +++ b/tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java @@ -42,7 +42,8 @@ private static Options getOptions() { Options options = new Options(); options.addOption(Option.builder().longOpt(OPT_DIR).desc("directory to process").numberOfArgs(1).build()); - options.addOption(Option.builder().longOpt(OPT_COMMAND).numberOfArgs(1).desc("command to execute").build()); + options.addOption(Option.builder().longOpt(OPT_COMMAND).numberOfArgs(1) + .desc("command to execute (executes all if omitted)").build()); options.addOption(Option.builder().longOpt(OPT_LOG).numberOfArgs(1).desc("log verbosity").build()); options.addOption(Option.builder().longOpt(OPT_FORCE).desc("force execution (even if already done)").build()); @@ -73,9 +74,12 @@ public static void main(String[] args) { boolean force = commandLine.hasOption(OPT_FORCE) ? true : false; Upgrader upgrader = new Upgrader(baseDir, force); - if (commandLine.hasOption(ITEM_COPY_UNIT_TO_METADATA)) { + if (!commandLine.hasOption(OPT_COMMAND) + || ITEM_COPY_UNIT_TO_METADATA.equals(commandLine.getOptionValue(OPT_COMMAND))) { upgrader.itemCopyUnitToMetadata(); - } else if (commandLine.hasOption(LINK_UPGRADE_JS_PROFILE)) { + } + if (!commandLine.hasOption(OPT_COMMAND) + || LINK_UPGRADE_JS_PROFILE.equals(commandLine.getOptionValue(OPT_COMMAND))) { upgrader.linkUpgradeJsProfile(); } } diff --git a/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java b/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java index be4094ab6ae..acf12f84999 100644 --- a/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java +++ b/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java @@ -67,7 +67,7 @@ private boolean checkUpgradeRecord(String key) { } public void itemCopyUnitToMetadata() { - if (checkUpgradeRecord(ITEM_COPY_UNIT_TO_METADATA)) { + if (!checkUpgradeRecord(ITEM_COPY_UNIT_TO_METADATA)) { return; } Path itemJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.items.Item.json"); @@ -125,7 +125,7 @@ public void itemCopyUnitToMetadata() { } public void linkUpgradeJsProfile() { - if (checkUpgradeRecord(LINK_UPGRADE_JS_PROFILE)) { + if (!checkUpgradeRecord(LINK_UPGRADE_JS_PROFILE)) { return; } From 08924232fc7d17d454a581163b06a183a9deda1d Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Sat, 13 May 2023 17:54:26 +0200 Subject: [PATCH 075/126] New Crowdin updates (#3589) * New translations DefaultSystemChannels.properties (Greek) * New translations restauth.properties (Greek) * New translations voice.properties (Greek) * New translations scriptprofile.properties (Greek) * New translations i18n.properties (Greek) * New translations validation.properties (Italian) * New translations validation.properties (French) --------- Signed-off-by: J-N-K Co-authored-by: J-N-K --- .../OH-INF/i18n/scriptprofile_el.properties | 4 ++++ .../OH-INF/i18n/validation_fr.properties | 4 ++++ .../OH-INF/i18n/validation_it.properties | 2 ++ .../OH-INF/i18n/restauth_el.properties | 2 ++ .../i18n/DefaultSystemChannels_el.properties | 11 ++++++++++ .../resources/OH-INF/i18n/voice_el.properties | 20 +++++++++++-------- .../resources/OH-INF/i18n/i18n_el.properties | 4 ++-- 7 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_el.properties diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_el.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_el.properties new file mode 100644 index 00000000000..b8b6b6a13c9 --- /dev/null +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_el.properties @@ -0,0 +1,4 @@ +profile.system.script.toItemScript.label = Μετασχηματισμός Thing σε Item +profile.system.script.toItemScript.description = Το Σενάριο ενεργειών για τη μετατροπή ενημερώσεων/εντολών κατάστασης από το Thing handler προς το Item. Το σενάριο μπορεί να επιστρέψει με απροσδιόριστη έξοδο (null) για να απορρίψει τις ενημερώσεις/εντολές και να μην τις περάσει. +profile.system.script.toHandlerScript.label = Μετασχηματισμός Item σε Thing +profile.system.script.toHandlerScript.description = Το Σενάριο ενεργειών για τη μετατροπή εντολών κατάστασης από το Item προς το Thing handler. Το σενάριο μπορεί να επιστρέψει με απροσδιόριστη έξοδο (null) για να απορρίψει τις εντολές και να μην τις περάσει. diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_fr.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_fr.properties index 456b27dca4e..4ff7be44ec1 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_fr.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_fr.properties @@ -11,3 +11,7 @@ min_value_numeric_violated=La valeur ne doit pas être inférieure à {0}. pattern_violated=La valeur {0} ne correspond pas au modèle {1}. options_violated=La valeur {0} ne correspond pas aux options de paramètre autorisées. Les options autorisées sont \: {1} multiple_limit_violated=Seuls {0} éléments sont autorisés alors que {1} sont fournis. + +bridge_not_configured=La configuration d'un pont de connexion est obligatoire. +type_description_missing=La description de type {0} pour {1} n''a pas été trouvée, bien que nous ayons vérifié sa présence avant. +config_description_missing=La description de configuration {0} pour {1} n''a pas été trouvée, bien que nous ayons vérifié sa présence avant. diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_it.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_it.properties index c3e941a1a42..1c331c64f92 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_it.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_it.properties @@ -13,3 +13,5 @@ options_violated=Il valore {0} non è tra le opzioni consentite per il parametro multiple_limit_violated=Sono consentiti {0} elementi, ma ne sono stati forniti {1}. bridge_not_configured=Configurare un bridge è obbligatorio. +type_description_missing=Descrizione del tipo {0} per {1} non trovata, anche se prima abbiamo controllato la presenza. +config_description_missing=Descrizione di configurazione {0} per {1} non trovata, anche se abbiamo prima controllato la presenza. diff --git a/bundles/org.openhab.core.io.rest.auth/src/main/resources/OH-INF/i18n/restauth_el.properties b/bundles/org.openhab.core.io.rest.auth/src/main/resources/OH-INF/i18n/restauth_el.properties index 87026d71635..1ae87dde61d 100644 --- a/bundles/org.openhab.core.io.rest.auth/src/main/resources/OH-INF/i18n/restauth_el.properties +++ b/bundles/org.openhab.core.io.rest.auth/src/main/resources/OH-INF/i18n/restauth_el.properties @@ -4,5 +4,7 @@ system.config.restauth.cacheExpiration.label = Χρόνος λήξης της π system.config.restauth.cacheExpiration.description = Όταν είναι ενεργοποιημένος ο βασικός έλεγχος ταυτότητας, τα διαπιστευτήρια τοποθετούνται σε μια προσωρινή μνήμη για να επιταχυνθεί η εξουσιοδότηση αιτήματος. Οι καταχωρήσεις στην προσωρινή μνήμη λήγουν μετά από λίγο, προκειμένου να μην διατηρούνται τα διαπιστευτήρια στη μνήμη επ 'αόριστον. Αυτή η τιμή καθορίζει τον χρόνο λήξης σε ώρες. Ορίστε το στο 0 για απενεργοποίηση της προσωρινής μνήμης. system.config.restauth.implicitUserRole.label = Έμμεσος ρόλος χρήστη system.config.restauth.implicitUserRole.description = Από προεπιλογή, οι λειτουργίες που απαιτούν τον ρόλο "χρήστη" είναι διαθέσιμες και χωρίς πιστοποίηση. Η απενεργοποίηση αυτής της επιλογής θα επιβάλει εξουσιοδότηση για αυτές τις λειτουργίες. Προειδοποίηση\: Αυτό προκαλεί διακοπή των πελατών που δεν υποστηρίζουν έλεγχο ταυτότητας. +system.config.restauth.trustedNetworks.label = Αξιόπιστα Δίκτυα +system.config.restauth.trustedNetworks.description = Έμμεση εκχώρηση πρόσβασης χρήστη σε αιτήματα που προέρχονται από αυτά τα δίκτυα. Συμπληρώστε λίστα CIDR διαχωρισμένη με κόμματα (αγνοείται αν είναι ενεργοποιημένη η επιλογή "Implicit User Role"). service.system.restauth.label = Ασφάλεια API diff --git a/bundles/org.openhab.core.thing/src/main/resources/OH-INF/i18n/DefaultSystemChannels_el.properties b/bundles/org.openhab.core.thing/src/main/resources/OH-INF/i18n/DefaultSystemChannels_el.properties index 334158715db..e39331c1002 100644 --- a/bundles/org.openhab.core.thing/src/main/resources/OH-INF/i18n/DefaultSystemChannels_el.properties +++ b/bundles/org.openhab.core.thing/src/main/resources/OH-INF/i18n/DefaultSystemChannels_el.properties @@ -1,11 +1,14 @@ channel-type.system.signal-strength.label = Ισχύς Σήματος +channel-type.system.signal-strength.description = Ισχύς σήματος με τιμές 0 (χειρότερη), 1, 2, 3 ή 4 (καλύτερη) channel-type.system.signal-strength.state.option.0 = κανένα σήμα channel-type.system.signal-strength.state.option.1 = αδύναμο channel-type.system.signal-strength.state.option.2 = μέτριο channel-type.system.signal-strength.state.option.3 = καλό channel-type.system.signal-strength.state.option.4 = εξαιρετικό channel-type.system.low-battery.label = Χαμηλή Μπαταρία +channel-type.system.low-battery.description = Προειδοποίηση χαμηλής στάθμης μπαταρίας με τιμές on (χαμηλή μπαταρία) ή off (επαρκής μπαταρία) channel-type.system.battery-level.label = Επίπεδο μπαταρίας +channel-type.system.battery-level.description = Επίπεδο μπαταρίας ως ποσοστό (0-100%) channel-type.system.trigger.label = Ενεργοποίηση channel-type.system.rawbutton.label = Σκέτο Κουμπί channel-type.system.button.label = Κουμπί @@ -45,3 +48,11 @@ channel-type.system.atmospheric-humidity.label = Ατμοσφαιρική Υγρ channel-type.system.atmospheric-humidity.description = Τρέχουσα ατμοσφαιρική σχετική υγρασία channel-type.system.barometric-pressure.label = Βαρομετρική Πίεση channel-type.system.barometric-pressure.description = Τρέχουσα βαρομετρική πίεση +channel-type.system.electric-power.label = Ηλεκτρική Ισχύς +channel-type.system.electric-power.description = Τρέχουσα ηλεκτρική ισχύς +channel-type.system.electric-current.label = Ηλεκτρική Ένταση +channel-type.system.electric-current.description = Τρέχουσα ηλεκτρική ένταση +channel-type.system.electric-voltage.label = Ηλεκτρική Τάση +channel-type.system.electric-voltage.description = Τρέχουσα ηλεκτρική τάση +channel-type.system.electrical-energy.label = Ηλεκτρική Ενέργεια +channel-type.system.electrical-energy.description = Τρέχουσα ηλεκτρική ενέργεια diff --git a/bundles/org.openhab.core.voice/src/main/resources/OH-INF/i18n/voice_el.properties b/bundles/org.openhab.core.voice/src/main/resources/OH-INF/i18n/voice_el.properties index 9b999a64761..918ece1a1a7 100644 --- a/bundles/org.openhab.core.voice/src/main/resources/OH-INF/i18n/voice_el.properties +++ b/bundles/org.openhab.core.voice/src/main/resources/OH-INF/i18n/voice_el.properties @@ -1,20 +1,24 @@ -system.config.voice.defaultTTS.label = Προεπιλεγμένο Κείμενο-Σε-Ομιλία -system.config.voice.defaultTTS.description = Η προεπιλεγμένη υπηρεσία κειμένου-σε-ομιλία (TTS) που θα χρησιμοποιηθεί αν δεν οριστεί κάποια άλλη. -system.config.voice.defaultSTT.label = Προεπιλεγμένη Ομιλία-Σε-Κείμενο -system.config.voice.defaultSTT.description = Η προεπιλεγμένη υπηρεσία ομιλίας-σε-κείμενο (STT) που θα χρησιμοποιηθεί αν δεν έχει καθοριστεί άλλη υπηρεσία. -system.config.voice.defaultVoice.label = Προεπιλεγμένη Φωνή -system.config.voice.defaultVoice.description = Η προεπιλεγμένη φωνή που θα χρησιμοποιηθεί αν δεν έχει καθοριστεί συγκεκριμένη υπηρεσία TTS ή φωνή. system.config.voice.defaultHLI.label = Προεπιλεγμένος Διερμηνέας Ανθρώπινης Γλώσσας system.config.voice.defaultHLI.description = Ο προεπιλεγμένος διερμηνέας ανθρώπινης γλώσσας που θα χρησιμοποιηθεί εάν δεν έχει καθοριστεί άλλος. system.config.voice.defaultKS.label = Προεπιλεγμένος Εντοπισμός Λέξεων-Κλειδιών system.config.voice.defaultKS.description = Η προεπιλεγμένη υπηρεσία εντοπισμού λέξεων-κλειδιών για χρήση εάν δεν έχει καθοριστεί άλλη. +system.config.voice.defaultSTT.label = Προεπιλεγμένη Ομιλία-Σε-Κείμενο +system.config.voice.defaultSTT.description = Η προεπιλεγμένη υπηρεσία ομιλίας-σε-κείμενο (STT) που θα χρησιμοποιηθεί αν δεν έχει καθοριστεί άλλη υπηρεσία. +system.config.voice.defaultTTS.label = Προεπιλεγμένο Κείμενο-Σε-Ομιλία +system.config.voice.defaultTTS.description = Η προεπιλεγμένη υπηρεσία κειμένου-σε-ομιλία (TTS) που θα χρησιμοποιηθεί αν δεν οριστεί κάποια άλλη. +system.config.voice.defaultVoice.label = Προεπιλεγμένη Φωνή +system.config.voice.defaultVoice.description = Η προεπιλεγμένη φωνή που θα χρησιμοποιηθεί αν δεν έχει καθοριστεί συγκεκριμένη υπηρεσία TTS ή φωνή. system.config.voice.keyword.label = Μαγική Λέξη system.config.voice.keyword.description = Η μαγική λέξη που θα αναγνωρίζεται και θα σηματοδοτεί την έναρξη του διαλόγου. system.config.voice.listeningItem.label = Διακόπτης Ακρόασης system.config.voice.listeningItem.description = Αν συμπληρωθεί, αυτό το αντικείμενο θα είναι ενεργό κατά την περίοδο κατά την οποία ο επεξεργαστής διαλόγου έχει εντοπίσει τη λέξη-κλειδί και ακούει για εντολές. - -service.system.voice.label = Φωνή +system.config.voice.listeningMelody.label = Μελωδία ειδοποίησης +system.config.voice.listeningMelody.description = Μια μελωδία που θα παιχτεί για να ειδοποιήσει το χρήστη όταν πρόκειται να ξεκινήσει η επεξεργασία διαλόγου. Αφήστε το κενό για να το απενεργοποιήσετε. (Χρησιμοποιήστε διαχωρισμένη λίστα σημειώσεων. Παράδειγμα\: "A O\:100 A'\:50") +system.config.voice.listeningMelody.option.Bb = Bb +system.config.voice.listeningMelody.option.F\# = F\# +system.config.voice.listeningMelody.option.E = E error.ks-error = Παρουσιάστηκε σφάλμα κατά τον εντοπισμό λέξεων-κλειδιών, {0} error.stt-error = Παρουσιάστηκε σφάλμα κατά την αναγνώριση κειμένου, {0} error.stt-exception = Σφάλμα κατά την αναγνώριση, {0} +service.system.voice.label = Φωνή diff --git a/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/i18n_el.properties b/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/i18n_el.properties index 037536bc483..37d95af1a68 100644 --- a/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/i18n_el.properties +++ b/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/i18n_el.properties @@ -1,7 +1,7 @@ system.config.i18n.language.label = Γλώσσα system.config.i18n.language.description = Η προεπιλεγμένη γλώσσα που θα χρησιμοποιηθεί. Αν δεν καθοριστεί, χρησιμοποιείται η προεπιλεγμένη γλώσσα συστήματος. -system.config.i18n.script.label = Δέσμη ενεργειών -system.config.i18n.script.description = Η δέσμη ενεργειών που θα πρέπει να χρησιμοποιηθεί. +system.config.i18n.script.label = Σενάριο +system.config.i18n.script.description = Το σενάριο ενεργειών που θα πρέπει να χρησιμοποιηθεί. system.config.i18n.region.label = Χώρα / Περιοχή system.config.i18n.region.description = Η περιοχή που θα πρέπει να χρησιμοποιηθεί. system.config.i18n.variant.label = Παραλλαγή From 0f1f1729b78785d950a426a0ff345ee0909e2d5f Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 13 May 2023 20:50:53 +0200 Subject: [PATCH 076/126] Reduce loglevel in UnitUtils (#3609) The log level was increased during the UoM refactoring but it turns out that a lot of code depends on failing silently. Therefore the log level is again reduced to DEBUG. Signed-off-by: Jan N. Klug --- .../src/main/java/org/openhab/core/types/util/UnitUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/types/util/UnitUtils.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/types/util/UnitUtils.java index be835a64a64..3c312039ee1 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/types/util/UnitUtils.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/types/util/UnitUtils.java @@ -173,7 +173,7 @@ public class UnitUtils { return quantity.getUnit(); } catch (IllegalArgumentException | MeasurementParseException e) { // we expect this exception in case the extracted string does not match any known unit - LOGGER.error("Unknown unit from pattern: {}", unitSymbol); + LOGGER.debug("Unknown unit from pattern: {}", unitSymbol); } } From 55d6d21f1f75ac4ee143d4319f564236017711e1 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 13 May 2023 20:55:17 +0200 Subject: [PATCH 077/126] Fix marketplace add-on services fail after upgrade (#3572) * Fix marketplace add-on services fail after upgrade A format change in the database (related to the introduction of addon.xml) leads to a non-migrateable and permanent error that floods the log with exceptions and malfunctions. If such an error is detected, the database is purged and an error logged. Signed-off-by: Jan N. Klug --- .../marketplace/AbstractRemoteAddonService.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/AbstractRemoteAddonService.java b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/AbstractRemoteAddonService.java index 20736dd5028..b07f57086dd 100644 --- a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/AbstractRemoteAddonService.java +++ b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/AbstractRemoteAddonService.java @@ -45,6 +45,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; /** * The {@link AbstractRemoteAddonService} implements basic functionality of a remote add-on-service @@ -90,8 +91,16 @@ public void refreshSource() { return; } List addons = new ArrayList<>(); - installedAddonStorage.stream().map(e -> Objects.requireNonNull(gson.fromJson(e.getValue(), Addon.class))) - .forEach(addons::add); + try { + installedAddonStorage.stream().map(e -> Objects.requireNonNull(gson.fromJson(e.getValue(), Addon.class))) + .forEach(addons::add); + } catch (JsonSyntaxException e) { + List.copyOf(installedAddonStorage.getKeys()).forEach(installedAddonStorage::remove); + logger.error( + "Failed to read JSON database, trying to purge it. You might need to re-install {} from the '{}' service.", + installedAddonStorage.getKeys(), getId()); + refreshSource(); + } // create lookup list to make sure installed addons take precedence List installedAddons = addons.stream().map(Addon::getUid).collect(Collectors.toList()); From 6eb49577fe9ad56ddb8f899c52d43b2c145d1dda Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 14 May 2023 18:43:43 +0200 Subject: [PATCH 078/126] Fix UpgradeTool execution log (#3614) It seems that in some cases GSON is not able to correctly serialize/deserialize ZonedDateTime. Since we do not depend on the value in any case (it's just informational), we can store a String instead. A missing metadata database also failed the check when upgrading items. This has been fixed, too. Signed-off-by: Jan N. Klug --- .../java/org/openhab/core/tools/internal/Upgrader.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java b/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java index acf12f84999..1492aef395d 100644 --- a/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java +++ b/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java @@ -78,7 +78,8 @@ public void itemCopyUnitToMetadata() { logger.error("Cannot access item database '{}', check path and access rights.", itemJsonDatabasePath); return; } - if (!Files.isWritable(metadataJsonDatabasePath)) { + // missing metadata database is also fine, we create one in that case + if (!Files.isWritable(metadataJsonDatabasePath) && Files.exists(metadataJsonDatabasePath)) { logger.error("Cannot access metadata database '{}', check path and access rights.", metadataJsonDatabasePath); return; @@ -164,10 +165,10 @@ public void linkUpgradeJsProfile() { } private static class UpgradeRecord { - public final ZonedDateTime executionDate; + public final String executionDate; public UpgradeRecord(ZonedDateTime executionDate) { - this.executionDate = executionDate; + this.executionDate = executionDate.toString(); } } } From f92424c0b8c1b586d6c54a0345800049b8e48445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20B=C3=B6hm?= Date: Wed, 17 May 2023 11:47:53 +0200 Subject: [PATCH 079/126] Add unit rpm (#3616) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hans Böhm --- .../src/main/java/org/openhab/core/library/unit/Units.java | 3 +++ .../test/java/org/openhab/core/library/unit/UnitsTest.java | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/unit/Units.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/unit/Units.java index 3ce1cbaebc6..6d1800bca47 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/unit/Units.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/unit/Units.java @@ -139,6 +139,8 @@ public final class Units extends CustomUnits { new ProductUnit<>(VOLT_AMPERE.multiply(tech.units.indriya.unit.Units.HOUR)), Energy.class); public static final Unit NEWTON = addUnit(tech.units.indriya.unit.Units.NEWTON); public static final Unit HERTZ = addUnit(tech.units.indriya.unit.Units.HERTZ); + public static final Unit RPM = addUnit( + new ProductUnit<>(AbstractUnit.ONE.divide(tech.units.indriya.unit.Units.MINUTE))); public static final Unit IRRADIANCE = addUnit( new ProductUnit<>(tech.units.indriya.unit.Units.WATT.divide(tech.units.indriya.unit.Units.SQUARE_METRE))); public static final Unit MICROWATT_PER_SQUARE_CENTIMETRE = addUnit( @@ -276,6 +278,7 @@ public final class Units extends CustomUnits { SimpleUnitFormat.getInstance().label(PEBIBYTE, "PiB"); SimpleUnitFormat.getInstance().alias(PEBIBYTE, "Pio"); SimpleUnitFormat.getInstance().label(PETABIT, "Pbit"); + SimpleUnitFormat.getInstance().label(RPM, "rpm"); SimpleUnitFormat.getInstance().label(STANDARD_GRAVITY, "gₙ"); SimpleUnitFormat.getInstance().label(SIEMENS_PER_METRE, "S/m"); SimpleUnitFormat.getInstance().label(TERABYTE, "TB"); diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/unit/UnitsTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/unit/UnitsTest.java index 14d995c3361..8e6bae95a9d 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/unit/UnitsTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/unit/UnitsTest.java @@ -387,6 +387,13 @@ public void testConductivity() { assertThat(converted.toString(), anyOf(is("10000 \u00B5S/cm"), is("10000 \u03BCS/cm"))); } + @Test + public void testRpm() { + QuantityType oneHertz = QuantityType.valueOf("60 rpm"); + QuantityType converted = oneHertz.toUnit("Hz"); + assertThat(converted.doubleValue(), is(closeTo(1.00, DEFAULT_ERROR))); + } + private static class QuantityEquals extends IsEqual> { private Quantity quantity; From d87007a585e7797a90172aba1bc60c30855d38c4 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Thu, 18 May 2023 11:00:45 +0200 Subject: [PATCH 080/126] Fix only first group config considered in persistence manager (#3618) Signed-off-by: Jan N. Klug --- .../openhab/core/persistence/internal/PersistenceManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManager.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManager.java index 69b7fae7c66..9f0fc9b94ab 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManager.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManager.java @@ -189,8 +189,8 @@ private boolean appliesToItem(PersistenceItemConfiguration itemConfig, Item item } else if (itemCfg instanceof PersistenceGroupConfig) { try { Item gItem = itemRegistry.getItem(((PersistenceGroupConfig) itemCfg).getGroup()); - if (gItem instanceof GroupItem) { - return ((GroupItem) gItem).getAllMembers().contains(item); + if (gItem instanceof GroupItem gItem2 && gItem2.getAllMembers().contains(item)) { + return true; } } catch (ItemNotFoundException e) { // do nothing From 3eca30f2c1c233869fcd23312dd166cd213e1777 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 20 May 2023 09:38:28 +0200 Subject: [PATCH 081/126] Fix Upgradetool does not remember last executed step (#3621) Signed-off-by: Jan N. Klug --- .../src/main/java/org/openhab/core/tools/internal/Upgrader.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java b/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java index 1492aef395d..789a0a7b41f 100644 --- a/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java +++ b/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java @@ -123,6 +123,7 @@ public void itemCopyUnitToMetadata() { metadataStorage.flush(); upgradeRecords.put(ITEM_COPY_UNIT_TO_METADATA, new UpgradeRecord(ZonedDateTime.now())); + upgradeRecords.flush(); } public void linkUpgradeJsProfile() { @@ -162,6 +163,7 @@ public void linkUpgradeJsProfile() { linkStorage.flush(); upgradeRecords.put(LINK_UPGRADE_JS_PROFILE, new UpgradeRecord(ZonedDateTime.now())); + upgradeRecords.flush(); } private static class UpgradeRecord { From cd7840dc63ccf9ea9b8924faa9845ab182002f28 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 20 May 2023 09:42:58 +0200 Subject: [PATCH 082/126] Add dimension RadiationSpecificActivity (#3608) * Add dimension RadiationSpecificActivity This can be used e.g. for radon monitoring. The unit Bq/m3 was wrongly assigned to the dimension `Density` before. Signed-off-by: Jan N. Klug * improvement Signed-off-by: Jan N. Klug --- .../core/internal/i18n/I18nProviderImpl.java | 2 ++ .../dimension/RadiationSpecificActivity.java | 26 +++++++++++++++++++ .../org/openhab/core/library/unit/Units.java | 14 +++++++++- .../openhab/core/library/unit/UnitsTest.java | 7 +++++ 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 bundles/org.openhab.core/src/main/java/org/openhab/core/library/dimension/RadiationSpecificActivity.java diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java index 1839bd79c89..e767b266f8d 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java @@ -73,6 +73,7 @@ import org.openhab.core.library.dimension.Density; import org.openhab.core.library.dimension.ElectricConductivity; import org.openhab.core.library.dimension.Intensity; +import org.openhab.core.library.dimension.RadiationSpecificActivity; import org.openhab.core.library.dimension.VolumetricFlowRate; import org.openhab.core.library.types.PointType; import org.openhab.core.library.unit.ImperialUnits; @@ -416,6 +417,7 @@ public static Map>, Map { +} diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/unit/Units.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/unit/Units.java index 6d1800bca47..ce910fe11a7 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/unit/Units.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/unit/Units.java @@ -55,6 +55,7 @@ import org.openhab.core.library.dimension.Density; import org.openhab.core.library.dimension.ElectricConductivity; import org.openhab.core.library.dimension.Intensity; +import org.openhab.core.library.dimension.RadiationSpecificActivity; import org.openhab.core.library.dimension.VolumetricFlowRate; import si.uom.NonSI; @@ -160,8 +161,14 @@ public final class Units extends CustomUnits { MultiplyConverter.ofRational(BigInteger.valueOf(100000), BigInteger.ONE))); public static final Unit MILLIBAR = addUnit(MetricPrefix.MILLI(BAR)); public static final Unit BECQUEREL = addUnit(tech.units.indriya.unit.Units.BECQUEREL); - public static final Unit BECQUEREL_PER_CUBIC_METRE = addUnit(new ProductUnit<>( + public static final Unit CURIE = addUnit(NonSI.CURIE); + public static final Unit MILLI_CURIE = addUnit(MetricPrefix.MILLI(CURIE)); + public static final Unit MICRO_CURIE = addUnit(MetricPrefix.MICRO(CURIE)); + public static final Unit NANO_CURIE = addUnit(MetricPrefix.NANO(CURIE)); + public static final Unit PICO_CURIE = addUnit(MetricPrefix.PICO(CURIE)); + public static final Unit BECQUEREL_PER_CUBIC_METRE = addUnit(new ProductUnit<>( tech.units.indriya.unit.Units.BECQUEREL.divide(tech.units.indriya.unit.Units.CUBIC_METRE))); + public static final Unit GRAY = addUnit(tech.units.indriya.unit.Units.GRAY); public static final Unit SIEVERT = addUnit(tech.units.indriya.unit.Units.SIEVERT); public static final Unit MILLIMETRE_PER_HOUR = addUnit( @@ -234,6 +241,11 @@ public final class Units extends CustomUnits { SimpleUnitFormat.getInstance().label(BIT_PER_SECOND, "bit/s"); SimpleUnitFormat.getInstance().label(BYTE, "B"); SimpleUnitFormat.getInstance().alias(BYTE, "o"); + SimpleUnitFormat.getInstance().label(CURIE, "Ci"); + SimpleUnitFormat.getInstance().label(MILLI_CURIE, "mCi"); + SimpleUnitFormat.getInstance().label(MICRO_CURIE, "µCi"); + SimpleUnitFormat.getInstance().label(NANO_CURIE, "nCi"); + SimpleUnitFormat.getInstance().label(PICO_CURIE, "pCi"); SimpleUnitFormat.getInstance().label(CUBICMETRE_PER_DAY, "m³/d"); SimpleUnitFormat.getInstance().label(CUBICMETRE_PER_HOUR, "m³/h"); SimpleUnitFormat.getInstance().label(CUBICMETRE_PER_MINUTE, "m³/min"); diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/unit/UnitsTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/unit/UnitsTest.java index 8e6bae95a9d..68bdd23b414 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/unit/UnitsTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/unit/UnitsTest.java @@ -387,6 +387,13 @@ public void testConductivity() { assertThat(converted.toString(), anyOf(is("10000 \u00B5S/cm"), is("10000 \u03BCS/cm"))); } + @Test + public void testSpecificActivity() { + QuantityType radon = QuantityType.valueOf("37 kBq/m³"); + QuantityType converted = radon.toUnit("nCi/l"); + assertThat(converted.doubleValue(), is(closeTo(1.00, DEFAULT_ERROR))); + } + @Test public void testRpm() { QuantityType oneHertz = QuantityType.valueOf("60 rpm"); From a12d64d4a350e12ea66ef65b77120ed88653976b Mon Sep 17 00:00:00 2001 From: Kai Kreuzer Date: Tue, 23 May 2023 09:10:54 +0200 Subject: [PATCH 083/126] Update to SAT version 0.15.0 (#3623) Signed-off-by: Kai Kreuzer --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 450971d0248..9743bef665c 100644 --- a/pom.xml +++ b/pom.xml @@ -74,7 +74,7 @@ 2.3.0 4.4.0 4.4.3 - 0.14.0 + 0.15.0 1.7.32 2.29.0 2.35.0 From 8e81bebd3a9df4cf57e132b4b0aa3c5328abff50 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Thu, 25 May 2023 08:36:45 +0200 Subject: [PATCH 084/126] Fix exception in TransformationHelper (#3627) Signed-off-by: Jan N. Klug --- .../java/org/openhab/core/transform/TransformationHelper.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationHelper.java b/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationHelper.java index e39202afe96..93500be5e19 100644 --- a/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationHelper.java +++ b/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationHelper.java @@ -125,6 +125,8 @@ public static boolean isTransform(String pattern) { return service.transform(function, value); } catch (IllegalFormatException e) { throw new TransformationException("Cannot format state '" + state + "' to format '" + format + "'", e); + } catch (RuntimeException e) { + throw new TransformationException("Transformation service threw an exception: " + e.getMessage(), e); } } } From 9e088f8c622094deb6844046064f5ab57f2b5ce7 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Thu, 25 May 2023 08:37:50 +0200 Subject: [PATCH 085/126] Move LSP service from misc to system (#3626) This is needed to clean-up UI. The categories should be used for add-ons while core services should use `system`. Signed-off-by: Jan N. Klug --- .../org/openhab/core/model/lsp/internal/ModelServer.java | 4 ++-- .../src/main/resources/OH-INF/config/lsp.xml | 2 +- .../src/main/resources/OH-INF/i18n/lsp.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_cs.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_da.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_de.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_el.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_fi.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_fr.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_he.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_hu.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_it.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_nl.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_no.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_pl.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_pt.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_ru.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_sl.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_sv.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_uk.properties | 6 +++--- .../src/main/resources/OH-INF/i18n/lsp_zh.properties | 6 +++--- 21 files changed, 60 insertions(+), 60 deletions(-) diff --git a/bundles/org.openhab.core.model.lsp/src/main/java/org/openhab/core/model/lsp/internal/ModelServer.java b/bundles/org.openhab.core.model.lsp/src/main/java/org/openhab/core/model/lsp/internal/ModelServer.java index 1e4e4e3ad66..7880c12c6ff 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/java/org/openhab/core/model/lsp/internal/ModelServer.java +++ b/bundles/org.openhab.core.model.lsp/src/main/java/org/openhab/core/model/lsp/internal/ModelServer.java @@ -48,12 +48,12 @@ */ @Component(immediate = true, service = ModelServer.class, configurationPid = ModelServer.CONFIGURATION_PID, // property = Constants.SERVICE_PID + "=org.openhab.lsp") -@ConfigurableService(category = "misc", label = "Language Server (LSP)", description_uri = ModelServer.CONFIG_URI) +@ConfigurableService(category = "system", label = "Language Server (LSP)", description_uri = ModelServer.CONFIG_URI) @NonNullByDefault public class ModelServer { public static final String CONFIGURATION_PID = "org.openhab.lsp"; - protected static final String CONFIG_URI = "misc:lsp"; + protected static final String CONFIG_URI = "system:lsp"; private static final String KEY_PORT = "port"; private static final int DEFAULT_PORT = 5007; diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/config/lsp.xml b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/config/lsp.xml index 507810d6ec3..70b29e25704 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/config/lsp.xml +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/config/lsp.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd"> - + The port the language server listens to. diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp.properties index e6bee1709dd..a1fb00d9e2b 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Port -misc.config.lsp.port.description = The port the language server listens to. +system.config.lsp.port.label = Port +system.config.lsp.port.description = The port the language server listens to. -service.misc.lsp.label = Language Server (LSP) +service.system.lsp.label = Language Server (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_cs.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_cs.properties index 6c2af8f9ccb..04831656fd3 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_cs.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_cs.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Port -misc.config.lsp.port.description = Port, který jazykový server používá. +system.config.lsp.port.label = Port +system.config.lsp.port.description = Port, který jazykový server používá. -service.misc.lsp.label = Jazykový server (LSP) +service.system.lsp.label = Jazykový server (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_da.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_da.properties index 1953d0fba59..ab39460d890 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_da.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_da.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Port -misc.config.lsp.port.description = Porten som sprogserveren lytter til. +system.config.lsp.port.label = Port +system.config.lsp.port.description = Porten som sprogserveren lytter til. -service.misc.lsp.label = Sprogserver (LSP) +service.system.lsp.label = Sprogserver (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_de.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_de.properties index 319ecee5c9c..eff3513e2db 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_de.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_de.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Port -misc.config.lsp.port.description = Der Port, auf den der Sprachserver lauscht. +system.config.lsp.port.label = Port +system.config.lsp.port.description = Der Port, auf den der Sprachserver lauscht. -service.misc.lsp.label = Sprachserver (LSP) +service.system.lsp.label = Sprachserver (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_el.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_el.properties index ee8da43f595..8b21b7fe529 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_el.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_el.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Θύρα -misc.config.lsp.port.description = Η θύρα που ακούει ο διακομιστής γλώσσας. +system.config.lsp.port.label = Θύρα +system.config.lsp.port.description = Η θύρα που ακούει ο διακομιστής γλώσσας. -service.misc.lsp.label = Διακομιστής Γλώσσας (LSP) +service.system.lsp.label = Διακομιστής Γλώσσας (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_fi.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_fi.properties index daae62c58ed..a7394a0ea9b 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_fi.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_fi.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Portti -misc.config.lsp.port.description = Portti, jota kieli palvelin kuuntelee. +system.config.lsp.port.label = Portti +system.config.lsp.port.description = Portti, jota kieli palvelin kuuntelee. -service.misc.lsp.label = Kielipalvelin (LSP) +service.system.lsp.label = Kielipalvelin (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_fr.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_fr.properties index cdcabf0ef09..af911827b10 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_fr.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_fr.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Port -misc.config.lsp.port.description = Le port d'écoute du serveur de langage. +system.config.lsp.port.label = Port +system.config.lsp.port.description = Le port d'écoute du serveur de langage. -service.misc.lsp.label = Serveur de langage (LSP) +service.system.lsp.label = Serveur de langage (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_he.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_he.properties index 2710e0d5d40..94590ad9591 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_he.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_he.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = יציאה -misc.config.lsp.port.description = הפורט ששרת השפה יאזין אליו. +system.config.lsp.port.label = יציאה +system.config.lsp.port.description = הפורט ששרת השפה יאזין אליו. -service.misc.lsp.label = שרת השפה (LSP) +service.system.lsp.label = שרת השפה (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_hu.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_hu.properties index 85f26e99041..ce2337df0cf 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_hu.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_hu.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Port -misc.config.lsp.port.description = A nyelvi kiszolgáló portja. +system.config.lsp.port.label = Port +system.config.lsp.port.description = A nyelvi kiszolgáló portja. -service.misc.lsp.label = Nyelvi kiszolgáló (LSP) +service.system.lsp.label = Nyelvi kiszolgáló (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_it.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_it.properties index db03a4d8a05..fcc0176b2b9 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_it.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_it.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Porta -misc.config.lsp.port.description = La porta usata dal server della traduzione. +system.config.lsp.port.label = Porta +system.config.lsp.port.description = La porta usata dal server della traduzione. -service.misc.lsp.label = Server Traduzione (LSP) +service.system.lsp.label = Server Traduzione (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_nl.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_nl.properties index 8f82d4b9c5a..9652114f63c 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_nl.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_nl.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Poort -misc.config.lsp.port.description = De poort waarnaar de taalserver luistert. +system.config.lsp.port.label = Poort +system.config.lsp.port.description = De poort waarnaar de taalserver luistert. -service.misc.lsp.label = Taalserver (LSP) +service.system.lsp.label = Taalserver (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_no.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_no.properties index ae862f5d5af..e59cd166d8e 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_no.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_no.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Port -misc.config.lsp.port.description = Porten språkserveren lytter til. +system.config.lsp.port.label = Port +system.config.lsp.port.description = Porten språkserveren lytter til. -service.misc.lsp.label = Språkserver (LSP) +service.system.lsp.label = Språkserver (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_pl.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_pl.properties index 08b30d2127a..87f3a245c37 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_pl.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_pl.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Port -misc.config.lsp.port.description = Port na którym nasłuchuje serwer języka. +system.config.lsp.port.label = Port +system.config.lsp.port.description = Port na którym nasłuchuje serwer języka. -service.misc.lsp.label = Serwer języka (LSP) +service.system.lsp.label = Serwer języka (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_pt.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_pt.properties index 8e0ff03285f..09382c0de4a 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_pt.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_pt.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Porta -misc.config.lsp.port.description = A porta que o servidor de idioma escuta. +system.config.lsp.port.label = Porta +system.config.lsp.port.description = A porta que o servidor de idioma escuta. -service.misc.lsp.label = Servidor de Idioma (LSP) +service.system.lsp.label = Servidor de Idioma (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_ru.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_ru.properties index 5ca338e72ec..e14e07a7d9a 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_ru.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_ru.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Порт -misc.config.lsp.port.description = Порт языкового сервера. +system.config.lsp.port.label = Порт +system.config.lsp.port.description = Порт языкового сервера. -service.misc.lsp.label = Языковой сервер (LSP) +service.system.lsp.label = Языковой сервер (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_sl.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_sl.properties index 7bd4363f6e7..c56b851aaef 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_sl.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_sl.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Vrata -misc.config.lsp.port.description = Vrata, ki jim prisluškuje jezikovni strežnik. +system.config.lsp.port.label = Vrata +system.config.lsp.port.description = Vrata, ki jim prisluškuje jezikovni strežnik. -service.misc.lsp.label = jezikovni strežnik (LSP) +service.system.lsp.label = jezikovni strežnik (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_sv.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_sv.properties index 3008b5aafa6..3e40c806178 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_sv.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_sv.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Port -misc.config.lsp.port.description = Porten som språkservern lyssnar på. +system.config.lsp.port.label = Port +system.config.lsp.port.description = Porten som språkservern lyssnar på. -service.misc.lsp.label = Språkserver (LSP) +service.system.lsp.label = Språkserver (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_uk.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_uk.properties index b0e70c659f5..11727d0361e 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_uk.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_uk.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = Порт -misc.config.lsp.port.description = Порт, до якого слухає мова. +system.config.lsp.port.label = Порт +system.config.lsp.port.description = Порт, до якого слухає мова. -service.misc.lsp.label = Мовний сервер (LSP) +service.system.lsp.label = Мовний сервер (LSP) diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_zh.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_zh.properties index 9d89949c6c2..7cac582d089 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_zh.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_zh.properties @@ -1,4 +1,4 @@ -misc.config.lsp.port.label = 端口 -misc.config.lsp.port.description = 语言服务器监听的端口。 +system.config.lsp.port.label = 端口 +system.config.lsp.port.description = 语言服务器监听的端口。 -service.misc.lsp.label = 语言服务器 (LSP) +service.system.lsp.label = 语言服务器 (LSP) From 0df4857b11e5a655bb228ada65c2f3a0ceeea294 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Thu, 25 May 2023 08:38:59 +0200 Subject: [PATCH 086/126] Move RuleHLI service from voice to system (#3625) This is needed to clean-up UI. The categories should be used for add-ons while core services should use `system`. Signed-off-by: Jan N. Klug --- .../resources/OH-INF/config/hli.xml | 2 +- .../resources/OH-INF/i18n/hli.properties | 6 +++--- .../resources/OH-INF/i18n/hli_cs.properties | 6 +++--- .../resources/OH-INF/i18n/hli_da.properties | 6 +++--- .../resources/OH-INF/i18n/hli_de.properties | 6 +++--- .../resources/OH-INF/i18n/hli_el.properties | 6 +++--- .../resources/OH-INF/i18n/hli_fi.properties | 6 +++--- .../resources/OH-INF/i18n/hli_fr.properties | 6 +++--- .../resources/OH-INF/i18n/hli_he.properties | 6 +++--- .../resources/OH-INF/i18n/hli_hu.properties | 6 +++--- .../resources/OH-INF/i18n/hli_it.properties | 6 +++--- .../resources/OH-INF/i18n/hli_nl.properties | 6 +++--- .../resources/OH-INF/i18n/hli_no.properties | 6 +++--- .../resources/OH-INF/i18n/hli_pl.properties | 6 +++--- .../resources/OH-INF/i18n/hli_pt.properties | 6 +++--- .../resources/OH-INF/i18n/hli_ru.properties | 6 +++--- .../resources/OH-INF/i18n/hli_sl.properties | 6 +++--- .../resources/OH-INF/i18n/hli_sv.properties | 6 +++--- .../resources/OH-INF/i18n/hli_uk.properties | 6 +++--- .../resources/OH-INF/i18n/hli_zh.properties | 6 +++--- .../model/script/internal/RuleHumanLanguageInterpreter.java | 4 ++-- 21 files changed, 60 insertions(+), 60 deletions(-) diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/config/hli.xml b/bundles/org.openhab.core.model.script/resources/OH-INF/config/hli.xml index 374a3c292f1..eb0c526a0f8 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/config/hli.xml +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/config/hli.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd"> - + item String diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli.properties index 8fc054f3290..92b501469ef 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Voice Command Item -voice.config.rulehli.item.description = The String Item to pass voice commands to. +system.config.rulehli.item.label = Voice Command Item +system.config.rulehli.item.description = The String Item to pass voice commands to. -service.voice.rulehli.label = Rule Voice Interpreter +service.system.rulehli.label = Rule Voice Interpreter diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_cs.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_cs.properties index c19c263e058..b38bc645412 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_cs.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_cs.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Item hlasového příkazu -voice.config.rulehli.item.description = String Item, do které se vloží hlasový příkaz. +system.config.rulehli.item.label = Item hlasového příkazu +system.config.rulehli.item.description = String Item, do které se vloží hlasový příkaz. -service.voice.rulehli.label = Hlasový tlumočník +service.system.rulehli.label = Hlasový tlumočník diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_da.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_da.properties index 9bef37e15c4..f5d8d8eb9cb 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_da.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_da.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Stemmekommando-item -voice.config.rulehli.item.description = Streng-item'et der skal sendes stemmekommandoer til. +system.config.rulehli.item.label = Stemmekommando-item +system.config.rulehli.item.description = Streng-item'et der skal sendes stemmekommandoer til. -service.voice.rulehli.label = Regel-stemmefortolker +service.system.rulehli.label = Regel-stemmefortolker diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_de.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_de.properties index 75a09edd818..6d8864489eb 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_de.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_de.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Item für Sprachanweisungen -voice.config.rulehli.item.description = Das String-Item an das Sprachanweisungen übergeben werden sollen. +system.config.rulehli.item.label = Item für Sprachanweisungen +system.config.rulehli.item.description = Das String-Item an das Sprachanweisungen übergeben werden sollen. -service.voice.rulehli.label = Regelbasierter Sprachinterpreter +service.system.rulehli.label = Regelbasierter Sprachinterpreter diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_el.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_el.properties index be8e8687cbd..abdf9b8a44b 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_el.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_el.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Στοιχείο Φωνητικής Εντολής -voice.config.rulehli.item.description = Το στοιχείο κειμένου για να περάσει τις φωνητικές εντολές. +system.config.rulehli.item.label = Στοιχείο Φωνητικής Εντολής +system.config.rulehli.item.description = Το στοιχείο κειμένου για να περάσει τις φωνητικές εντολές. -service.voice.rulehli.label = Διερμηνέας Κανόνων Φωνής +service.system.rulehli.label = Διερμηνέας Κανόνων Φωνής diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_fi.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_fi.properties index 835267deeb0..a41f5218206 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_fi.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_fi.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Äänikomennon Item -voice.config.rulehli.item.description = Tekstimuotoinen Item, jolle äänikomennot välitetään. +system.config.rulehli.item.label = Äänikomennon Item +system.config.rulehli.item.description = Tekstimuotoinen Item, jolle äänikomennot välitetään. -service.voice.rulehli.label = Sääntöpohjainen äänitulkki +service.system.rulehli.label = Sääntöpohjainen äänitulkki diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_fr.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_fr.properties index 497269634f2..b8497096562 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_fr.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_fr.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Item de commande vocale -voice.config.rulehli.item.description = L'item pour passer des commandes vocales. +system.config.rulehli.item.label = Item de commande vocale +system.config.rulehli.item.description = L'item pour passer des commandes vocales. -service.voice.rulehli.label = Interprêteur de règle vocale +service.system.rulehli.label = Interprêteur de règle vocale diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_he.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_he.properties index 3d297fb1b78..c2c79e22310 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_he.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_he.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = פקודה קולית -voice.config.rulehli.item.description = ה-Item אליו יועברו הפקודות הקוליות. +system.config.rulehli.item.label = פקודה קולית +system.config.rulehli.item.description = ה-Item אליו יועברו הפקודות הקוליות. -service.voice.rulehli.label = מתורגמן כללים קולי +service.system.rulehli.label = מתורגמן כללים קולי diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_hu.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_hu.properties index c95bf2bad82..e25c5b0cf75 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_hu.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_hu.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Hangvezérlés elem -voice.config.rulehli.item.description = A szöveg elem, amely a hangüzeneteket fogadja. +system.config.rulehli.item.label = Hangvezérlés elem +system.config.rulehli.item.description = A szöveg elem, amely a hangüzeneteket fogadja. -service.voice.rulehli.label = Szabály hang értelmező +service.system.rulehli.label = Szabály hang értelmező diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_it.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_it.properties index dc0f67fe27e..e41c09b1350 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_it.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_it.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Item Comando Vocale -voice.config.rulehli.item.description = La Stringa dell'Item passata al comando vocale. +system.config.rulehli.item.label = Item Comando Vocale +system.config.rulehli.item.description = La Stringa dell'Item passata al comando vocale. -service.voice.rulehli.label = Regola Inteprete Vocale +service.system.rulehli.label = Regola Inteprete Vocale diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_nl.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_nl.properties index df6e7e9fce6..d78532e0a5a 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_nl.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_nl.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Spraakopdracht Item -voice.config.rulehli.item.description = Het String Item om spraakopdrachten naar door te geven. +system.config.rulehli.item.label = Spraakopdracht Item +system.config.rulehli.item.description = Het String Item om spraakopdrachten naar door te geven. -service.voice.rulehli.label = Rule Spraak Interpreter +service.system.rulehli.label = Rule Spraak Interpreter diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_no.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_no.properties index 541440e9f5e..4d704b2470d 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_no.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_no.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Stemmekommando Item -voice.config.rulehli.item.description = Streng Item'et stemmekommandoer skal sendes til. +system.config.rulehli.item.label = Stemmekommando Item +system.config.rulehli.item.description = Streng Item'et stemmekommandoer skal sendes til. -service.voice.rulehli.label = Regelbasert Stemmetolk +service.system.rulehli.label = Regelbasert Stemmetolk diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_pl.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_pl.properties index 0e5be3ec0fd..51fe4ff82ef 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_pl.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_pl.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Element komendy głosowej -voice.config.rulehli.item.description = Element do którego mają być przekazywane polecenia głosowe. +system.config.rulehli.item.label = Element komendy głosowej +system.config.rulehli.item.description = Element do którego mają być przekazywane polecenia głosowe. -service.voice.rulehli.label = Interpretator reguł głosu +service.system.rulehli.label = Interpretator reguł głosu diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_pt.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_pt.properties index 334767d6519..09cae541aa2 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_pt.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_pt.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Item de Comando de Voz -voice.config.rulehli.item.description = O Item de texto para passar comandos de voz. +system.config.rulehli.item.label = Item de Comando de Voz +system.config.rulehli.item.description = O Item de texto para passar comandos de voz. -service.voice.rulehli.label = Interpretador de voz +service.system.rulehli.label = Interpretador de voz diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_ru.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_ru.properties index b94ca001fc9..173ae4efb39 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_ru.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_ru.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Item голосовых команд -voice.config.rulehli.item.description = Строковый Item для передачи голосовых команд. +system.config.rulehli.item.label = Item голосовых команд +system.config.rulehli.item.description = Строковый Item для передачи голосовых команд. -service.voice.rulehli.label = Управление голосовым интерпретатором +service.system.rulehli.label = Управление голосовым интерпретатором diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_sl.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_sl.properties index be2a7c4459b..1de53e55bd2 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_sl.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_sl.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Predmet glasovnega ukaza -voice.config.rulehli.item.description = Besedilni predmet, ki se mu naj posreduje ukaz. +system.config.rulehli.item.label = Predmet glasovnega ukaza +system.config.rulehli.item.description = Besedilni predmet, ki se mu naj posreduje ukaz. -service.voice.rulehli.label = Tolmač glasovnih ukazov +service.system.rulehli.label = Tolmač glasovnih ukazov diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_sv.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_sv.properties index a829304e159..4d176002d27 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_sv.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_sv.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Item för röstkommando -voice.config.rulehli.item.description = Sträng-Item att skicka röstkommandon till. +system.config.rulehli.item.label = Item för röstkommando +system.config.rulehli.item.description = Sträng-Item att skicka röstkommandon till. -service.voice.rulehli.label = Rösttolkare för regler +service.system.rulehli.label = Rösttolkare för regler diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_uk.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_uk.properties index fc90a8ba89d..aa82b3c9125 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_uk.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_uk.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = Голосова команда -voice.config.rulehli.item.description = Пункт для передачі голосових команд. +system.config.rulehli.item.label = Голосова команда +system.config.rulehli.item.description = Пункт для передачі голосових команд. -service.voice.rulehli.label = Правило голосового інтерпретера +service.system.rulehli.label = Правило голосового інтерпретера diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_zh.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_zh.properties index 03189a9b1b1..736ede14255 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_zh.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_zh.properties @@ -1,4 +1,4 @@ -voice.config.rulehli.item.label = 语音命令 -voice.config.rulehli.item.description = 要传递语音命令的字符串。 +system.config.rulehli.item.label = 语音命令 +system.config.rulehli.item.description = 要传递语音命令的字符串。 -service.voice.rulehli.label = 语音规则解释器 +service.system.rulehli.label = 语音规则解释器 diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/internal/RuleHumanLanguageInterpreter.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/internal/RuleHumanLanguageInterpreter.java index 3f4e90b57ce..928b3047a4f 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/internal/RuleHumanLanguageInterpreter.java +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/internal/RuleHumanLanguageInterpreter.java @@ -47,13 +47,13 @@ @NonNullByDefault @Component(immediate = true, service = HumanLanguageInterpreter.class, configurationPid = "org.openhab.rulehli", // property = Constants.SERVICE_PID + "=org.openhab.rulehli") -@ConfigurableService(category = "voice", label = "Rule Voice Interpreter", description_uri = RuleHumanLanguageInterpreter.CONFIG_URI) +@ConfigurableService(category = "system", label = "Rule Voice Interpreter", description_uri = RuleHumanLanguageInterpreter.CONFIG_URI) public class RuleHumanLanguageInterpreter implements HumanLanguageInterpreter { private final Logger logger = LoggerFactory.getLogger(RuleHumanLanguageInterpreter.class); // constants for the configuration properties - protected static final String CONFIG_URI = "voice:rulehli"; + protected static final String CONFIG_URI = "system:rulehli"; private String itemName = "VoiceCommand"; From a8a5e86fb9e3b87f3e6f7892e984adacf7afe83f Mon Sep 17 00:00:00 2001 From: J-N-K Date: Thu, 25 May 2023 19:19:06 +0200 Subject: [PATCH 087/126] Fix NPE in UpgradeTool when no pattern present (#3630) Reported on the forum. State descriptions do no necessarily contain a pattern and thus `.get("pattern")` can return null. Signed-off-by: Jan N. Klug --- .../openhab/core/tools/internal/Upgrader.java | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java b/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java index 789a0a7b41f..5cf8daa7880 100644 --- a/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java +++ b/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java @@ -94,27 +94,31 @@ public void itemCopyUnitToMetadata() { ManagedItemProvider.PersistedItem item = itemStorage.get(itemName); if (item != null && item.itemType.startsWith("Number:")) { if (metadataStorage.containsKey(NumberItem.UNIT_METADATA_NAMESPACE + ":" + itemName)) { - logger.debug("{}: already contains a 'unit' metadata, skipping it", itemName); + logger.debug("{}: Already contains a 'unit' metadata, skipping it", itemName); } else { Metadata metadata = metadataStorage.get("stateDescription:" + itemName); if (metadata == null) { logger.debug("{}: Nothing to do, no state description found.", itemName); } else { String pattern = (String) metadata.getConfiguration().get("pattern"); - if (pattern.contains(UnitUtils.UNIT_PLACEHOLDER)) { - logger.warn( - "{}: State description contains unit place-holder '%unit%', check if 'unit' metadata is needed!", - itemName); - } else { - Unit stateDescriptionUnit = UnitUtils.parseUnit(pattern); - if (stateDescriptionUnit != null) { - String unit = stateDescriptionUnit.toString(); - MetadataKey defaultUnitMetadataKey = new MetadataKey(NumberItem.UNIT_METADATA_NAMESPACE, + if (pattern != null) { + if (pattern.contains(UnitUtils.UNIT_PLACEHOLDER)) { + logger.warn( + "{}: State description contains unit place-holder '%unit%', check if 'unit' metadata is needed!", itemName); - Metadata defaultUnitMetadata = new Metadata(defaultUnitMetadataKey, unit, null); - metadataStorage.put(defaultUnitMetadataKey.toString(), defaultUnitMetadata); - logger.info("{}: Wrote 'unit={}' to metadata.", itemName, unit); + } else { + Unit stateDescriptionUnit = UnitUtils.parseUnit(pattern); + if (stateDescriptionUnit != null) { + String unit = stateDescriptionUnit.toString(); + MetadataKey defaultUnitMetadataKey = new MetadataKey( + NumberItem.UNIT_METADATA_NAMESPACE, itemName); + Metadata defaultUnitMetadata = new Metadata(defaultUnitMetadataKey, unit, null); + metadataStorage.put(defaultUnitMetadataKey.toString(), defaultUnitMetadata); + logger.info("{}: Wrote 'unit={}' to metadata.", itemName, unit); + } } + } else { + logger.debug("{}: Nothing to do, no pattern found.", itemName); } } } From cd06e6d3249ef1e84a264e22ba03a11890b2e3d5 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Thu, 25 May 2023 19:58:11 +0200 Subject: [PATCH 088/126] Fix event ordering for item events (#3628) Posting the `ItemAddedEvent` after notifying the listeners results in `ItemStateUpdated/ChangedEvents` before the `ItemAddedEvent` because the state could be altered by persistence services. This is at least unexpected and can be fixed by swapping the order. Signed-off-by: Jan N. Klug --- .../org/openhab/core/internal/items/ItemRegistryImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemRegistryImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemRegistryImpl.java index 30c01685af6..daeba98f4c8 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemRegistryImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/ItemRegistryImpl.java @@ -370,20 +370,20 @@ public Collection getItemsByTagAndType(String type, String... tags) { @Override protected void notifyListenersAboutAddedElement(Item element) { - super.notifyListenersAboutAddedElement(element); postEvent(ItemEventFactory.createAddedEvent(element)); + super.notifyListenersAboutAddedElement(element); } @Override protected void notifyListenersAboutRemovedElement(Item element) { - super.notifyListenersAboutRemovedElement(element); postEvent(ItemEventFactory.createRemovedEvent(element)); + super.notifyListenersAboutRemovedElement(element); } @Override protected void notifyListenersAboutUpdatedElement(Item oldElement, Item element) { - super.notifyListenersAboutUpdatedElement(oldElement, element); postEvent(ItemEventFactory.createUpdateEvent(element, oldElement)); + super.notifyListenersAboutUpdatedElement(oldElement, element); } @Override From 4d3535a4116364084a19610dfc9dd4152356145b Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 26 May 2023 09:32:29 +0100 Subject: [PATCH 089/126] [ColorUtil] optimise constants; hue overflow check (#3629) * [ColorUtil] define constants; fix hue overflow Signed-off-by: Andrew Fiddian-Green --- .../java/org/openhab/core/util/ColorUtil.java | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/util/ColorUtil.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/util/ColorUtil.java index 6fe22d063a7..23295f0e38a 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/util/ColorUtil.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/util/ColorUtil.java @@ -38,7 +38,16 @@ public class ColorUtil { private static final Logger LOGGER = LoggerFactory.getLogger(ColorUtil.class); private static final MathContext COLOR_MATH_CONTEXT = new MathContext(5, RoundingMode.HALF_UP); - protected static final BigDecimal BIG_DECIMAL_HUNDRED = BigDecimal.valueOf(100); + private static final BigDecimal BIG_DECIMAL_360 = BigDecimal.valueOf(360); + private static final BigDecimal BIG_DECIMAL_255 = BigDecimal.valueOf(255); + private static final BigDecimal BIG_DECIMAL_240 = BigDecimal.valueOf(240); + private static final BigDecimal BIG_DECIMAL_120 = BigDecimal.valueOf(120); + private static final BigDecimal BIG_DECIMAL_100 = BigDecimal.valueOf(100); + private static final BigDecimal BIG_DECIMAL_60 = BigDecimal.valueOf(60); + private static final BigDecimal BIG_DECIMAL_5 = BigDecimal.valueOf(5); + private static final BigDecimal BIG_DECIMAL_3 = BigDecimal.valueOf(3); + private static final BigDecimal BIG_DECIMAL_2_POINT_55 = new BigDecimal("2.55"); + public static final Gamut DEFAULT_GAMUT = new Gamut(new double[] { 0.9961, 0.0001 }, new double[] { 0, 0.9961 }, new double[] { 0, 0.0001 }); @@ -80,11 +89,11 @@ public static PercentType[] hsbToRgbPercent(HSBType hsb) { PercentType green = null; PercentType blue = null; - final BigDecimal h = hsb.getHue().toBigDecimal().divide(BIG_DECIMAL_HUNDRED, 10, RoundingMode.HALF_UP); - final BigDecimal s = hsb.getSaturation().toBigDecimal().divide(BIG_DECIMAL_HUNDRED); + final BigDecimal h = hsb.getHue().toBigDecimal().divide(BIG_DECIMAL_100, 10, RoundingMode.HALF_UP); + final BigDecimal s = hsb.getSaturation().toBigDecimal().divide(BIG_DECIMAL_100); - int hInt = h.multiply(BigDecimal.valueOf(5)).divide(BigDecimal.valueOf(3), 0, RoundingMode.DOWN).intValue(); - final BigDecimal f = h.multiply(BigDecimal.valueOf(5)).divide(BigDecimal.valueOf(3), 10, RoundingMode.HALF_UP) + int hInt = h.multiply(BIG_DECIMAL_5).divide(BIG_DECIMAL_3, 0, RoundingMode.DOWN).intValue(); + final BigDecimal f = h.multiply(BIG_DECIMAL_5).divide(BIG_DECIMAL_3, 10, RoundingMode.HALF_UP) .remainder(BigDecimal.ONE); final BigDecimal value = hsb.getBrightness().toBigDecimal(); @@ -241,10 +250,10 @@ public static HSBType rgbToHsb(PercentType[] rgb) throws IllegalArgumentExceptio return new HSBType(new DecimalType(), new PercentType(), new PercentType(max)); } - PercentType saturation = new PercentType(span.divide(max, COLOR_MATH_CONTEXT).multiply(BIG_DECIMAL_HUNDRED)); + PercentType saturation = new PercentType(span.divide(max, COLOR_MATH_CONTEXT).multiply(BIG_DECIMAL_100)); PercentType brightness = new PercentType(max); - BigDecimal scale = span.divide(BigDecimal.valueOf(60), COLOR_MATH_CONTEXT); + BigDecimal scale = span.divide(BIG_DECIMAL_60, COLOR_MATH_CONTEXT); BigDecimal redAngle = max.subtract(r).divide(scale, COLOR_MATH_CONTEXT); BigDecimal greenAngle = max.subtract(g).divide(scale, COLOR_MATH_CONTEXT); @@ -254,12 +263,14 @@ public static HSBType rgbToHsb(PercentType[] rgb) throws IllegalArgumentExceptio if (r.compareTo(max) == 0) { hue = blueAngle.subtract(greenAngle); } else if (g.compareTo(max) == 0) { - hue = BigDecimal.valueOf(120).add(redAngle).subtract(blueAngle); + hue = BIG_DECIMAL_120.add(redAngle).subtract(blueAngle); } else { - hue = BigDecimal.valueOf(240).add(greenAngle).subtract(redAngle); + hue = BIG_DECIMAL_240.add(greenAngle).subtract(redAngle); } if (hue.compareTo(BigDecimal.ZERO) < 0) { - hue = hue.add(BigDecimal.valueOf(360)); + hue = hue.add(BIG_DECIMAL_360); + } else if (hue.compareTo(BIG_DECIMAL_360) > 0) { + hue = hue.subtract(BIG_DECIMAL_360); } return new HSBType(new DecimalType(hue), saturation, brightness); @@ -485,15 +496,15 @@ private static boolean inRange(double val) { } private static int convertColorPercentToByte(PercentType percent) { - return percent.toBigDecimal().multiply(BigDecimal.valueOf(255)) - .divide(BIG_DECIMAL_HUNDRED, 0, RoundingMode.HALF_UP).intValue(); + return percent.toBigDecimal().multiply(BIG_DECIMAL_255).divide(BIG_DECIMAL_100, 0, RoundingMode.HALF_UP) + .intValue(); } private static PercentType convertByteToColorPercent(int b) { - return new PercentType(new BigDecimal(b).divide(new BigDecimal("2.55"), COLOR_MATH_CONTEXT)); + return new PercentType(new BigDecimal(b).divide(BIG_DECIMAL_2_POINT_55, COLOR_MATH_CONTEXT)); } private static PercentType convertDoubleToColorPercent(double d) { - return new PercentType(new BigDecimal(d).multiply(BIG_DECIMAL_HUNDRED, COLOR_MATH_CONTEXT)); + return new PercentType(new BigDecimal(d).multiply(BIG_DECIMAL_100, COLOR_MATH_CONTEXT)); } } From c6a4dd2a311f1a35c67bd9f3a0883e5fa9020b9a Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 27 May 2023 19:14:48 +0200 Subject: [PATCH 090/126] Remove package support from FeatureInstaller (#3634) Signed-off-by: Jan N. Klug --- .../core/karaf/internal/FeatureInstaller.java | 32 +------------------ 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/FeatureInstaller.java b/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/FeatureInstaller.java index d3dd50ba760..4613a77c98d 100644 --- a/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/FeatureInstaller.java +++ b/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/FeatureInstaller.java @@ -83,8 +83,6 @@ public class FeatureInstaller implements ConfigurationListener { protected static final String CONFIG_URI = "system:addons"; public static final String PREFIX = "openhab-"; - public static final String PREFIX_PACKAGE = "package-"; - public static final String MINIMAL_PACKAGE = "minimal"; private static final String CFG_REMOTE = "remote"; private static final String PAX_URL_PID = "org.ops4j.pax.url.mvn"; @@ -191,13 +189,6 @@ private synchronized void processConfigQueue() { waitForConfigUpdateEvent(); } - if (installPackage(config)) { - changed = true; - // our package selection has changed, so let's wait for the values to be available in config admin - // which we will receive as another call to modified - continue; - } - if (installAddons(config)) { changed = true; } @@ -278,7 +269,7 @@ private void waitForConfigUpdateEvent() { private void changeAddonConfig(String type, String id, BiFunction, String, Boolean> method) throws IOException { Configuration cfg = configurationAdmin.getConfiguration(OpenHAB.ADDONS_SERVICE_PID, null); - Dictionary props = cfg.getProperties(); + Dictionary props = Objects.requireNonNullElse(cfg.getProperties(), new Hashtable<>()); Object typeProp = props.get(type); String[] addonIds = typeProp != null ? typeProp.toString().split(",") : new String[0]; Set normalizedIds = new HashSet<>(); // sets don't allow duplicates @@ -512,27 +503,6 @@ private void uninstallFeature(String name) { } } - private boolean installPackage(final Map config) { - boolean configChanged = false; - Object packageName = config.get(OpenHAB.CFG_PACKAGE); - if (packageName instanceof String currentPackage) { - String fullName = PREFIX + PREFIX_PACKAGE + currentPackage.strip(); - if (!MINIMAL_PACKAGE.equals(currentPackage)) { - configChanged = installFeature(fullName); - } - - // uninstall all other packages - try { - Stream.of(featuresService.listFeatures()).map(Feature::getName) - .filter(feature -> feature.startsWith(PREFIX + PREFIX_PACKAGE) && !feature.equals(fullName)) - .forEach(this::uninstallFeature); - } catch (Exception e) { - logger.error("Failed retrieving features: {}", e.getMessage(), debugException(e)); - } - } - return configChanged; - } - private void postInstalledEvent(String featureName) { String extensionId = featureName.substring(PREFIX.length()); Event event = AddonEventFactory.createAddonInstalledEvent(extensionId); From ee6d7b39b21f41a6645b2fb206b3dba815b839cf Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 27 May 2023 19:19:25 +0200 Subject: [PATCH 091/126] Fix NPE in WatchServiceImpl (#3633) It was reported on the forum that in some cases the hash is null. Signed-off-by: Jan N. Klug --- .../org/openhab/core/internal/service/WatchServiceImpl.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/WatchServiceImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/WatchServiceImpl.java index a90de4c0eb7..b9c093d9220 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/WatchServiceImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/WatchServiceImpl.java @@ -223,6 +223,12 @@ public void onEvent(@Nullable DirectoryChangeEvent directoryChangeEvent) throws Path path = directoryChangeEvent.path(); + if (directoryChangeEvent.eventType() != DirectoryChangeEvent.EventType.DELETE + && directoryChangeEvent.hash() == null) { + logger.warn("Detected invalid event (hash must not be null for CREATE/MODIFY): {}", directoryChangeEvent); + return; + } + synchronized (scheduledEvents) { ScheduledFuture future = scheduledEvents.remove(path); if (future != null && !future.isDone()) { From ed392eec86eeacc19ac149dce314667e1ee78d07 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 27 May 2023 21:10:32 +0200 Subject: [PATCH 092/126] [rest] Add endpoint for UoM information (#3611) * [rest] Add endpoint for UoM information Signed-off-by: Jan N. Klug --- .../resources/SystemInfoResource.java | 16 ++++- .../internal/resources/beans/UoMInfoBean.java | 62 +++++++++++++++++++ .../org/openhab/core/i18n/UnitProvider.java | 4 ++ .../core/internal/i18n/I18nProviderImpl.java | 7 +++ .../core/internal/i18n/TestUnitProvider.java | 7 +++ 5 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/resources/beans/UoMInfoBean.java diff --git a/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/resources/SystemInfoResource.java b/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/resources/SystemInfoResource.java index 434c2a55f1e..f419bb4361c 100644 --- a/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/resources/SystemInfoResource.java +++ b/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/resources/SystemInfoResource.java @@ -23,9 +23,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.auth.Role; +import org.openhab.core.i18n.UnitProvider; import org.openhab.core.io.rest.RESTConstants; import org.openhab.core.io.rest.RESTResource; import org.openhab.core.io.rest.internal.resources.beans.SystemInfoBean; +import org.openhab.core.io.rest.internal.resources.beans.UoMInfoBean; import org.openhab.core.service.StartLevelService; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -64,10 +66,12 @@ public class SystemInfoResource implements RESTResource { public static final String PATH_SYSTEMINFO = "systeminfo"; private final StartLevelService startLevelService; + private final UnitProvider unitProvider; @Activate - public SystemInfoResource(@Reference StartLevelService startLevelService) { + public SystemInfoResource(@Reference StartLevelService startLevelService, @Reference UnitProvider unitProvider) { this.startLevelService = startLevelService; + this.unitProvider = unitProvider; } @GET @@ -79,4 +83,14 @@ public Response getSystemInfo(@Context UriInfo uriInfo) { final SystemInfoBean bean = new SystemInfoBean(startLevelService.getStartLevel()); return Response.ok(bean).build(); } + + @GET + @Path("/uom") + @Produces(MediaType.APPLICATION_JSON) + @Operation(operationId = "getUoMInformation", summary = "Get all supported dimensions and their system units.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = UoMInfoBean.class))) }) + public Response getUoMInfo(@Context UriInfo uriInfo) { + final UoMInfoBean bean = new UoMInfoBean(unitProvider); + return Response.ok(bean).build(); + } } diff --git a/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/resources/beans/UoMInfoBean.java b/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/resources/beans/UoMInfoBean.java new file mode 100644 index 00000000000..225d19221d4 --- /dev/null +++ b/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/resources/beans/UoMInfoBean.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.rest.internal.resources.beans; + +import java.util.Comparator; +import java.util.List; +import java.util.Objects; + +import javax.measure.Quantity; +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.i18n.UnitProvider; +import org.openhab.core.types.util.UnitUtils; + +/** + * This is a java bean that is used to define UoM information for the REST interface. + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class UoMInfoBean { + + public final UoMInfo uomInfo; + + public static class UoMInfo { + public final List dimensions; + + public static class DimensionInfo { + public final String dimension; + public final String systemUnit; + + public DimensionInfo(String dimension, String systemUnit) { + this.dimension = dimension; + this.systemUnit = systemUnit; + } + } + + @SuppressWarnings({ "unchecked" }) + public UoMInfo(UnitProvider unitProvider) { + dimensions = unitProvider.getAllDimensions().stream().map(dimension -> { + Unit unit = unitProvider.getUnit((Class) dimension); + String dimensionName = Objects.requireNonNull(UnitUtils.getDimensionName(unit)); + return new DimensionInfo(dimensionName, unit.toString()); + }).sorted(Comparator.comparing(a -> a.dimension)).toList(); + } + } + + public UoMInfoBean(UnitProvider unitProvider) { + uomInfo = new UoMInfo(unitProvider); + } +} diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/i18n/UnitProvider.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/i18n/UnitProvider.java index 72a61ec941d..c1077ecb058 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/i18n/UnitProvider.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/i18n/UnitProvider.java @@ -12,6 +12,8 @@ */ package org.openhab.core.i18n; +import java.util.Collection; + import javax.measure.Quantity; import javax.measure.Unit; import javax.measure.spi.SystemOfUnits; @@ -44,4 +46,6 @@ public interface UnitProvider { * @return the {@link SystemOfUnits} which is currently set, must not be null. */ SystemOfUnits getMeasurementSystem(); + + Collection>> getAllDimensions(); } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java index e767b266f8d..abe76195a22 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java @@ -17,11 +17,13 @@ import java.text.MessageFormat; import java.time.DateTimeException; import java.time.ZoneId; +import java.util.Collection; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.ResourceBundle; +import java.util.Set; import javax.measure.Quantity; import javax.measure.Unit; @@ -379,6 +381,11 @@ public SystemOfUnits getMeasurementSystem() { return SIUnits.getInstance(); } + @Override + public Collection>> getAllDimensions() { + return Set.copyOf(getDimensionMap().keySet()); + } + public static Map>, Map>>> getDimensionMap() { Map>, Map>>> dimensionMap = new HashMap<>(); diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/i18n/TestUnitProvider.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/i18n/TestUnitProvider.java index 19aeaca401d..066e822f786 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/i18n/TestUnitProvider.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/internal/i18n/TestUnitProvider.java @@ -12,7 +12,9 @@ */ package org.openhab.core.internal.i18n; +import java.util.Collection; import java.util.Map; +import java.util.Set; import javax.measure.Quantity; import javax.measure.Unit; @@ -45,4 +47,9 @@ public > Unit getUnit(Class dimension) { public SystemOfUnits getMeasurementSystem() { return SIUnits.getInstance(); } + + @Override + public Collection>> getAllDimensions() { + return Set.of(); + } } From 7843b6872a6db430931fdedb426a4cdaf603544d Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 27 May 2023 21:30:34 +0200 Subject: [PATCH 093/126] Add group name to GroupStateTriggerHandler (#3536) * Add group name to GroupStateTriggerHandler When triggering on state change/update of group member, the group is not available in the rule context (because the event is the `ItemStateChanged/UpdatedEvent` that caused the group to change/update. This adds a new element `triggeringGroup` to the rule context. This is useful for generalized rules that trigger on different groups. Signed-off-by: Jan N. Klug --- .../handler/GroupCommandTriggerHandler.java | 4 ++++ .../handler/GroupStateTriggerHandler.java | 8 ++++++++ .../automation/moduletypes/ItemTriggers.json | 18 ++++++++++++++++++ .../OH-INF/i18n/automation.properties | 4 ++++ .../internal/engine/DSLScriptEngine.java | 9 ++++++++- .../jvmmodel/ScriptJvmModelInferrer.xtend | 10 ++++++++++ 6 files changed, 52 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupCommandTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupCommandTriggerHandler.java index 69a12a8156e..b9d4a781ce5 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupCommandTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupCommandTriggerHandler.java @@ -106,10 +106,14 @@ public void receive(Event event) { if (event instanceof ItemCommandEvent icEvent) { String itemName = icEvent.getItemName(); Item item = itemRegistry.get(itemName); + Item group = itemRegistry.get(groupName); if (item != null && item.getGroupNames().contains(groupName)) { String command = this.command; Command itemCommand = icEvent.getItemCommand(); if (command == null || command.equals(itemCommand.toFullString())) { + if (group != null) { + values.put("triggeringGroup", group); + } values.put("triggeringItem", item); values.put("command", itemCommand); values.put("event", event); diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupStateTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupStateTriggerHandler.java index bafe1a0d604..5a069b87550 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupStateTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupStateTriggerHandler.java @@ -115,10 +115,14 @@ public void receive(Event event) { if (event instanceof ItemStateUpdatedEvent isEvent && UPDATE_MODULE_TYPE_ID.equals(module.getTypeUID())) { String itemName = isEvent.getItemName(); Item item = itemRegistry.get(itemName); + Item group = itemRegistry.get(groupName); if (item != null && item.getGroupNames().contains(groupName)) { State state = isEvent.getItemState(); if ((this.state == null || state.toFullString().equals(this.state))) { Map values = new HashMap<>(); + if (group != null) { + values.put("triggeringGroup", group); + } values.put("triggeringItem", item); values.put("state", state); values.put("event", event); @@ -129,12 +133,16 @@ public void receive(Event event) { && CHANGE_MODULE_TYPE_ID.equals(module.getTypeUID())) { String itemName = iscEvent.getItemName(); Item item = itemRegistry.get(itemName); + Item group = itemRegistry.get(groupName); if (item != null && item.getGroupNames().contains(groupName)) { State state = iscEvent.getItemState(); State oldState = iscEvent.getOldItemState(); if (stateMatches(this.state, state) && stateMatches(this.previousState, oldState)) { Map values = new HashMap<>(); + if (group != null) { + values.put("triggeringGroup", group); + } values.put("triggeringItem", item); values.put("oldState", oldState); values.put("newState", state); diff --git a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/automation/moduletypes/ItemTriggers.json b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/automation/moduletypes/ItemTriggers.json index ee1bdaf7294..7e2a500ef46 100644 --- a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/automation/moduletypes/ItemTriggers.json +++ b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/automation/moduletypes/ItemTriggers.json @@ -290,6 +290,15 @@ } ], "outputs": [ + { + "name": "triggeringGroup", + "type": "org.openhab.core.items.Item", + "description": "the group that the item belongs to", + "label": "Triggering Group", + "tags": [ + "item" + ] + }, { "name": "triggeringItem", "type": "org.openhab.core.items.Item", @@ -366,6 +375,15 @@ } ], "outputs": [ + { + "name": "triggeringGroup", + "type": "org.openhab.core.items.Item", + "description": "the group that the item belongs to", + "label": "Triggering Group", + "tags": [ + "item" + ] + }, { "name": "triggeringItem", "type": "org.openhab.core.items.Item", diff --git a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation.properties b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation.properties index f319b9e88cd..23059910c9e 100644 --- a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation.properties +++ b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation.properties @@ -66,6 +66,8 @@ module-type.core.GroupCommandTrigger.config.command.option.OPEN = OPEN module-type.core.GroupCommandTrigger.config.command.option.CLOSED = CLOSED module-type.core.GroupCommandTrigger.config.command.option.UP = UP module-type.core.GroupCommandTrigger.config.command.option.DOWN = DOWN +module-type.core.GroupCommandTrigger.output.triggeringGroup.label = Triggering Group +module-type.core.GroupCommandTrigger.output.triggeringGroup.description = the group that the item belongs to module-type.core.GroupCommandTrigger.output.triggeringItem.label = Triggering Item module-type.core.GroupCommandTrigger.output.triggeringItem.description = the member of the group that received the command module-type.core.GroupCommandTrigger.output.command.label = Command @@ -118,6 +120,8 @@ module-type.core.GroupStateUpdateTrigger.config.state.option.OPEN = OPEN module-type.core.GroupStateUpdateTrigger.config.state.option.CLOSED = CLOSED module-type.core.GroupStateUpdateTrigger.config.state.option.UP = UP module-type.core.GroupStateUpdateTrigger.config.state.option.DOWN = DOWN +module-type.core.GroupStateUpdateTrigger.output.triggeringGroup.label = Triggering Group +module-type.core.GroupStateUpdateTrigger.output.triggeringGroup.description = the group that the item belongs to module-type.core.GroupStateUpdateTrigger.output.triggeringItem.label = Triggering Item module-type.core.GroupStateUpdateTrigger.output.triggeringItem.description = the member of the group that updated its state module-type.core.GroupStateUpdateTrigger.output.state.label = State diff --git a/bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/DSLScriptEngine.java b/bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/DSLScriptEngine.java index 5ff5e75bf0b..7ae6cc6c51f 100644 --- a/bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/DSLScriptEngine.java +++ b/bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/DSLScriptEngine.java @@ -32,6 +32,7 @@ import org.eclipse.xtext.xbase.interpreter.IEvaluationContext; import org.eclipse.xtext.xbase.interpreter.impl.DefaultEvaluationContext; import org.openhab.core.automation.module.script.ScriptExtensionAccessor; +import org.openhab.core.items.Item; import org.openhab.core.items.events.ItemEvent; import org.openhab.core.model.script.engine.Script; import org.openhab.core.model.script.engine.ScriptExecutionException; @@ -63,7 +64,8 @@ public class DSLScriptEngine implements javax.script.ScriptEngine { private static final Map IMPLICIT_VARS = Map.of("command", ScriptJvmModelInferrer.VAR_RECEIVED_COMMAND, "state", ScriptJvmModelInferrer.VAR_NEW_STATE, "newState", ScriptJvmModelInferrer.VAR_NEW_STATE, "oldState", ScriptJvmModelInferrer.VAR_PREVIOUS_STATE, - "triggeringItem", ScriptJvmModelInferrer.VAR_TRIGGERING_ITEM, "input", ScriptJvmModelInferrer.VAR_INPUT); + "triggeringItem", ScriptJvmModelInferrer.VAR_TRIGGERING_ITEM, "triggeringGroup", + ScriptJvmModelInferrer.VAR_TRIGGERING_GROUP, "input", ScriptJvmModelInferrer.VAR_INPUT); private final Logger logger = LoggerFactory.getLogger(DSLScriptEngine.class); @@ -179,6 +181,11 @@ private DefaultEvaluationContext createEvaluationContext(Script script, IEvaluat if (value instanceof ItemEvent event) { evalContext.newValue(QualifiedName.create(ScriptJvmModelInferrer.VAR_TRIGGERING_ITEM_NAME), event.getItemName()); + Object group = context.getAttribute(ScriptJvmModelInferrer.VAR_TRIGGERING_GROUP); + if (group instanceof Item groupItem) { + evalContext.newValue(QualifiedName.create(ScriptJvmModelInferrer.VAR_TRIGGERING_GROUP_NAME), + groupItem.getName()); + } } if (value instanceof ThingStatusInfoChangedEvent event) { evalContext.newValue(QualifiedName.create(ScriptJvmModelInferrer.VAR_TRIGGERING_THING), diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/jvmmodel/ScriptJvmModelInferrer.xtend b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/jvmmodel/ScriptJvmModelInferrer.xtend index dc4b3759123..5d871849367 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/jvmmodel/ScriptJvmModelInferrer.xtend +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/jvmmodel/ScriptJvmModelInferrer.xtend @@ -44,6 +44,12 @@ class ScriptJvmModelInferrer extends AbstractModelInferrer { /** Variable name for the input string in a "script transformation" or "script profile" */ public static final String VAR_INPUT = "input"; + /** Variable name for the group in a "member of state triggered" or "member of command triggered" rule */ + public static final String VAR_TRIGGERING_GROUP = "triggeringGroup"; + + /** Variable name for the group in a "member of state triggered" or "member of command triggered" rule */ + public static final String VAR_TRIGGERING_GROUP_NAME = "triggeringGroupName"; + /** Variable name for the item in a "state triggered" or "command triggered" rule */ public static final String VAR_TRIGGERING_ITEM = "triggeringItem"; @@ -131,6 +137,10 @@ class ScriptJvmModelInferrer extends AbstractModelInferrer { static = true val inputTypeRef = script.newTypeRef(String) parameters += script.toParameter(VAR_INPUT, inputTypeRef) + val groupTypeRef = script.newTypeRef(Item) + parameters += script.toParameter(VAR_TRIGGERING_GROUP, groupTypeRef) + val groupNameRef = script.newTypeRef(String) + parameters += script.toParameter(VAR_TRIGGERING_GROUP_NAME, groupNameRef) val itemTypeRef = script.newTypeRef(Item) parameters += script.toParameter(VAR_TRIGGERING_ITEM, itemTypeRef) val itemNameRef = script.newTypeRef(String) From 6bb15b806c739dd9b5a72bafc27e3c6c932a0405 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Sun, 28 May 2023 05:33:48 +1000 Subject: [PATCH 094/126] Add getAllStatesSince() and getAllStatesBetween() to PersistenceExtensions (#3466) Signed-off-by: Jimmy Tanagra --- .../extensions/PersistenceExtensions.java | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java index 478587314f4..a1d536c1d47 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java @@ -1230,7 +1230,62 @@ public static long countStateChangesBetween(Item item, ZonedDateTime begin, @Nul return null; } - private static Iterable getAllStatesBetween(Item item, ZonedDateTime begin, + /** + * Retrieves the historic items for a given item since a certain point in time. + * The default persistence service is used. + * + * @param item the item for which to retrieve the historic item + * @param timestamp the point in time from which to retrieve the states + * @return the historic items since the given point in time, or null if no historic items could be + * found. + */ + public static Iterable getAllStatesSince(Item item, ZonedDateTime timestamp) { + return getAllStatesBetween(item, timestamp, null); + } + + /** + * Retrieves the historic items for a given item since a certain point in time + * through a {@link PersistenceService} identified by the serviceId. + * + * @param item the item for which to retrieve the historic item + * @param timestamp the point in time from which to retrieve the states + * @param serviceId the name of the {@link PersistenceService} to use + * @return the historic items since the given point in time, or null if no historic items could be + * found or if the provided serviceId does not refer to an available + * {@link QueryablePersistenceService} + */ + public static Iterable getAllStatesSince(Item item, ZonedDateTime timestamp, String serviceId) { + return getAllStatesBetween(item, timestamp, null, serviceId); + } + + /** + * Retrieves the historic items for a given item beetween two certain points in time. + * The default persistence service is used. + * + * @param item the item for which to retrieve the historic item + * @param begin the point in time from which to retrieve the states + * @param end the point in time to which to retrieve the states + * @return the historic items between the given points in time, or null if no historic items could be + * found. + */ + public static Iterable getAllStatesBetween(Item item, ZonedDateTime begin, + @Nullable ZonedDateTime end) { + return getAllStatesBetween(item, begin, end, getDefaultServiceId()); + } + + /** + * Retrieves the historic items for a given item beetween two certain points in time + * through a {@link PersistenceService} identified by the serviceId. + * + * @param item the item for which to retrieve the historic item + * @param begin the point in time from which to retrieve the states + * @param end the point in time to which to retrieve the states + * @param serviceId the name of the {@link PersistenceService} to use + * @return the historic items between the given points in time, or null if no historic items could be + * found or if the provided serviceId does not refer to an available + * {@link QueryablePersistenceService} + */ + public static Iterable getAllStatesBetween(Item item, ZonedDateTime begin, @Nullable ZonedDateTime end, String serviceId) { PersistenceService service = getService(serviceId); if (service instanceof QueryablePersistenceService qService) { From 796f66a2fa684e8a776ab1c50aac2346557f6329 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 27 May 2023 21:48:16 +0200 Subject: [PATCH 095/126] Add a ScriptEngineFactory bundle tracker (#3275) * Add a ScriptEngineFactory bundle tracker The `ScriptEngingeFactoryBundleTracker` tracks all bundles that provide `ScriptEngineFcatory` capabilities and sets a `ReadyMarker` if all of them are activated. Signed-off-by: Jan N. Klug --- .../ScriptEngineFactoryBundleTracker.java | 156 ++++++++++++++++++ .../core/service/StartLevelService.java | 2 +- 2 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryBundleTracker.java diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryBundleTracker.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryBundleTracker.java new file mode 100644 index 00000000000..fa9e752be3b --- /dev/null +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryBundleTracker.java @@ -0,0 +1,156 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.automation.module.script.internal; + +import java.util.Dictionary; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.automation.module.script.ScriptEngineFactory; +import org.openhab.core.service.ReadyMarker; +import org.openhab.core.service.ReadyMarkerFilter; +import org.openhab.core.service.ReadyService; +import org.openhab.core.service.StartLevelService; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.util.tracker.BundleTracker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link ScriptEngineFactoryBundleTracker} tracks bundles that provide {@link ScriptEngineFactory} and sets the + * {@link #READY_MARKER} when all registered bundles are active + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +@Component(immediate = true) +public class ScriptEngineFactoryBundleTracker extends BundleTracker implements ReadyService.ReadyTracker { + private static final int STATE_MASK = Bundle.INSTALLED | Bundle.RESOLVED | Bundle.ACTIVE | Bundle.STARTING + | Bundle.STOPPING | Bundle.UNINSTALLED; + public static final ReadyMarker READY_MARKER = new ReadyMarker("automation", "scriptEngineFactories"); + + private final Logger logger = LoggerFactory.getLogger(ScriptEngineFactoryBundleTracker.class); + + private final ReadyService readyService; + private final StartLevelService startLevelService; + + private final Map bundles = new ConcurrentHashMap<>(); + private boolean ready = false; + + @Activate + public ScriptEngineFactoryBundleTracker(final @Reference ReadyService readyService, + final @Reference StartLevelService startLevelService, BundleContext bc) { + super(bc, STATE_MASK, null); + this.readyService = readyService; + this.startLevelService = startLevelService; + + this.open(); + + readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE) + .withIdentifier(Integer.toString(StartLevelService.STARTLEVEL_OSGI))); + } + + @Deactivate + public void deactivate() throws Exception { + this.close(); + ready = false; + } + + private boolean allBundlesActive() { + return bundles.values().stream().allMatch(i -> i == Bundle.ACTIVE); + } + + @Override + public Bundle addingBundle(@NonNullByDefault({}) Bundle bundle, @Nullable BundleEvent event) { + String bsn = bundle.getSymbolicName(); + int state = bundle.getState(); + if (isScriptingBundle(bundle)) { + logger.debug("Added {}: {} ", bsn, stateToString(state)); + bundles.put(bsn, state); + } + checkReady(); + + return bundle; + } + + @Override + public void modifiedBundle(@NonNullByDefault({}) Bundle bundle, @Nullable BundleEvent event, + @NonNullByDefault({}) Bundle object) { + String bsn = bundle.getSymbolicName(); + int state = bundle.getState(); + if (isScriptingBundle(bundle)) { + logger.debug("Modified {}: {}", bsn, stateToString(state)); + bundles.put(bsn, state); + } + checkReady(); + } + + @Override + public void removedBundle(@NonNullByDefault({}) Bundle bundle, @Nullable BundleEvent event, + @NonNullByDefault({}) Bundle object) { + String bsn = bundle.getSymbolicName(); + if (isScriptingBundle(bundle)) { + logger.debug("Removed {}", bsn); + bundles.remove(bsn); + } + checkReady(); + } + + @Override + public void onReadyMarkerAdded(ReadyMarker readyMarker) { + checkReady(); + } + + @Override + public void onReadyMarkerRemoved(ReadyMarker readyMarker) { + ready = false; + readyService.unmarkReady(READY_MARKER); + } + + private void checkReady() { + if (!ready && startLevelService.getStartLevel() > StartLevelService.STARTLEVEL_OSGI && allBundlesActive()) { + logger.info("All automation bundles ready."); + readyService.markReady(READY_MARKER); + ready = true; + } else if (ready && !allBundlesActive()) { + readyService.unmarkReady(READY_MARKER); + ready = false; + } + } + + private String stateToString(int state) { + return switch (state) { + case Bundle.UNINSTALLED -> "UNINSTALLED"; + case Bundle.INSTALLED -> "INSTALLED"; + case Bundle.RESOLVED -> "RESOLVED"; + case Bundle.STARTING -> "STARTING"; + case Bundle.STOPPING -> "STOPPING"; + case Bundle.ACTIVE -> "ACTIVE"; + default -> "UNKNOWN"; + }; + } + + private boolean isScriptingBundle(Bundle bundle) { + Dictionary headers = bundle.getHeaders(); + String provideCapability = headers.get("Provide-Capability"); + return provideCapability != null && provideCapability.contains(ScriptEngineFactory.class.getName()); + } +} diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/service/StartLevelService.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/service/StartLevelService.java index 2ad325632d2..e35dc37d3af 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/service/StartLevelService.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/service/StartLevelService.java @@ -58,7 +58,7 @@ * 10 - OSGi application start level has been reached, i.e. bundles are activated. * 20 - Model entities (items, things, links, persist config) have been loaded, both from db as well as files. * 30 - Item states have been restored from persistence service, where applicable. - * 40 - Rules are loaded and parsed, both from db as well as dsl and script files. + * 40 - Rules from db, dsl and script files are loaded and parsed, script engine factories are available. * 50 - Rule engine has executed all "system started" rules and is active. * 70 - User interface is up and running. * 80 - All things have been initialized. From 86abf68727639204ce8a34fbddfea900315b45d0 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 28 May 2023 09:23:24 +0200 Subject: [PATCH 096/126] Fix build (#3637) copyright outdated Signed-off-by: Jan N. Klug --- .../script/internal/ScriptEngineFactoryBundleTracker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryBundleTracker.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryBundleTracker.java index fa9e752be3b..3b4f6f4dd9f 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryBundleTracker.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryBundleTracker.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2022 Contributors to the openHAB project + * Copyright (c) 2010-2023 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. From d34b9164f796e5e3dfda9c86536e99f6bfccffbc Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 28 May 2023 10:14:03 +0200 Subject: [PATCH 097/126] Trigger immediately when start level already reached (#3278) * Trigger immediately when start level already reached Signed-off-by: Jan N. Klug --- .../factory/CoreModuleHandlerFactory.java | 8 +- .../module/handler/SystemTriggerHandler.java | 21 ++- .../handler/SystemTriggerHandlerTest.java | 165 ++++++++++++++++++ .../openhab/core/test/java/JavaOSGiTest.java | 9 + .../itest.bndrun | 7 +- .../test/AutomationIntegrationJsonTest.java | 13 +- .../test/AutomationIntegrationTest.java | 15 +- .../itest.bndrun | 7 +- .../internal/module/RunRuleModuleTest.java | 11 ++ .../internal/module/RuntimeRuleTest.java | 11 ++ .../itest.bndrun | 7 +- .../internal/BasicConditionHandlerTest.java | 12 ++ .../DayOfWeekConditionHandlerTest.java | 17 ++ .../timer/internal/RuntimeRuleTest.java | 13 ++ .../itest.bndrun | 7 +- .../core/automation/event/RuleEventTest.java | 15 +- 16 files changed, 318 insertions(+), 20 deletions(-) create mode 100644 bundles/org.openhab.core.automation/src/test/java/org/openhab/core/automation/internal/module/handler/SystemTriggerHandlerTest.java diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/factory/CoreModuleHandlerFactory.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/factory/CoreModuleHandlerFactory.java index 9862255a7c8..f69cb05cba7 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/factory/CoreModuleHandlerFactory.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/factory/CoreModuleHandlerFactory.java @@ -42,6 +42,7 @@ import org.openhab.core.events.EventPublisher; import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.items.ItemRegistry; +import org.openhab.core.service.StartLevelService; import org.osgi.framework.BundleContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -78,14 +79,17 @@ public class CoreModuleHandlerFactory extends BaseModuleHandlerFactory implement private final TimeZoneProvider timeZoneProvider; private final EventPublisher eventPublisher; private final BundleContext bundleContext; + private final StartLevelService startLevelService; @Activate public CoreModuleHandlerFactory(BundleContext bundleContext, final @Reference EventPublisher eventPublisher, - final @Reference ItemRegistry itemRegistry, final @Reference TimeZoneProvider timeZoneProvider) { + final @Reference ItemRegistry itemRegistry, final @Reference TimeZoneProvider timeZoneProvider, + final @Reference StartLevelService startLevelService) { this.bundleContext = bundleContext; this.eventPublisher = eventPublisher; this.itemRegistry = itemRegistry; this.timeZoneProvider = timeZoneProvider; + this.startLevelService = startLevelService; } @Override @@ -112,7 +116,7 @@ public Collection getTypes() { } else if (ItemCommandTriggerHandler.MODULE_TYPE_ID.equals(moduleTypeUID)) { return new ItemCommandTriggerHandler(trigger, ruleUID, bundleContext, itemRegistry); } else if (SystemTriggerHandler.STARTLEVEL_MODULE_TYPE_ID.equals(moduleTypeUID)) { - return new SystemTriggerHandler(trigger, bundleContext); + return new SystemTriggerHandler(trigger, bundleContext, startLevelService); } else if (ThingStatusTriggerHandler.CHANGE_MODULE_TYPE_ID.equals(moduleTypeUID) || ThingStatusTriggerHandler.UPDATE_MODULE_TYPE_ID.equals(moduleTypeUID)) { return new ThingStatusTriggerHandler(trigger, bundleContext); diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/SystemTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/SystemTriggerHandler.java index acf3b1f9384..9310be832a6 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/SystemTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/SystemTriggerHandler.java @@ -47,14 +47,15 @@ public class SystemTriggerHandler extends BaseTriggerModuleHandler implements Ev private final Integer startlevel; private final Set types; - private final BundleContext bundleContext; + private final StartLevelService startLevelService; private boolean triggered = false; - private ServiceRegistration eventSubscriberRegistration; + private final ServiceRegistration eventSubscriberRegistration; - public SystemTriggerHandler(Trigger module, BundleContext bundleContext) { + public SystemTriggerHandler(Trigger module, BundleContext bundleContext, StartLevelService startLevelService) { super(module); + this.startLevelService = startLevelService; this.startlevel = ((BigDecimal) module.getConfiguration().get(CFG_STARTLEVEL)).intValue(); if (STARTLEVEL_MODULE_TYPE_ID.equals(module.getTypeUID())) { this.types = Set.of(StartlevelEvent.TYPE); @@ -62,8 +63,18 @@ public SystemTriggerHandler(Trigger module, BundleContext bundleContext) { logger.warn("Module type '{}' is not (yet) handled by this class.", module.getTypeUID()); throw new IllegalArgumentException(module.getTypeUID() + " is no valid module type."); } - this.bundleContext = bundleContext; - eventSubscriberRegistration = this.bundleContext.registerService(EventSubscriber.class.getName(), this, null); + eventSubscriberRegistration = bundleContext.registerService(EventSubscriber.class.getName(), this, null); + } + + @Override + public void setCallback(ModuleHandlerCallback callback) { + super.setCallback(callback); + + // trigger immediately when start level is already reached + int currentStartLevel = startLevelService.getStartLevel(); + if (currentStartLevel > StartLevelService.STARTLEVEL_RULEENGINE && currentStartLevel >= startlevel) { + trigger(); + } } @Override diff --git a/bundles/org.openhab.core.automation/src/test/java/org/openhab/core/automation/internal/module/handler/SystemTriggerHandlerTest.java b/bundles/org.openhab.core.automation/src/test/java/org/openhab/core/automation/internal/module/handler/SystemTriggerHandlerTest.java new file mode 100644 index 00000000000..4d25df52dec --- /dev/null +++ b/bundles/org.openhab.core.automation/src/test/java/org/openhab/core/automation/internal/module/handler/SystemTriggerHandlerTest.java @@ -0,0 +1,165 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.automation.internal.module.handler; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.openhab.core.automation.Trigger; +import org.openhab.core.automation.handler.TriggerHandlerCallback; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.events.Event; +import org.openhab.core.events.system.SystemEventFactory; +import org.openhab.core.service.StartLevelService; +import org.osgi.framework.BundleContext; + +/** + * The {@link SystemTriggerHandlerTest} contains tests for the {@link SystemTriggerHandler} + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class SystemTriggerHandlerTest { + private static final int CFG_STARTLEVEL = 80; + + private @Mock @NonNullByDefault({}) BundleContext bundleContextMock; + private @Mock @NonNullByDefault({}) StartLevelService startLevelServiceMock; + private @Mock @NonNullByDefault({}) TriggerHandlerCallback callbackMock; + + private @Mock @NonNullByDefault({}) Trigger triggerMock; + + @BeforeEach + public void setup() { + when(triggerMock.getConfiguration()) + .thenReturn(new Configuration(Map.of(SystemTriggerHandler.CFG_STARTLEVEL, CFG_STARTLEVEL))); + when(triggerMock.getTypeUID()).thenReturn(SystemTriggerHandler.STARTLEVEL_MODULE_TYPE_ID); + } + + @Test + public void testDoesNotTriggerIfStartLevelTooLow() { + when(startLevelServiceMock.getStartLevel()).thenReturn(0); + + SystemTriggerHandler triggerHandler = new SystemTriggerHandler(triggerMock, bundleContextMock, + startLevelServiceMock); + triggerHandler.setCallback(callbackMock); + + verifyNoInteractions(callbackMock); + } + + @Test + public void testDoesTriggerImmediatelyIfStartLevelHigherOnInit() { + when(startLevelServiceMock.getStartLevel()).thenReturn(100); + + SystemTriggerHandler triggerHandler = new SystemTriggerHandler(triggerMock, bundleContextMock, + startLevelServiceMock); + triggerHandler.setCallback(callbackMock); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Map.class); + verify(callbackMock).triggered(eq(triggerMock), captor.capture()); + + Map configuration = (Map) captor.getValue(); + assertThat(configuration.get(SystemTriggerHandler.OUT_STARTLEVEL), is(CFG_STARTLEVEL)); + } + + @Test + public void testDoesNotTriggerIfStartLevelEventLower() { + when(startLevelServiceMock.getStartLevel()).thenReturn(0); + + SystemTriggerHandler triggerHandler = new SystemTriggerHandler(triggerMock, bundleContextMock, + startLevelServiceMock); + triggerHandler.setCallback(callbackMock); + + Event event = SystemEventFactory.createStartlevelEvent(70); + triggerHandler.receive(event); + + verifyNoInteractions(callbackMock); + } + + @Test + public void testDoesTriggerIfStartLevelEventHigher() { + when(startLevelServiceMock.getStartLevel()).thenReturn(0); + + SystemTriggerHandler triggerHandler = new SystemTriggerHandler(triggerMock, bundleContextMock, + startLevelServiceMock); + triggerHandler.setCallback(callbackMock); + + Event event = SystemEventFactory.createStartlevelEvent(100); + triggerHandler.receive(event); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Map.class); + verify(callbackMock).triggered(eq(triggerMock), captor.capture()); + + Map configuration = (Map) captor.getValue(); + assertThat(configuration.get(SystemTriggerHandler.OUT_STARTLEVEL), is(CFG_STARTLEVEL)); + } + + @Test + public void testDoesNotTriggerAfterInitialTrigger() { + when(startLevelServiceMock.getStartLevel()).thenReturn(100); + + SystemTriggerHandler triggerHandler = new SystemTriggerHandler(triggerMock, bundleContextMock, + startLevelServiceMock); + triggerHandler.setCallback(callbackMock); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Map.class); + verify(callbackMock).triggered(eq(triggerMock), captor.capture()); + + Map configuration = (Map) captor.getValue(); + assertThat(configuration.get(SystemTriggerHandler.OUT_STARTLEVEL), is(CFG_STARTLEVEL)); + + Event event = SystemEventFactory.createStartlevelEvent(100); + triggerHandler.receive(event); + + verifyNoMoreInteractions(callbackMock); + } + + @Test + public void testDoesNotTriggerAfterEventTrigger() { + when(startLevelServiceMock.getStartLevel()).thenReturn(0); + + SystemTriggerHandler triggerHandler = new SystemTriggerHandler(triggerMock, bundleContextMock, + startLevelServiceMock); + triggerHandler.setCallback(callbackMock); + + Event event = SystemEventFactory.createStartlevelEvent(100); + triggerHandler.receive(event); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Map.class); + verify(callbackMock).triggered(eq(triggerMock), captor.capture()); + + Map configuration = (Map) captor.getValue(); + assertThat(configuration.get(SystemTriggerHandler.OUT_STARTLEVEL), is(CFG_STARTLEVEL)); + + triggerHandler.receive(event); + + verifyNoMoreInteractions(callbackMock); + } +} diff --git a/bundles/org.openhab.core.test/src/main/java/org/openhab/core/test/java/JavaOSGiTest.java b/bundles/org.openhab.core.test/src/main/java/org/openhab/core/test/java/JavaOSGiTest.java index 27f55a748a9..4f944c548da 100644 --- a/bundles/org.openhab.core.test/src/main/java/org/openhab/core/test/java/JavaOSGiTest.java +++ b/bundles/org.openhab.core.test/src/main/java/org/openhab/core/test/java/JavaOSGiTest.java @@ -344,6 +344,15 @@ protected String getInterfaceName(final Object service) { } } + /** + * Get the OSGi {@link BundleContext} + * + * @return the bundle context + */ + protected BundleContext getBundleContext() { + return bundleContext; + } + /** * Registers a volatile storage service. */ diff --git a/itests/org.openhab.core.automation.integration.tests/itest.bndrun b/itests/org.openhab.core.automation.integration.tests/itest.bndrun index 41f22a29f5e..21aafaab4ef 100644 --- a/itests/org.openhab.core.automation.integration.tests/itest.bndrun +++ b/itests/org.openhab.core.automation.integration.tests/itest.bndrun @@ -61,4 +61,9 @@ Fragment-Host: org.openhab.core.automation junit-jupiter-engine;version='[5.9.2,5.9.3)',\ junit-platform-commons;version='[1.9.2,1.9.3)',\ junit-platform-engine;version='[1.9.2,1.9.3)',\ - junit-platform-launcher;version='[1.9.2,1.9.3)' + junit-platform-launcher;version='[1.9.2,1.9.3)',\ + net.bytebuddy.byte-buddy;version='[1.12.19,1.12.20)',\ + net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ + org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ + org.objenesis;version='[3.3.0,3.3.1)',\ + org.osgi.service.cm;version='[1.6.0,1.6.1)' diff --git a/itests/org.openhab.core.automation.integration.tests/src/main/java/org/openhab/core/automation/integration/test/AutomationIntegrationJsonTest.java b/itests/org.openhab.core.automation.integration.tests/src/main/java/org/openhab/core/automation/integration/test/AutomationIntegrationJsonTest.java index 33362852a65..14fcf8d0c68 100644 --- a/itests/org.openhab.core.automation.integration.tests/src/main/java/org/openhab/core/automation/integration/test/AutomationIntegrationJsonTest.java +++ b/itests/org.openhab.core.automation.integration.tests/src/main/java/org/openhab/core/automation/integration/test/AutomationIntegrationJsonTest.java @@ -15,6 +15,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; import java.util.Collection; import java.util.Optional; @@ -36,6 +37,7 @@ import org.openhab.core.automation.Trigger; import org.openhab.core.automation.events.RuleStatusInfoEvent; import org.openhab.core.automation.internal.RuleEngineImpl; +import org.openhab.core.automation.internal.module.factory.CoreModuleHandlerFactory; import org.openhab.core.automation.type.ActionType; import org.openhab.core.automation.type.Input; import org.openhab.core.automation.type.ModuleTypeRegistry; @@ -46,6 +48,7 @@ import org.openhab.core.events.Event; import org.openhab.core.events.EventPublisher; import org.openhab.core.events.EventSubscriber; +import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.items.Item; import org.openhab.core.items.ItemNotFoundException; import org.openhab.core.items.ItemProvider; @@ -55,6 +58,7 @@ import org.openhab.core.library.items.SwitchItem; import org.openhab.core.library.types.OnOffType; import org.openhab.core.service.ReadyMarker; +import org.openhab.core.service.StartLevelService; import org.openhab.core.storage.StorageService; import org.openhab.core.test.java.JavaOSGiTest; import org.openhab.core.test.storage.VolatileStorageService; @@ -90,7 +94,12 @@ public class AutomationIntegrationJsonTest extends JavaOSGiTest { public void before() { logger.info("@Before.begin"); - getService(ItemRegistry.class); + eventPublisher = getService(EventPublisher.class); + itemRegistry = getService(ItemRegistry.class); + CoreModuleHandlerFactory coreModuleHandlerFactory = new CoreModuleHandlerFactory(getBundleContext(), + eventPublisher, itemRegistry, mock(TimeZoneProvider.class), mock(StartLevelService.class)); + mock(CoreModuleHandlerFactory.class); + registerService(coreModuleHandlerFactory); ItemProvider itemProvider = new ItemProvider() { @@ -136,8 +145,6 @@ public void receive(Event e) { StorageService storageService = getService(StorageService.class); managedRuleProvider = getService(ManagedRuleProvider.class); - eventPublisher = getService(EventPublisher.class); - itemRegistry = getService(ItemRegistry.class); ruleRegistry = getService(RuleRegistry.class); ruleManager = getService(RuleManager.class); moduleTypeRegistry = getService(ModuleTypeRegistry.class); diff --git a/itests/org.openhab.core.automation.integration.tests/src/main/java/org/openhab/core/automation/integration/test/AutomationIntegrationTest.java b/itests/org.openhab.core.automation.integration.tests/src/main/java/org/openhab/core/automation/integration/test/AutomationIntegrationTest.java index d8bc15abec4..1632e368bb0 100644 --- a/itests/org.openhab.core.automation.integration.tests/src/main/java/org/openhab/core/automation/integration/test/AutomationIntegrationTest.java +++ b/itests/org.openhab.core.automation.integration.tests/src/main/java/org/openhab/core/automation/integration/test/AutomationIntegrationTest.java @@ -16,6 +16,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; import java.util.Collection; import java.util.Collections; @@ -24,6 +25,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; @@ -49,6 +51,7 @@ import org.openhab.core.automation.events.RuleStatusInfoEvent; import org.openhab.core.automation.events.RuleUpdatedEvent; import org.openhab.core.automation.internal.RuleEngineImpl; +import org.openhab.core.automation.internal.module.factory.CoreModuleHandlerFactory; import org.openhab.core.automation.template.RuleTemplate; import org.openhab.core.automation.template.RuleTemplateProvider; import org.openhab.core.automation.template.Template; @@ -67,6 +70,7 @@ import org.openhab.core.events.Event; import org.openhab.core.events.EventPublisher; import org.openhab.core.events.EventSubscriber; +import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.items.Item; import org.openhab.core.items.ItemNotFoundException; import org.openhab.core.items.ItemProvider; @@ -76,6 +80,7 @@ import org.openhab.core.library.items.SwitchItem; import org.openhab.core.library.types.OnOffType; import org.openhab.core.service.ReadyMarker; +import org.openhab.core.service.StartLevelService; import org.openhab.core.storage.StorageService; import org.openhab.core.test.java.JavaOSGiTest; import org.slf4j.Logger; @@ -106,7 +111,13 @@ public class AutomationIntegrationTest extends JavaOSGiTest { public void before() { logger.info("@Before.begin"); - getService(ItemRegistry.class); + eventPublisher = getService(EventPublisher.class); + itemRegistry = getService(ItemRegistry.class); + CoreModuleHandlerFactory coreModuleHandlerFactory = new CoreModuleHandlerFactory(getBundleContext(), + Objects.requireNonNull(eventPublisher), Objects.requireNonNull(itemRegistry), + mock(TimeZoneProvider.class), mock(StartLevelService.class)); + mock(CoreModuleHandlerFactory.class); + registerService(coreModuleHandlerFactory); ItemProvider itemProvider = new ItemProvider() { @Override @@ -148,8 +159,6 @@ public void removeProviderChangeListener(ProviderChangeListener listener) registerVolatileStorageService(); StorageService storageService = getService(StorageService.class); - eventPublisher = getService(EventPublisher.class); - itemRegistry = getService(ItemRegistry.class); ruleRegistry = getService(RuleRegistry.class); ruleEngine = getService(RuleManager.class); managedRuleProvider = getService(ManagedRuleProvider.class); diff --git a/itests/org.openhab.core.automation.module.core.tests/itest.bndrun b/itests/org.openhab.core.automation.module.core.tests/itest.bndrun index f86cb861a69..de798e868f5 100644 --- a/itests/org.openhab.core.automation.module.core.tests/itest.bndrun +++ b/itests/org.openhab.core.automation.module.core.tests/itest.bndrun @@ -61,4 +61,9 @@ Fragment-Host: org.openhab.core.automation junit-jupiter-engine;version='[5.9.2,5.9.3)',\ junit-platform-commons;version='[1.9.2,1.9.3)',\ junit-platform-engine;version='[1.9.2,1.9.3)',\ - junit-platform-launcher;version='[1.9.2,1.9.3)' + junit-platform-launcher;version='[1.9.2,1.9.3)',\ + net.bytebuddy.byte-buddy;version='[1.12.19,1.12.20)',\ + net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ + org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ + org.objenesis;version='[3.3.0,3.3.1)',\ + org.osgi.service.cm;version='[1.6.0,1.6.1)' diff --git a/itests/org.openhab.core.automation.module.core.tests/src/main/java/org/openhab/core/automation/internal/module/RunRuleModuleTest.java b/itests/org.openhab.core.automation.module.core.tests/src/main/java/org/openhab/core/automation/internal/module/RunRuleModuleTest.java index e3c68f6d6c9..b6e0dac1487 100644 --- a/itests/org.openhab.core.automation.module.core.tests/src/main/java/org/openhab/core/automation/internal/module/RunRuleModuleTest.java +++ b/itests/org.openhab.core.automation.module.core.tests/src/main/java/org/openhab/core/automation/internal/module/RunRuleModuleTest.java @@ -14,6 +14,7 @@ import static java.util.Map.entry; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; import java.util.ArrayList; import java.util.Collection; @@ -32,6 +33,7 @@ import org.openhab.core.automation.RuleRegistry; import org.openhab.core.automation.RuleStatus; import org.openhab.core.automation.internal.RuleEngineImpl; +import org.openhab.core.automation.internal.module.factory.CoreModuleHandlerFactory; import org.openhab.core.automation.util.ModuleBuilder; import org.openhab.core.automation.util.RuleBuilder; import org.openhab.core.common.registry.ProviderChangeListener; @@ -39,6 +41,7 @@ import org.openhab.core.events.Event; import org.openhab.core.events.EventPublisher; import org.openhab.core.events.EventSubscriber; +import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.items.Item; import org.openhab.core.items.ItemNotFoundException; import org.openhab.core.items.ItemProvider; @@ -48,6 +51,7 @@ import org.openhab.core.library.items.SwitchItem; import org.openhab.core.library.types.OnOffType; import org.openhab.core.service.ReadyMarker; +import org.openhab.core.service.StartLevelService; import org.openhab.core.test.java.JavaOSGiTest; import org.openhab.core.test.storage.VolatileStorageService; import org.osgi.framework.ServiceReference; @@ -68,6 +72,13 @@ public class RunRuleModuleTest extends JavaOSGiTest { @BeforeEach public void before() { + EventPublisher eventPublisher = getService(EventPublisher.class); + ItemRegistry itemRegistry = getService(ItemRegistry.class); + CoreModuleHandlerFactory coreModuleHandlerFactory = new CoreModuleHandlerFactory(getBundleContext(), + eventPublisher, itemRegistry, mock(TimeZoneProvider.class), mock(StartLevelService.class)); + mock(CoreModuleHandlerFactory.class); + registerService(coreModuleHandlerFactory); + registerService(new ItemProvider() { @Override public void addProviderChangeListener(final ProviderChangeListener listener) { diff --git a/itests/org.openhab.core.automation.module.core.tests/src/main/java/org/openhab/core/automation/internal/module/RuntimeRuleTest.java b/itests/org.openhab.core.automation.module.core.tests/src/main/java/org/openhab/core/automation/internal/module/RuntimeRuleTest.java index c7fddef1b7f..22d0e6a8f39 100644 --- a/itests/org.openhab.core.automation.module.core.tests/src/main/java/org/openhab/core/automation/internal/module/RuntimeRuleTest.java +++ b/itests/org.openhab.core.automation.module.core.tests/src/main/java/org/openhab/core/automation/internal/module/RuntimeRuleTest.java @@ -14,6 +14,7 @@ import static java.util.Map.entry; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; import java.util.Collection; import java.util.Collections; @@ -38,6 +39,7 @@ import org.openhab.core.automation.RuleStatusDetail; import org.openhab.core.automation.events.RuleStatusInfoEvent; import org.openhab.core.automation.internal.RuleEngineImpl; +import org.openhab.core.automation.internal.module.factory.CoreModuleHandlerFactory; import org.openhab.core.automation.internal.module.handler.CompareConditionHandler; import org.openhab.core.automation.type.ModuleTypeRegistry; import org.openhab.core.automation.util.ModuleBuilder; @@ -47,6 +49,7 @@ import org.openhab.core.events.Event; import org.openhab.core.events.EventPublisher; import org.openhab.core.events.EventSubscriber; +import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.items.Item; import org.openhab.core.items.ItemNotFoundException; import org.openhab.core.items.ItemProvider; @@ -56,6 +59,7 @@ import org.openhab.core.library.items.SwitchItem; import org.openhab.core.library.types.OnOffType; import org.openhab.core.service.ReadyMarker; +import org.openhab.core.service.StartLevelService; import org.openhab.core.test.java.JavaOSGiTest; import org.openhab.core.test.storage.VolatileStorageService; import org.openhab.core.types.Command; @@ -78,6 +82,13 @@ public class RuntimeRuleTest extends JavaOSGiTest { @BeforeEach public void before() { + EventPublisher eventPublisher = getService(EventPublisher.class); + ItemRegistry itemRegistry = getService(ItemRegistry.class); + CoreModuleHandlerFactory coreModuleHandlerFactory = new CoreModuleHandlerFactory(getBundleContext(), + eventPublisher, itemRegistry, mock(TimeZoneProvider.class), mock(StartLevelService.class)); + mock(CoreModuleHandlerFactory.class); + registerService(coreModuleHandlerFactory); + registerService(new ItemProvider() { @Override public void addProviderChangeListener(final ProviderChangeListener listener) { diff --git a/itests/org.openhab.core.automation.module.timer.tests/itest.bndrun b/itests/org.openhab.core.automation.module.timer.tests/itest.bndrun index c938385357a..364e3f2dcac 100644 --- a/itests/org.openhab.core.automation.module.timer.tests/itest.bndrun +++ b/itests/org.openhab.core.automation.module.timer.tests/itest.bndrun @@ -61,4 +61,9 @@ Fragment-Host: org.openhab.core.automation junit-jupiter-engine;version='[5.9.2,5.9.3)',\ junit-platform-commons;version='[1.9.2,1.9.3)',\ junit-platform-engine;version='[1.9.2,1.9.3)',\ - junit-platform-launcher;version='[1.9.2,1.9.3)' + junit-platform-launcher;version='[1.9.2,1.9.3)',\ + net.bytebuddy.byte-buddy;version='[1.12.19,1.12.20)',\ + net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ + org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ + org.objenesis;version='[3.3.0,3.3.1)',\ + org.osgi.service.cm;version='[1.6.0,1.6.1)' diff --git a/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/BasicConditionHandlerTest.java b/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/BasicConditionHandlerTest.java index 3a06424bad8..19c7b4b6d12 100644 --- a/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/BasicConditionHandlerTest.java +++ b/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/BasicConditionHandlerTest.java @@ -15,6 +15,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; import java.util.ArrayList; import java.util.Collection; @@ -38,6 +39,7 @@ import org.openhab.core.automation.RuleStatusInfo; import org.openhab.core.automation.Trigger; import org.openhab.core.automation.internal.RuleEngineImpl; +import org.openhab.core.automation.internal.module.factory.CoreModuleHandlerFactory; import org.openhab.core.automation.internal.module.handler.ItemCommandActionHandler; import org.openhab.core.automation.internal.module.handler.ItemStateTriggerHandler; import org.openhab.core.automation.util.ModuleBuilder; @@ -47,14 +49,17 @@ import org.openhab.core.events.Event; import org.openhab.core.events.EventPublisher; import org.openhab.core.events.EventSubscriber; +import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.items.Item; import org.openhab.core.items.ItemNotFoundException; import org.openhab.core.items.ItemProvider; +import org.openhab.core.items.ItemRegistry; import org.openhab.core.items.events.ItemCommandEvent; import org.openhab.core.items.events.ItemEventFactory; import org.openhab.core.library.items.SwitchItem; import org.openhab.core.library.types.OnOffType; import org.openhab.core.service.ReadyMarker; +import org.openhab.core.service.StartLevelService; import org.openhab.core.test.java.JavaOSGiTest; import org.openhab.core.test.storage.VolatileStorageService; import org.slf4j.Logger; @@ -81,6 +86,13 @@ public abstract class BasicConditionHandlerTest extends JavaOSGiTest { */ @BeforeEach public void beforeBase() { + EventPublisher eventPublisher = getService(EventPublisher.class); + ItemRegistry itemRegistry = getService(ItemRegistry.class); + CoreModuleHandlerFactory coreModuleHandlerFactory = new CoreModuleHandlerFactory(getBundleContext(), + eventPublisher, itemRegistry, mock(TimeZoneProvider.class), mock(StartLevelService.class)); + mock(CoreModuleHandlerFactory.class); + registerService(coreModuleHandlerFactory); + ItemProvider itemProvider = new ItemProvider() { @Override public void addProviderChangeListener(ProviderChangeListener listener) { diff --git a/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/DayOfWeekConditionHandlerTest.java b/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/DayOfWeekConditionHandlerTest.java index 8f5cc114ba8..61d3c6c54a8 100644 --- a/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/DayOfWeekConditionHandlerTest.java +++ b/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/DayOfWeekConditionHandlerTest.java @@ -14,6 +14,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.mock; import java.text.SimpleDateFormat; import java.time.ZonedDateTime; @@ -24,12 +25,18 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.openhab.core.automation.Condition; +import org.openhab.core.automation.internal.module.factory.CoreModuleHandlerFactory; import org.openhab.core.automation.internal.module.handler.DayOfWeekConditionHandler; import org.openhab.core.automation.type.ModuleTypeRegistry; import org.openhab.core.automation.util.ModuleBuilder; import org.openhab.core.config.core.Configuration; +import org.openhab.core.events.EventPublisher; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.items.ItemRegistry; +import org.openhab.core.service.StartLevelService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,6 +57,16 @@ public DayOfWeekConditionHandlerTest() { logger.info("Today is {}", dayOfWeek); } + @BeforeEach + public void before() { + EventPublisher eventPublisher = getService(EventPublisher.class); + ItemRegistry itemRegistry = getService(ItemRegistry.class); + CoreModuleHandlerFactory coreModuleHandlerFactory = new CoreModuleHandlerFactory(getBundleContext(), + eventPublisher, itemRegistry, mock(TimeZoneProvider.class), mock(StartLevelService.class)); + mock(CoreModuleHandlerFactory.class); + registerService(coreModuleHandlerFactory); + } + @Test public void assertThatConditionWorks() { Configuration conditionConfiguration = new Configuration( diff --git a/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/RuntimeRuleTest.java b/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/RuntimeRuleTest.java index 1077c801b41..120f02c4b17 100644 --- a/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/RuntimeRuleTest.java +++ b/itests/org.openhab.core.automation.module.timer.tests/src/main/java/org/openhab/core/automation/module/timer/internal/RuntimeRuleTest.java @@ -15,6 +15,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; import java.util.Collection; import java.util.HashMap; @@ -37,6 +38,7 @@ import org.openhab.core.automation.RuleStatusInfo; import org.openhab.core.automation.Trigger; import org.openhab.core.automation.internal.RuleEngineImpl; +import org.openhab.core.automation.internal.module.factory.CoreModuleHandlerFactory; import org.openhab.core.automation.internal.module.handler.GenericCronTriggerHandler; import org.openhab.core.automation.type.ModuleTypeRegistry; import org.openhab.core.automation.util.ModuleBuilder; @@ -44,12 +46,16 @@ import org.openhab.core.common.registry.ProviderChangeListener; import org.openhab.core.config.core.Configuration; import org.openhab.core.events.Event; +import org.openhab.core.events.EventPublisher; import org.openhab.core.events.EventSubscriber; +import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.items.Item; import org.openhab.core.items.ItemProvider; +import org.openhab.core.items.ItemRegistry; import org.openhab.core.items.events.ItemCommandEvent; import org.openhab.core.library.items.SwitchItem; import org.openhab.core.service.ReadyMarker; +import org.openhab.core.service.StartLevelService; import org.openhab.core.test.java.JavaOSGiTest; import org.openhab.core.test.storage.VolatileStorageService; import org.slf4j.Logger; @@ -72,6 +78,13 @@ public class RuntimeRuleTest extends JavaOSGiTest { @BeforeEach public void before() { + EventPublisher eventPublisher = getService(EventPublisher.class); + ItemRegistry itemRegistry = getService(ItemRegistry.class); + CoreModuleHandlerFactory coreModuleHandlerFactory = new CoreModuleHandlerFactory(getBundleContext(), + eventPublisher, itemRegistry, mock(TimeZoneProvider.class), mock(StartLevelService.class)); + mock(CoreModuleHandlerFactory.class); + registerService(coreModuleHandlerFactory); + ItemProvider itemProvider = new TestItemProvider(Set.of(new SwitchItem("myLampItem"))); registerService(itemProvider); registerService(volatileStorageService); diff --git a/itests/org.openhab.core.automation.tests/itest.bndrun b/itests/org.openhab.core.automation.tests/itest.bndrun index 3d9499b316a..55ed88082d3 100644 --- a/itests/org.openhab.core.automation.tests/itest.bndrun +++ b/itests/org.openhab.core.automation.tests/itest.bndrun @@ -61,4 +61,9 @@ Fragment-Host: org.openhab.core.automation junit-jupiter-engine;version='[5.9.2,5.9.3)',\ junit-platform-commons;version='[1.9.2,1.9.3)',\ junit-platform-engine;version='[1.9.2,1.9.3)',\ - junit-platform-launcher;version='[1.9.2,1.9.3)' + junit-platform-launcher;version='[1.9.2,1.9.3)',\ + net.bytebuddy.byte-buddy;version='[1.12.19,1.12.20)',\ + net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ + org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ + org.objenesis;version='[3.3.0,3.3.1)',\ + org.osgi.service.cm;version='[1.6.0,1.6.1)' diff --git a/itests/org.openhab.core.automation.tests/src/main/java/org/openhab/core/automation/event/RuleEventTest.java b/itests/org.openhab.core.automation.tests/src/main/java/org/openhab/core/automation/event/RuleEventTest.java index 42d3e4aa8da..fabce422523 100644 --- a/itests/org.openhab.core.automation.tests/src/main/java/org/openhab/core/automation/event/RuleEventTest.java +++ b/itests/org.openhab.core.automation.tests/src/main/java/org/openhab/core/automation/event/RuleEventTest.java @@ -16,6 +16,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; import java.util.ArrayList; import java.util.Collection; @@ -41,6 +42,7 @@ import org.openhab.core.automation.events.RuleStatusInfoEvent; import org.openhab.core.automation.events.RuleUpdatedEvent; import org.openhab.core.automation.internal.RuleEngineImpl; +import org.openhab.core.automation.internal.module.factory.CoreModuleHandlerFactory; import org.openhab.core.automation.util.ModuleBuilder; import org.openhab.core.automation.util.RuleBuilder; import org.openhab.core.common.registry.ProviderChangeListener; @@ -48,6 +50,7 @@ import org.openhab.core.events.Event; import org.openhab.core.events.EventPublisher; import org.openhab.core.events.EventSubscriber; +import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.items.Item; import org.openhab.core.items.ItemNotFoundException; import org.openhab.core.items.ItemProvider; @@ -57,6 +60,7 @@ import org.openhab.core.library.items.SwitchItem; import org.openhab.core.library.types.OnOffType; import org.openhab.core.service.ReadyMarker; +import org.openhab.core.service.StartLevelService; import org.openhab.core.test.java.JavaOSGiTest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -80,6 +84,13 @@ public RuleEventTest() { @BeforeEach public void before() { + EventPublisher eventPublisher = getService(EventPublisher.class); + ItemRegistry itemRegistry = getService(ItemRegistry.class); + CoreModuleHandlerFactory coreModuleHandlerFactory = new CoreModuleHandlerFactory(getBundleContext(), + eventPublisher, itemRegistry, mock(TimeZoneProvider.class), mock(StartLevelService.class)); + mock(CoreModuleHandlerFactory.class); + registerService(coreModuleHandlerFactory); + ItemProvider itemProvider = new ItemProvider() { @Override @@ -148,9 +159,7 @@ public void receive(Event event) { ruleRegistry.add(rule); ruleEngine.setEnabled(rule.getUID(), true); - waitForAssert(() -> { - assertThat(ruleEngine.getStatusInfo(rule.getUID()).getStatus(), is(RuleStatus.IDLE)); - }); + waitForAssert(() -> assertThat(ruleEngine.getStatusInfo(rule.getUID()).getStatus(), is(RuleStatus.IDLE))); // TEST RULE From 88bb76c21c1f0938ef520ad28bae91a3e91f1f88 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 28 May 2023 10:15:16 +0200 Subject: [PATCH 098/126] Add event information in rules for time, manual and RunRuleAction trigger (#2965) * Add event information for time triggers Signed-off-by: Jan N. Klug --- .../rest/internal/RuleResource.java | 11 +- .../events/AutomationEventFactory.java | 101 ++++++++++++++++++ .../automation/events/ExecutionEvent.java | 48 +++++++++ .../core/automation/events/TimerEvent.java | 49 +++++++++ .../handler/DateTimeTriggerHandler.java | 8 +- .../handler/GenericCronTriggerHandler.java | 10 +- .../module/handler/RunRuleActionHandler.java | 14 ++- .../handler/TimeOfDayTriggerHandler.java | 11 +- .../core/events/AbstractEventFactory.java | 3 +- 9 files changed, 246 insertions(+), 9 deletions(-) create mode 100644 bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/events/AutomationEventFactory.java create mode 100644 bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/events/ExecutionEvent.java create mode 100644 bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/events/TimerEvent.java diff --git a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java index 27b46dfc0c6..ac226f289e9 100644 --- a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java +++ b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java @@ -64,12 +64,14 @@ import org.openhab.core.automation.dto.RuleDTOMapper; import org.openhab.core.automation.dto.TriggerDTO; import org.openhab.core.automation.dto.TriggerDTOMapper; +import org.openhab.core.automation.events.AutomationEventFactory; import org.openhab.core.automation.rest.internal.dto.EnrichedRuleDTO; import org.openhab.core.automation.rest.internal.dto.EnrichedRuleDTOMapper; import org.openhab.core.automation.util.ModuleBuilder; import org.openhab.core.automation.util.RuleBuilder; import org.openhab.core.config.core.ConfigUtil; import org.openhab.core.config.core.Configuration; +import org.openhab.core.events.Event; import org.openhab.core.io.rest.DTOMapper; import org.openhab.core.io.rest.JSONResponse; import org.openhab.core.io.rest.RESTConstants; @@ -331,7 +333,14 @@ public Response runNow(@PathParam("ruleUID") @Parameter(description = "ruleUID") ruleUID); return Response.status(Status.NOT_FOUND).build(); } else { - ruleManager.runNow(ruleUID, false, context); + if (context == null || context.isEmpty()) { + // only add event to context if no context given, otherwise it might interfere with the intention of the + // provided context + Event event = AutomationEventFactory.createExecutionEvent(ruleUID, null, "manual"); + ruleManager.runNow(ruleUID, false, Map.of("event", event)); + } else { + ruleManager.runNow(ruleUID, false, context); + } return Response.ok().build(); } } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/events/AutomationEventFactory.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/events/AutomationEventFactory.java new file mode 100644 index 00000000000..fe85cf3794f --- /dev/null +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/events/AutomationEventFactory.java @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.automation.events; + +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.events.AbstractEventFactory; +import org.openhab.core.events.Event; +import org.openhab.core.events.EventFactory; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is a factory that creates Timer and Execution Events. + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +@Component(service = EventFactory.class, immediate = true) +public class AutomationEventFactory extends AbstractEventFactory { + private static final String MODULE_IDENTIFIER = "{moduleId}"; + private static final String TIMER_EVENT_TOPIC = "openhab/timer/" + MODULE_IDENTIFIER + "/triggered"; + private static final String EXECUTION_EVENT_TOPIC = "openhab/execution/" + MODULE_IDENTIFIER + "/triggered"; + + private final Logger logger = LoggerFactory.getLogger(AutomationEventFactory.class); + + private static final Set SUPPORTED_TYPES = Set.of(TimerEvent.TYPE, ExecutionEvent.TYPE); + + public AutomationEventFactory() { + super(SUPPORTED_TYPES); + } + + @Override + protected Event createEventByType(String eventType, String topic, String payload, @Nullable String source) + throws Exception { + logger.trace("creating ruleEvent of type: {}", eventType); + if (TimerEvent.TYPE.equals(eventType)) { + return createTimerEvent(topic, payload, Objects.requireNonNullElse(source, "")); + } else if (ExecutionEvent.TYPE.equals(eventType)) { + if (source == null) { + throw new IllegalArgumentException("'source' must not be null for execution events"); + } + return createExecutionEvent(topic, payload, source); + } + throw new IllegalArgumentException("The event type '" + eventType + "' is not supported by this factory."); + } + + private Event createTimerEvent(String topic, String payload, String source) { + return new TimerEvent(topic, payload, source); + } + + private Event createExecutionEvent(String topic, String payload, String source) { + return new ExecutionEvent(topic, payload, source); + } + + /** + * Creates a {@link TimerEvent} + * + * @param moduleId the module type id of this event + * @param label The label (or id) of this object + * @param configuration the configuration of the trigger + * @return the created event + */ + public static TimerEvent createTimerEvent(String moduleId, @Nullable String label, + Map configuration) { + String topic = TIMER_EVENT_TOPIC.replace(MODULE_IDENTIFIER, moduleId); + String payload = serializePayload(configuration); + return new TimerEvent(topic, payload, label); + } + + /** + * Creates an {@link ExecutionEvent} + * + * @param moduleId the module type id of this event + * @param payload A map with additional information like preceding events when rules are called from other rules + * (optional) + * @param source The source of this event (e.g. "script" or "manual") + * @return the created event + */ + public static ExecutionEvent createExecutionEvent(String moduleId, @Nullable Map payload, + String source) { + String topic = EXECUTION_EVENT_TOPIC.replace(MODULE_IDENTIFIER, moduleId); + String serializedPayload = serializePayload(Objects.requireNonNullElse(payload, Map.of())); + return new ExecutionEvent(topic, serializedPayload, source); + } +} diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/events/ExecutionEvent.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/events/ExecutionEvent.java new file mode 100644 index 00000000000..20177bdec23 --- /dev/null +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/events/ExecutionEvent.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.automation.events; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.events.AbstractEvent; + +/** + * An {@link ExecutionEvent} is only used to notify rules when a script or the REST API trigger the run. + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class ExecutionEvent extends AbstractEvent { + + public static final String TYPE = ExecutionEvent.class.getSimpleName(); + + /** + * Constructs a new rule execution event + * + * @param topic the topic of the event + * @param payload the payload of the event + * @param source the source of the event + */ + public ExecutionEvent(String topic, String payload, String source) { + super(topic, payload, source); + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public String toString() { + return "Execution triggered by " + getSource(); + } +} diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/events/TimerEvent.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/events/TimerEvent.java new file mode 100644 index 00000000000..98db2e0bf7d --- /dev/null +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/events/TimerEvent.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.automation.events; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.events.AbstractEvent; + +/** + * An {@link TimerEvent} is only used to notify rules when timer triggers fire. + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class TimerEvent extends AbstractEvent { + + public static final String TYPE = TimerEvent.class.getSimpleName(); + + /** + * Constructs a new timer event + * + * @param topic the topic of the event + * @param payload the payload of the event (contains trigger configuration) + * @param source the source of the event + */ + public TimerEvent(String topic, String payload, @Nullable String source) { + super(topic, payload, source); + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public String toString() { + return "Timer " + getSource() + " triggered."; + } +} diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/DateTimeTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/DateTimeTriggerHandler.java index 756a09c600b..de01e4dabff 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/DateTimeTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/DateTimeTriggerHandler.java @@ -14,12 +14,16 @@ import java.time.ZoneId; import java.time.format.DateTimeFormatter; +import java.util.Map; +import java.util.Objects; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.automation.ModuleHandlerCallback; import org.openhab.core.automation.Trigger; +import org.openhab.core.automation.events.AutomationEventFactory; +import org.openhab.core.automation.events.TimerEvent; import org.openhab.core.automation.handler.BaseTriggerModuleHandler; import org.openhab.core.automation.handler.TimeBasedTriggerHandler; import org.openhab.core.automation.handler.TriggerHandlerCallback; @@ -114,7 +118,9 @@ public synchronized void setCallback(ModuleHandlerCallback callback) { public void run() { ModuleHandlerCallback callback = this.callback; if (callback instanceof TriggerHandlerCallback triggerHandlerCallback) { - triggerHandlerCallback.triggered(module); + TimerEvent event = AutomationEventFactory.createTimerEvent(module.getTypeUID(), + Objects.requireNonNullElse(module.getLabel(), module.getId()), Map.of(CONFIG_ITEM_NAME, itemName)); + triggerHandlerCallback.triggered(module, Map.of("event", event)); } else { logger.debug("Tried to trigger, but callback isn't available!"); } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GenericCronTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GenericCronTriggerHandler.java index cb12a8bdbac..9bcfab5acf4 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GenericCronTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GenericCronTriggerHandler.java @@ -12,10 +12,15 @@ */ package org.openhab.core.automation.internal.module.handler; +import java.util.Map; +import java.util.Objects; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.automation.ModuleHandlerCallback; import org.openhab.core.automation.Trigger; +import org.openhab.core.automation.events.AutomationEventFactory; +import org.openhab.core.automation.events.TimerEvent; import org.openhab.core.automation.handler.BaseTriggerModuleHandler; import org.openhab.core.automation.handler.TimeBasedTriggerHandler; import org.openhab.core.automation.handler.TriggerHandlerCallback; @@ -80,7 +85,10 @@ public synchronized void dispose() { @Override public void run() { if (callback != null) { - ((TriggerHandlerCallback) callback).triggered(module); + TimerEvent event = AutomationEventFactory.createTimerEvent(module.getTypeUID(), + Objects.requireNonNullElse(module.getLabel(), module.getId()), + Map.of(CFG_CRON_EXPRESSION, expression)); + ((TriggerHandlerCallback) callback).triggered(module, Map.of("event", event)); } else { logger.debug("Tried to trigger, but callback isn't available!"); } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/RunRuleActionHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/RunRuleActionHandler.java index 6755dbe9952..9a4c329d1ff 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/RunRuleActionHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/RunRuleActionHandler.java @@ -12,14 +12,17 @@ */ package org.openhab.core.automation.internal.module.handler; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.automation.Action; +import org.openhab.core.automation.events.AutomationEventFactory; import org.openhab.core.automation.handler.BaseActionModuleHandler; import org.openhab.core.config.core.Configuration; +import org.openhab.core.events.Event; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,6 +67,7 @@ public class RunRuleActionHandler extends BaseActionModuleHandler { * the UIDs of the rules to be executed. */ private final List ruleUIDs; + private final String moduleId; /** * boolean to express if the conditions should be considered, defaults to @@ -84,16 +88,22 @@ public RunRuleActionHandler(final Action module) { throw new IllegalArgumentException("'ruleUIDs' property must not be null."); } if (config.get(CONSIDER_CONDITIONS_KEY) != null && config.get(CONSIDER_CONDITIONS_KEY) instanceof Boolean) { - this.considerConditions = ((Boolean) config.get(CONSIDER_CONDITIONS_KEY)).booleanValue(); + this.considerConditions = (Boolean) config.get(CONSIDER_CONDITIONS_KEY); } + this.moduleId = module.getId(); } @Override public @Nullable Map execute(Map context) { // execute each rule after the other; at the moment synchronously + Object previousEvent = context.get("event"); + Event event = AutomationEventFactory.createExecutionEvent(moduleId, + previousEvent instanceof Event ? Map.of("previous", previousEvent) : null, "runRuleAction"); + Map newContext = new HashMap<>(context); + newContext.put("event", event); for (String uid : ruleUIDs) { if (callback != null) { - callback.runNow(uid, considerConditions, context); + callback.runNow(uid, considerConditions, newContext); } else { logger.warn("Action is not applied to {} because rule engine is not available.", uid); } diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/TimeOfDayTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/TimeOfDayTriggerHandler.java index e3729c297a7..a1a2b2d1db2 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/TimeOfDayTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/TimeOfDayTriggerHandler.java @@ -13,11 +13,15 @@ package org.openhab.core.automation.internal.module.handler; import java.text.MessageFormat; +import java.util.Map; +import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.automation.ModuleHandlerCallback; import org.openhab.core.automation.Trigger; +import org.openhab.core.automation.events.AutomationEventFactory; +import org.openhab.core.automation.events.TimerEvent; import org.openhab.core.automation.handler.BaseTriggerModuleHandler; import org.openhab.core.automation.handler.TimeBasedTriggerHandler; import org.openhab.core.automation.handler.TriggerHandlerCallback; @@ -46,13 +50,14 @@ public class TimeOfDayTriggerHandler extends BaseTriggerModuleHandler public static final String CFG_TIME = "time"; private final CronScheduler scheduler; + private final String time; private final String expression; private @Nullable ScheduledCompletableFuture schedule; public TimeOfDayTriggerHandler(Trigger module, CronScheduler scheduler) { super(module); this.scheduler = scheduler; - String time = module.getConfiguration().get(CFG_TIME).toString(); + this.time = module.getConfiguration().get(CFG_TIME).toString(); this.expression = buildExpressionFromConfigurationTime(time); } @@ -83,7 +88,9 @@ private void scheduleJob() { @Override public void run() { if (callback != null) { - ((TriggerHandlerCallback) callback).triggered(module); + TimerEvent event = AutomationEventFactory.createTimerEvent(module.getTypeUID(), + Objects.requireNonNullElse(module.getLabel(), module.getId()), Map.of(CFG_TIME, time)); + ((TriggerHandlerCallback) callback).triggered(module, Map.of("event", event)); } else { logger.debug("Tried to trigger, but callback isn't available!"); } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/events/AbstractEventFactory.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/events/AbstractEventFactory.java index f54ecebd4f7..2a2d90b2e2a 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/events/AbstractEventFactory.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/events/AbstractEventFactory.java @@ -22,8 +22,7 @@ /** * The {@link AbstractEventFactory} defines an abstract implementation of the {@link EventFactory} interface. Subclasses * must implement the abstract method {@link #createEventByType(String, String, String, String)} in order to create - * event - * instances based on the event type. + * event instances based on the event type. * * @author Stefan Bußweiler - Initial contribution */ From 3e8905bcb741767e40e35fc5d83eba51b18adf97 Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Sun, 28 May 2023 13:20:05 +0200 Subject: [PATCH 099/126] New Crowdin updates (#3638) * New translations lsp.properties (Italian) * New translations hli.properties (Italian) * New translations addons.properties (Finnish) * New translations automation.properties (Italian) * New translations LanguageSupport.properties (Portuguese) --- .../OH-INF/i18n/automation_it.properties | 4 ++++ .../resources/OH-INF/i18n/lsp_it.properties | 2 +- .../resources/OH-INF/i18n/hli_it.properties | 4 ++-- .../resources/LanguageSupport_pt.properties | 24 +++++++++---------- .../OH-INF/i18n/addons_fi.properties | 2 +- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_it.properties b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_it.properties index ba888e231c0..be0e180238f 100644 --- a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_it.properties +++ b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_it.properties @@ -66,6 +66,8 @@ module-type.core.GroupCommandTrigger.config.command.option.OPEN = APERTO module-type.core.GroupCommandTrigger.config.command.option.CLOSED = CHIUSO module-type.core.GroupCommandTrigger.config.command.option.UP = SU module-type.core.GroupCommandTrigger.config.command.option.DOWN = GIÙ +module-type.core.GroupCommandTrigger.output.triggeringGroup.label = Gruppo Di Attivazione +module-type.core.GroupCommandTrigger.output.triggeringGroup.description = il gruppo a cui appartiene l'item module-type.core.GroupCommandTrigger.output.triggeringItem.label = Elemento innescante module-type.core.GroupCommandTrigger.output.triggeringItem.description = Il membro del gruppo che ha ricevuto il comando module-type.core.GroupCommandTrigger.output.command.label = Comando @@ -118,6 +120,8 @@ module-type.core.GroupStateUpdateTrigger.config.state.option.OPEN = APERTO module-type.core.GroupStateUpdateTrigger.config.state.option.CLOSED = CHIUSO module-type.core.GroupStateUpdateTrigger.config.state.option.UP = SU module-type.core.GroupStateUpdateTrigger.config.state.option.DOWN = GIÙ +module-type.core.GroupStateUpdateTrigger.output.triggeringGroup.label = Gruppo Di Attivazione +module-type.core.GroupStateUpdateTrigger.output.triggeringGroup.description = il gruppo a cui appartiene l'item module-type.core.GroupStateUpdateTrigger.output.triggeringItem.label = Elemento innescante module-type.core.GroupStateUpdateTrigger.output.triggeringItem.description = Il membro del gruppo che ha aggiornato il suo stato module-type.core.GroupStateUpdateTrigger.output.state.label = Stato diff --git a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_it.properties b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_it.properties index fcc0176b2b9..211de11a7af 100644 --- a/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_it.properties +++ b/bundles/org.openhab.core.model.lsp/src/main/resources/OH-INF/i18n/lsp_it.properties @@ -1,4 +1,4 @@ system.config.lsp.port.label = Porta -system.config.lsp.port.description = La porta usata dal server della traduzione. +system.config.lsp.port.description = La porta che il server della traduzione ascolta. service.system.lsp.label = Server Traduzione (LSP) diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_it.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_it.properties index e41c09b1350..f7349bb7db7 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_it.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_it.properties @@ -1,4 +1,4 @@ system.config.rulehli.item.label = Item Comando Vocale -system.config.rulehli.item.description = La Stringa dell'Item passata al comando vocale. +system.config.rulehli.item.description = L'elemento stringa a cui passare i comandi vocali. -service.system.rulehli.label = Regola Inteprete Vocale +service.system.rulehli.label = Regola Interprete Vocale diff --git a/bundles/org.openhab.core.voice/src/main/resources/LanguageSupport_pt.properties b/bundles/org.openhab.core.voice/src/main/resources/LanguageSupport_pt.properties index 1ba5c43c48c..05bdbe461a3 100644 --- a/bundles/org.openhab.core.voice/src/main/resources/LanguageSupport_pt.properties +++ b/bundles/org.openhab.core.voice/src/main/resources/LanguageSupport_pt.properties @@ -1,21 +1,21 @@ ok = Ok. -sorry = Desculpa, eu não entendi. -error = Desculpa. Ocorreu um erro inesperado. +sorry = Desculpe, não percebi. +error = Desculpe. Ocorreu um erro inesperado. multiple_objects = Há mais de um objeto com um nome semelhante. no_objects = Não há nenhum objeto com esse nome. -command_not_accepted = O objeto referenciado não aceita o comando . -state_already_singular = O objeto já está . -state_already_plural = Todos objetos já estão . +command_not_accepted = O objeto indicado não aceita o comando . +state_already_singular = O objeto já é ou está . +state_already_plural = Todos os objetos já são ou estão . state_current = neste estado state_on = ligado state_off = desligado -state_up = para cima -state_down = para baixo -state_play = reproduzindo -state_pause = pausado +state_up = acima +state_down = baixo +state_play = ativo +state_pause = parado state_rewind = retrocedendo -state_fastforward = avançando rápido -state_open = aberta -state_closed = fechada +state_fastforward = avanço rápido +state_open = aberto +state_closed = fechado state_undef = indefinido state_null = não definido diff --git a/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/addons_fi.properties b/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/addons_fi.properties index b97a02d1559..12e78a8e06a 100644 --- a/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/addons_fi.properties +++ b/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/addons_fi.properties @@ -1,6 +1,6 @@ system.config.addons.includeIncompatible.label = Sisällytä (mahdollisesti) yhteensopimattomat lisäosat system.config.addons.includeIncompatible.description = Jotkin lisäpalvelut voivat tarjota lisäosia, joissa yhteensopivuutta nykyisen järjestelmän kanssa ei ole odotettavissa. Tämän valinnan käyttöönotto sisällyttää nämä merkinnät saatavilla olevien lisäosien luetteloon. system.config.addons.remote.label = Käytä etäversiovarantoa -system.config.addons.remote.description = Määrittää, pitäisikö openHAB\:n käyttää lisäosien asennusta varten etätietovarantoa. +system.config.addons.remote.description = Määrittää, pitäisikö openHAB:n käyttää lisäosien asennusta varten etätietovarantoa. service.system.addons.label = Lisäosien hallinta From 5550ea79b2ed661d4d3aa5f0a35178f475d6969a Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Mon, 29 May 2023 14:26:59 +0200 Subject: [PATCH 100/126] New Crowdin updates (#3639) * New translations audio.properties (Romanian) * New translations addons.properties (Romanian) * New translations i18n.properties (Romanian) * New translations network.properties (Romanian) * New translations marketplace.properties (Romanian) * New translations validation.properties (Finnish) * New translations scriptprofile.properties (French) * New translations scriptprofile.properties (Slovenian) * New translations validation.properties (Hebrew) * New translations validation.properties (Polish) * New translations validation.properties (Slovenian) * New translations hli.properties (French) * New translations hli.properties (Polish) * New translations automation.properties (Hebrew) * New translations automation.properties (Polish) --- .../OH-INF/i18n/marketplace_ro.properties | 13 +++++++++++++ .../resources/OH-INF/i18n/audio_ro.properties | 6 ++++++ .../OH-INF/i18n/scriptprofile_fr.properties | 4 ++++ .../OH-INF/i18n/scriptprofile_sl.properties | 4 ++++ .../OH-INF/i18n/automation_he.properties | 4 ++++ .../OH-INF/i18n/automation_pl.properties | 4 ++++ .../OH-INF/i18n/validation_fi.properties | 4 ++++ .../OH-INF/i18n/validation_he.properties | 2 ++ .../OH-INF/i18n/validation_pl.properties | 2 ++ .../OH-INF/i18n/validation_sl.properties | 2 ++ .../resources/OH-INF/i18n/hli_fr.properties | 2 +- .../resources/OH-INF/i18n/hli_pl.properties | 2 +- .../resources/OH-INF/i18n/addons_ro.properties | 6 ++++++ .../resources/OH-INF/i18n/i18n_ro.properties | 18 ++++++++++++++++++ .../OH-INF/i18n/network_ro.properties | 10 ++++++++++ 15 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 bundles/org.openhab.core.addon.marketplace/src/main/resources/OH-INF/i18n/marketplace_ro.properties create mode 100644 bundles/org.openhab.core.audio/src/main/resources/OH-INF/i18n/audio_ro.properties create mode 100644 bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_fr.properties create mode 100644 bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_sl.properties create mode 100644 bundles/org.openhab.core/src/main/resources/OH-INF/i18n/addons_ro.properties create mode 100644 bundles/org.openhab.core/src/main/resources/OH-INF/i18n/i18n_ro.properties create mode 100644 bundles/org.openhab.core/src/main/resources/OH-INF/i18n/network_ro.properties diff --git a/bundles/org.openhab.core.addon.marketplace/src/main/resources/OH-INF/i18n/marketplace_ro.properties b/bundles/org.openhab.core.addon.marketplace/src/main/resources/OH-INF/i18n/marketplace_ro.properties new file mode 100644 index 00000000000..995bde82834 --- /dev/null +++ b/bundles/org.openhab.core.addon.marketplace/src/main/resources/OH-INF/i18n/marketplace_ro.properties @@ -0,0 +1,13 @@ +system.config.jsonaddonservice.showUnstable.label = Arată suplimentele nestabilite +system.config.jsonaddonservice.showUnstable.description = Include intrări care nu au fost etichetate ca "stabile". Aceste suplimente ar trebui să fie utilizate numai în scopuri de testare și nu sunt considerate ca fiind pregătite pentru sistemul de producție. +system.config.jsonaddonservice.urls.label = URL-uri pentru servicii suplimentare +system.config.jsonaddonservice.urls.description = Conducta (|) separă lista de URLS care oferă servicii suplimentare terțe prin fișiere Json. Atenție\: Pachetele distribuite prin servicii adiționale terțe ar putea să nu fie reexaminate corespunzător și ar putea conține un cod periculos și astfel să dăuneze sistemului dvs. +system.config.marketplace.apiKey.label = API Key for community.openhab.org +system.config.marketplace.apiKey.description = Specificați cheia API pe care să o utilizați pe forumul comunității (pentru personal și curatori - acest lucru permite, de exemplu, să vedeți conținut care nu este încă revizuit sau ascuns în alt mod de publicul larg). Lăsați necompletat dacă nu aveți unul. +system.config.marketplace.enable.label = Activați bazarul comunitar +system.config.marketplace.enable.description = Dacă este setat pe fals niciun supliment din piața comunitară nu va fi afișat. Suplimentele instalate vor fi încă disponibile. +system.config.marketplace.showUnpublished.label = Arată intrările nepublicate +system.config.marketplace.showUnpublished.description = Include intrările care nu au fost etichetate ca publicate. Avertisment\: aceasta poate include intrări care nu sunt gata și care ar putea să nu funcționeze sau să nu afecteze instalarea. Activează pe propriul risc doar în scopuri de testare. + +service.system.marketplace.label = Piață comunitară +service.system.jsonaddonservice.label = Json 3rd party Add-on Service diff --git a/bundles/org.openhab.core.audio/src/main/resources/OH-INF/i18n/audio_ro.properties b/bundles/org.openhab.core.audio/src/main/resources/OH-INF/i18n/audio_ro.properties new file mode 100644 index 00000000000..c3a9819d97f --- /dev/null +++ b/bundles/org.openhab.core.audio/src/main/resources/OH-INF/i18n/audio_ro.properties @@ -0,0 +1,6 @@ +system.config.audio.defaultSource.label = Sursa Implicită +system.config.audio.defaultSource.description = Sursa audio implicită pentru utilizare dacă nu este specificată o altă sursă. +system.config.audio.defaultSink.label = Sink implicit +system.config.audio.defaultSink.description = Rezerva audio implicită pentru utilizare în cazul în care nu este specificat niciun altul. + +service.system.audio.label = Audio diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_fr.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_fr.properties new file mode 100644 index 00000000000..d10b5ff8630 --- /dev/null +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_fr.properties @@ -0,0 +1,4 @@ +profile.system.script.toItemScript.label = Transformation Thing vers Item +profile.system.script.toItemScript.description = Le script pour transformer les mises à jour d'état et les commandes du Thing vers un Item. Le script peut retourner null pour ignorer les mises à jour/commandes et ne pas les transférer. +profile.system.script.toHandlerScript.label = Transformation Item vers Thing +profile.system.script.toHandlerScript.description = Le script pour transformer les mises à jour d'état et les commandes de l'Item vers un Thing. Le script peut retourner null pour ignorer les mises à jour/commandes et ne pas les transférer. diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_sl.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_sl.properties new file mode 100644 index 00000000000..673061ef5a2 --- /dev/null +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_sl.properties @@ -0,0 +1,4 @@ +profile.system.script.toItemScript.label = Pretvorba stvari v predmet +profile.system.script.toItemScript.description = Skript za pretvorbo stanja posodobitev in ukazov iz rokovalca s stvarmi v predmet. Skript lahko vrne nič, da zavrže posodobitve/ukaze in jih ne spusti skozi. +profile.system.script.toHandlerScript.label = Pretvorba predmeta v stvar +profile.system.script.toHandlerScript.description = Skript za pretvorbo ukazov predmeta v rokovalca s stvarmi. Skript lahko vrne nič, da zavrže ukaze in jih ne spusti skozi. diff --git a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_he.properties b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_he.properties index 82184b42815..4867b9cef56 100644 --- a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_he.properties +++ b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_he.properties @@ -66,6 +66,8 @@ module-type.core.GroupCommandTrigger.config.command.option.OPEN = פתוח module-type.core.GroupCommandTrigger.config.command.option.CLOSED = סגור module-type.core.GroupCommandTrigger.config.command.option.UP = למעלה module-type.core.GroupCommandTrigger.config.command.option.DOWN = למטה +module-type.core.GroupCommandTrigger.output.triggeringGroup.label = קבוצה מפעילה +module-type.core.GroupCommandTrigger.output.triggeringGroup.description = הקבוצה שאליה שייך הפריט module-type.core.GroupCommandTrigger.output.triggeringItem.label = פריט מפעיל module-type.core.GroupCommandTrigger.output.triggeringItem.description = חבר הקבוצה שקיבל את הפקודה module-type.core.GroupCommandTrigger.output.command.label = פקודה @@ -118,6 +120,8 @@ module-type.core.GroupStateUpdateTrigger.config.state.option.OPEN = פתוח module-type.core.GroupStateUpdateTrigger.config.state.option.CLOSED = סגור module-type.core.GroupStateUpdateTrigger.config.state.option.UP = למעלה module-type.core.GroupStateUpdateTrigger.config.state.option.DOWN = למטה +module-type.core.GroupStateUpdateTrigger.output.triggeringGroup.label = קבוצה מפעילה +module-type.core.GroupStateUpdateTrigger.output.triggeringGroup.description = הקבוצה שאליה שייך הפריט module-type.core.GroupStateUpdateTrigger.output.triggeringItem.label = פריט מפעיל module-type.core.GroupStateUpdateTrigger.output.triggeringItem.description = החבר בקבוצה שעדכן את מצבה module-type.core.GroupStateUpdateTrigger.output.state.label = מצב diff --git a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_pl.properties b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_pl.properties index 411dc199435..148f3b3099e 100644 --- a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_pl.properties +++ b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_pl.properties @@ -66,6 +66,8 @@ module-type.core.GroupCommandTrigger.config.command.option.OPEN = OTWÓRZ module-type.core.GroupCommandTrigger.config.command.option.CLOSED = ZAMKNIĘTE module-type.core.GroupCommandTrigger.config.command.option.UP = W GÓRĘ module-type.core.GroupCommandTrigger.config.command.option.DOWN = W DUŁ +module-type.core.GroupCommandTrigger.output.triggeringGroup.label = Grupa wyzwalająca +module-type.core.GroupCommandTrigger.output.triggeringGroup.description = grupa, do której należy element module-type.core.GroupCommandTrigger.output.triggeringItem.label = Wlement wyzwalający module-type.core.GroupCommandTrigger.output.triggeringItem.description = członek grupy, który otrzymał polecenie module-type.core.GroupCommandTrigger.output.command.label = Komenda @@ -118,6 +120,8 @@ module-type.core.GroupStateUpdateTrigger.config.state.option.OPEN = OTWÓRZ module-type.core.GroupStateUpdateTrigger.config.state.option.CLOSED = ZAMKNIĘTY module-type.core.GroupStateUpdateTrigger.config.state.option.UP = W GÓRĘ module-type.core.GroupStateUpdateTrigger.config.state.option.DOWN = W DUŁ +module-type.core.GroupStateUpdateTrigger.output.triggeringGroup.label = Grupa wyzwalająca +module-type.core.GroupStateUpdateTrigger.output.triggeringGroup.description = grupa, do której należy ten element module-type.core.GroupStateUpdateTrigger.output.triggeringItem.label = Element wyzwalający module-type.core.GroupStateUpdateTrigger.output.triggeringItem.description = członek grupy, który zaktualizował swój stan module-type.core.GroupStateUpdateTrigger.output.state.label = Stan diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_fi.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_fi.properties index cbfd0e36f99..fc0af12bce7 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_fi.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_fi.properties @@ -11,3 +11,7 @@ min_value_numeric_violated=Arvo ei saa olla pienempi kuin {0}. pattern_violated=Arvo {0} ei vastaa malli-lauseketta {1}. options_violated=Arvo {0} ei vastaa sallittuja parametrivaihtoehtoja. Sallitut vaihtoehdot ovat\: {1} multiple_limit_violated=Vain {0} elementtiä on sallittu, mutta {1} on annettu. + +bridge_not_configured=Silta on määritettävä. +type_description_missing=Tyypin kuvausta {0} tiedolle {1} ei löydy, vaikka olemassaolo tarkastettiin aikaisemmin. +config_description_missing=Asetuksen kuvausta {0} tiedolle {1} ei löydy, vaikka olemassaolo tarkastettiin aikaisemmin. diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_he.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_he.properties index 41f4b6ef97d..ac08733c15f 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_he.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_he.properties @@ -13,3 +13,5 @@ options_violated=הערך {0} אינו תואם לאפשרויות הפרמטר multiple_limit_violated=רק {0} אלמנטים מותרים אך הוזנו {1}. bridge_not_configured=הגדרת מגשר היא חובה. +type_description_missing=סוג תיאור {0} עבור {1} לא נמצא, למרות שבדקנו את הנוכחות בעבר. +config_description_missing=תיאור התצורה {0} עבור {1} לא נמצא, למרות שבדקנו את הנוכחות בעבר. diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_pl.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_pl.properties index 5bc41d2c0de..c5aef7d487b 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_pl.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_pl.properties @@ -13,3 +13,5 @@ options_violated=Wartość {0} nie pasuje do dozwolonych opcji parametrów. Dozw multiple_limit_violated=Tylko {0} elementy są dozwolone, ale {1} jest zapewniony. bridge_not_configured=Konfiguracja mostka jest obowiązkowa. +type_description_missing=Nie znaleziono opisu typu {0} dla {1} pomimo iż sprawdzaliśmy poprzednie wyniki. +config_description_missing=Nie znaleziono opisu typu {0} dla {1} pomimo iż sprawdzaliśmy poprzednie wyniki. diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_sl.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_sl.properties index c7d6d05b96f..0ba3b7b4159 100644 --- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_sl.properties +++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_sl.properties @@ -13,3 +13,5 @@ options_violated=Vrednost {0} se ne ujema z dovoljenimim izborom parametrov. Dov multiple_limit_violated=Dovoljenih je le {0} elementov, podanih pa je {1}. bridge_not_configured=Konfiguriranje mostu je obvezno. +type_description_missing=Opis vrste {0} za {1} ni bil najden, čeprav je obstoj že bil preverjen. +config_description_missing=Opis konfiguracije {0} za {1} ni bil najden, čeprav je obstoj že bil preverjen. diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_fr.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_fr.properties index b8497096562..37c3d8a91c6 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_fr.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_fr.properties @@ -1,4 +1,4 @@ system.config.rulehli.item.label = Item de commande vocale system.config.rulehli.item.description = L'item pour passer des commandes vocales. -service.system.rulehli.label = Interprêteur de règle vocale +service.system.rulehli.label = Interpréteur de règle vocale diff --git a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_pl.properties b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_pl.properties index 51fe4ff82ef..76996fa937a 100644 --- a/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_pl.properties +++ b/bundles/org.openhab.core.model.script/resources/OH-INF/i18n/hli_pl.properties @@ -1,4 +1,4 @@ system.config.rulehli.item.label = Element komendy głosowej -system.config.rulehli.item.description = Element do którego mają być przekazywane polecenia głosowe. +system.config.rulehli.item.description = Element do którego mają być przekazywane komendy głosowe. service.system.rulehli.label = Interpretator reguł głosu diff --git a/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/addons_ro.properties b/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/addons_ro.properties new file mode 100644 index 00000000000..df96365a81a --- /dev/null +++ b/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/addons_ro.properties @@ -0,0 +1,6 @@ +system.config.addons.includeIncompatible.label = Includeți suplimentele (potential) incompatibile +system.config.addons.includeIncompatible.description = Unele servicii suplimentare pot oferi suplimente în cazul în care nu se așteaptă compatibilitatea cu sistemul care rulează în prezent. Activarea acestei opțiuni va include aceste intrări în lista suplimentelor disponibile. +system.config.addons.remote.label = Accesare repozitoriu de la distanță +system.config.addons.remote.description = Definește dacă openHAB trebuie să acceseze depozitul de la distanță pentru instalarea suplimentelor. + +service.system.addons.label = Gestionare suplimente diff --git a/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/i18n_ro.properties b/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/i18n_ro.properties new file mode 100644 index 00000000000..54fcef20f3d --- /dev/null +++ b/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/i18n_ro.properties @@ -0,0 +1,18 @@ +system.config.i18n.language.label = Limbă +system.config.i18n.language.description = Limba implicită care trebuie utilizată. Dacă nu este specificată, se utilizează setarea implicită a sistemului. +system.config.i18n.script.label = Script +system.config.i18n.script.description = Scriptul care ar trebui utilizat. +system.config.i18n.region.label = Țara / Regiunea +system.config.i18n.region.description = Regiunea care ar trebui utilizată. +system.config.i18n.variant.label = Variantă +system.config.i18n.variant.description = O variație a Localului. Orice valoare arbitrară folosită pentru a indica o variație a unui local. +system.config.i18n.timezone.label = Fus Orar +system.config.i18n.timezone.description = Fusul orar poate fi setat din interfața utilizatorului. Fusul orar de bază este cel implicit. +system.config.i18n.location.label = Locatie +system.config.i18n.location.description = Locația acestei instalări.
Coordonează ca <latitude>,<longitude>[<altitude>]
Exemplu\: "52.5200066,13.4049540" (Berlin) +system.config.i18n.measurementSystem.label = Sistem de măsurare +system.config.i18n.measurementSystem.description = Sistemul de măsurare este utilizat pentru conversia unităților. +system.config.i18n.measurementSystem.option.SI = Metric +system.config.i18n.measurementSystem.option.US = Imperial (SUA) + +service.system.i18n.label = Setări regionale diff --git a/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/network_ro.properties b/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/network_ro.properties new file mode 100644 index 00000000000..4eff329d268 --- /dev/null +++ b/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/network_ro.properties @@ -0,0 +1,10 @@ +system.config.network.primaryAddress.label = Adresa Principala +system.config.network.primaryAddress.description = Un subnet (e.g. 192.168.1.0/24). +system.config.network.broadcastAddress.label = Adresă difuzare +system.config.network.broadcastAddress.description = O adresă de difuzare (ex. 192.168.1.255). +system.config.network.useOnlyOneAddress.label = Adresă IP unică per interfață +system.config.network.useOnlyOneAddress.description = Utilizați o singură adresă IP pentru fiecare interfață și familie. +system.config.network.useIPv6.label = Utilizare IPv6 +system.config.network.useIPv6.description = Utilizaţi adrese IPv6 dacă sunt disponibile. + +service.system.network.label = Setări rețea From f36ccea50c492ca27d964dc69e411d25e771245d Mon Sep 17 00:00:00 2001 From: J-N-K Date: Thu, 8 Jun 2023 13:26:43 +0200 Subject: [PATCH 101/126] Bump jose4j (#3645) Signed-off-by: Jan N. Klug --- bom/compile/pom.xml | 2 +- bom/runtime/pom.xml | 2 +- features/karaf/openhab-tp/src/main/feature/feature.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bom/compile/pom.xml b/bom/compile/pom.xml index 1823005acc8..2cd40425121 100644 --- a/bom/compile/pom.xml +++ b/bom/compile/pom.xml @@ -413,7 +413,7 @@ org.bitbucket.b_c jose4j - 0.7.7 + 0.9.3 compile diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index a127bb0abea..8862acbdead 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -973,7 +973,7 @@ org.bitbucket.b_c jose4j - 0.7.7 + 0.9.3 compile diff --git a/features/karaf/openhab-tp/src/main/feature/feature.xml b/features/karaf/openhab-tp/src/main/feature/feature.xml index 96bfdbc5a43..ea76ff655e0 100644 --- a/features/karaf/openhab-tp/src/main/feature/feature.xml +++ b/features/karaf/openhab-tp/src/main/feature/feature.xml @@ -202,9 +202,9 @@
- openhab.tp;feature=jose4j;version=0.7.7 + openhab.tp;feature=jose4j;version=0.9.3 mvn:org.apache.servicemix.specs/org.apache.servicemix.specs.activation-api-1.2.1/1.2.1_3 - mvn:org.bitbucket.b_c/jose4j/0.7.7 + mvn:org.bitbucket.b_c/jose4j/0.9.3 From be7488958d06962b26239ca1ab1ae0f9a7ccda1d Mon Sep 17 00:00:00 2001 From: Florian Hotze Date: Thu, 8 Jun 2023 14:31:34 +0200 Subject: [PATCH 102/126] [sse] Add type information to state event (#3647) * SSE Item state stream: Provide state type Signed-off-by: Florian Hotze * Add comment for method source Signed-off-by: Florian Hotze --------- Signed-off-by: Florian Hotze --- .../io/rest/sse/internal/SseItemStatesEventBuilder.java | 7 +++++++ .../openhab/core/io/rest/sse/internal/dto/StateDTO.java | 2 ++ 2 files changed, 9 insertions(+) diff --git a/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseItemStatesEventBuilder.java b/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseItemStatesEventBuilder.java index 28d7b1fc091..8bb59852435 100644 --- a/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseItemStatesEventBuilder.java +++ b/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseItemStatesEventBuilder.java @@ -78,6 +78,7 @@ public SseItemStatesEventBuilder(final BundleContext bundleContext, final @Refer Item item = itemRegistry.getItem(itemName); StateDTO stateDto = new StateDTO(); stateDto.state = item.getState().toString(); + stateDto.type = getStateType(item.getState()); String displayState = getDisplayState(item, localeService.getLocale(null)); // Only include the display state if it's different than the raw state if (stateDto.state != null && !stateDto.state.equals(displayState)) { @@ -168,4 +169,10 @@ public SseItemStatesEventBuilder(final BundleContext bundleContext, final @Refer return displayState; } + + // Taken from org.openhab.core.items.events.ItemEventFactory + private static String getStateType(State state) { + String stateClassName = state.getClass().getSimpleName(); + return stateClassName.substring(0, stateClassName.length() - "Type".length()); + } } diff --git a/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/dto/StateDTO.java b/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/dto/StateDTO.java index 566a1b64832..0c908369f32 100644 --- a/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/dto/StateDTO.java +++ b/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/dto/StateDTO.java @@ -21,6 +21,8 @@ public class StateDTO { public String state; public String displayState; + public String type; + public StateDTO() { } } From e3396c9477d9282cd29496331884e07b8c41c17d Mon Sep 17 00:00:00 2001 From: GiviMAD Date: Sat, 10 Jun 2023 15:35:23 +0200 Subject: [PATCH 103/126] [websocket] Allow registering websocket adapters (#3622) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [WebSocket] Allow register websocket handlers Signed-off-by: Miguel Álvarez --- .../AnonymousUserSecurityContext.java | 2 +- .../rest/auth/{internal => }/AuthFilter.java | 110 +++++++----- .../ExpiringUserSecurityContextCache.java | 8 +- .../auth/{internal => }/AuthFilterTest.java | 7 +- bundles/org.openhab.core.io.websocket/pom.xml | 5 + .../io/websocket/CommonWebSocketServlet.java | 136 +++++++++++++++ .../core/io/websocket/EventWebSocket.java | 10 +- .../io/websocket/EventWebSocketAdapter.java | 80 +++++++++ .../io/websocket/EventWebSocketServlet.java | 160 ------------------ .../core/io/websocket/WebSocketAdapter.java | 44 +++++ .../websocket/CommonWebSocketServletTest.java | 99 +++++++++++ .../core/io/websocket/EventWebSocketTest.java | 2 +- .../openhab-core/src/main/feature/feature.xml | 2 + 13 files changed, 449 insertions(+), 216 deletions(-) rename bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/{internal => }/AnonymousUserSecurityContext.java (96%) rename bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/{internal => }/AuthFilter.java (78%) rename bundles/org.openhab.core.io.rest.auth/src/test/java/org/openhab/core/io/rest/auth/{internal => }/AuthFilterTest.java (93%) create mode 100644 bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/CommonWebSocketServlet.java create mode 100644 bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/EventWebSocketAdapter.java delete mode 100644 bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/EventWebSocketServlet.java create mode 100644 bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/WebSocketAdapter.java create mode 100644 bundles/org.openhab.core.io.websocket/src/test/java/org/openhab/core/io/websocket/CommonWebSocketServletTest.java diff --git a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AnonymousUserSecurityContext.java b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/AnonymousUserSecurityContext.java similarity index 96% rename from bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AnonymousUserSecurityContext.java rename to bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/AnonymousUserSecurityContext.java index aa3bbae99e4..ec4a0fa4427 100644 --- a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AnonymousUserSecurityContext.java +++ b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/AnonymousUserSecurityContext.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.io.rest.auth.internal; +package org.openhab.core.io.rest.auth; import java.security.Principal; diff --git a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthFilter.java b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/AuthFilter.java similarity index 78% rename from bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthFilter.java rename to bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/AuthFilter.java index 7faa3b41051..38bbfbca7dc 100644 --- a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthFilter.java +++ b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/AuthFilter.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.io.rest.auth.internal; +package org.openhab.core.io.rest.auth; import java.io.IOException; import java.net.InetAddress; @@ -53,6 +53,7 @@ import org.openhab.core.config.core.ConfigurableService; import org.openhab.core.io.rest.JSONResponse; import org.openhab.core.io.rest.RESTConstants; +import org.openhab.core.io.rest.auth.internal.*; import org.osgi.framework.Constants; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -77,7 +78,8 @@ * @author Miguel Álvarez - Add trusted networks for implicit user role */ @PreMatching -@Component(configurationPid = "org.openhab.restauth", property = Constants.SERVICE_PID + "=org.openhab.restauth") +@Component(configurationPid = "org.openhab.restauth", property = Constants.SERVICE_PID + + "=org.openhab.restauth", service = AuthFilter.class) @ConfigurableService(category = "system", label = "API Security", description_uri = AuthFilter.CONFIG_URI) @JaxrsExtension @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") @@ -232,56 +234,80 @@ private SecurityContext authenticateBasicAuth(String credentialString) throws Au public void filter(@Nullable ContainerRequestContext requestContext) throws IOException { if (requestContext != null) { try { - String altTokenHeader = requestContext.getHeaderString(ALT_AUTH_HEADER); - if (altTokenHeader != null) { - requestContext.setSecurityContext(authenticateBearerToken(altTokenHeader)); - return; + SecurityContext sc = getSecurityContext(servletRequest, false); + if (sc != null) { + requestContext.setSecurityContext(sc); } + } catch (AuthenticationException e) { + logger.warn("Unauthorized API request from {}: {}", getClientIp(servletRequest), e.getMessage()); + requestContext.abortWith(JSONResponse.createErrorResponse(Status.UNAUTHORIZED, "Invalid credentials")); + } + } + } - String authHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); - if (authHeader != null) { - String[] authParts = authHeader.split(" "); - if (authParts.length == 2) { - String authType = authParts[0]; - String authValue = authParts[1]; - if ("Bearer".equalsIgnoreCase(authType)) { - requestContext.setSecurityContext(authenticateBearerToken(authValue)); - return; - } else if ("Basic".equalsIgnoreCase(authType)) { - String[] decodedCredentials = new String(Base64.getDecoder().decode(authValue), "UTF-8") - .split(":"); - if (decodedCredentials.length > 2) { - throw new AuthenticationException("Invalid Basic authentication credential format"); - } - switch (decodedCredentials.length) { - case 1: - requestContext.setSecurityContext(authenticateBearerToken(decodedCredentials[0])); - break; - case 2: - if (!allowBasicAuth) { - throw new AuthenticationException( - "Basic authentication with username/password is not allowed"); - } - requestContext.setSecurityContext(authenticateBasicAuth(authValue)); - } - } + public @Nullable SecurityContext getSecurityContext(HttpServletRequest request, boolean allowQueryToken) + throws AuthenticationException, IOException { + String altTokenHeader = request.getHeader(ALT_AUTH_HEADER); + if (altTokenHeader != null) { + return authenticateBearerToken(altTokenHeader); + } + String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION); + String authType = null; + String authValue = null; + boolean authFromQuery = false; + if (authHeader != null) { + String[] authParts = authHeader.split(" "); + if (authParts.length == 2) { + authType = authParts[0]; + authValue = authParts[1]; + } + } else if (allowQueryToken) { + Map parameterMap = request.getParameterMap(); + String[] accessToken = parameterMap.get("accessToken"); + if (accessToken != null && accessToken.length > 0) { + authValue = accessToken[0]; + authFromQuery = true; + } + } + if (authValue != null) { + if (authFromQuery) { + try { + return authenticateBearerToken(authValue); + } catch (AuthenticationException e) { + if (allowBasicAuth) { + return authenticateBasicAuth(authValue); } - } else if (isImplicitUserRole(requestContext)) { - requestContext.setSecurityContext(new AnonymousUserSecurityContext()); } - } catch (AuthenticationException e) { - logger.warn("Unauthorized API request from {}: {}", getClientIp(requestContext), e.getMessage()); - requestContext.abortWith(JSONResponse.createErrorResponse(Status.UNAUTHORIZED, "Invalid credentials")); + } else if ("Bearer".equalsIgnoreCase(authType)) { + return authenticateBearerToken(authValue); + } else if ("Basic".equalsIgnoreCase(authType)) { + String[] decodedCredentials = new String(Base64.getDecoder().decode(authValue), "UTF-8").split(":"); + if (decodedCredentials.length > 2) { + throw new AuthenticationException("Invalid Basic authentication credential format"); + } + switch (decodedCredentials.length) { + case 1: + return authenticateBearerToken(decodedCredentials[0]); + case 2: + if (!allowBasicAuth) { + throw new AuthenticationException( + "Basic authentication with username/password is not allowed"); + } + return authenticateBasicAuth(authValue); + } } + } else if (isImplicitUserRole(servletRequest)) { + return new AnonymousUserSecurityContext(); } + return null; } - private boolean isImplicitUserRole(ContainerRequestContext requestContext) { + private boolean isImplicitUserRole(HttpServletRequest request) { if (implicitUserRole) { return true; } try { - byte[] clientAddress = InetAddress.getByName(getClientIp(requestContext)).getAddress(); + byte[] clientAddress = InetAddress.getByName(getClientIp(request)).getAddress(); return trustedNetworks.stream().anyMatch(networkCIDR -> networkCIDR.isInRange(clientAddress)); } catch (IOException e) { logger.debug("Error validating trusted networks: {}", e.getMessage()); @@ -303,8 +329,8 @@ private List parseTrustedNetworks(String value) { return cidrList; } - private String getClientIp(ContainerRequestContext requestContext) throws UnknownHostException { - String ipForwarded = Objects.requireNonNullElse(requestContext.getHeaderString("x-forwarded-for"), ""); + private String getClientIp(HttpServletRequest request) throws UnknownHostException { + String ipForwarded = Objects.requireNonNullElse(request.getHeader("x-forwarded-for"), ""); String clientIp = ipForwarded.split(",")[0]; return clientIp.isBlank() ? servletRequest.getRemoteAddr() : clientIp; } diff --git a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/ExpiringUserSecurityContextCache.java b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/ExpiringUserSecurityContextCache.java index a5dab8e6499..5112d10e682 100644 --- a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/ExpiringUserSecurityContextCache.java +++ b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/ExpiringUserSecurityContextCache.java @@ -37,7 +37,7 @@ public class ExpiringUserSecurityContextCache { private int calls = 0; - ExpiringUserSecurityContextCache(long expirationTime) { + public ExpiringUserSecurityContextCache(long expirationTime) { this.keepPeriod = expirationTime; entryMap = new LinkedHashMap<>() { private static final long serialVersionUID = -1220310861591070462L; @@ -48,7 +48,7 @@ protected boolean removeEldestEntry(Map.@Nullable Entry eldest) { }; } - synchronized @Nullable UserSecurityContext get(String key) { + public synchronized @Nullable UserSecurityContext get(String key) { calls++; if (calls >= CLEANUP_FREQUENCY) { new HashSet<>(entryMap.keySet()).forEach(k -> getEntry(k)); @@ -61,11 +61,11 @@ protected boolean removeEldestEntry(Map.@Nullable Entry eldest) { return null; } - synchronized void put(String key, UserSecurityContext value) { + public synchronized void put(String key, UserSecurityContext value) { entryMap.put(key, new Entry(System.currentTimeMillis(), value)); } - synchronized void clear() { + public synchronized void clear() { entryMap.clear(); } diff --git a/bundles/org.openhab.core.io.rest.auth/src/test/java/org/openhab/core/io/rest/auth/internal/AuthFilterTest.java b/bundles/org.openhab.core.io.rest.auth/src/test/java/org/openhab/core/io/rest/auth/AuthFilterTest.java similarity index 93% rename from bundles/org.openhab.core.io.rest.auth/src/test/java/org/openhab/core/io/rest/auth/internal/AuthFilterTest.java rename to bundles/org.openhab.core.io.rest.auth/src/test/java/org/openhab/core/io/rest/auth/AuthFilterTest.java index a4d98fefa2d..2f5bca9c806 100644 --- a/bundles/org.openhab.core.io.rest.auth/src/test/java/org/openhab/core/io/rest/auth/internal/AuthFilterTest.java +++ b/bundles/org.openhab.core.io.rest.auth/src/test/java/org/openhab/core/io/rest/auth/AuthFilterTest.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.io.rest.auth.internal; +package org.openhab.core.io.rest.auth; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -32,6 +32,7 @@ import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.openhab.core.auth.UserRegistry; +import org.openhab.core.io.rest.auth.internal.JwtHelper; /** * The {@link AuthFilterTest} is a @@ -79,7 +80,7 @@ public void noImplicitUserRoleDeniesAccess() throws IOException { public void trustedNetworkAllowsAccessIfForwardedHeaderMatches() throws IOException { authFilter.activate(Map.of(AuthFilter.CONFIG_IMPLICIT_USER_ROLE, false, AuthFilter.CONFIG_TRUSTED_NETWORKS, "192.168.1.0/24")); - when(containerRequestContext.getHeaderString("x-forwarded-for")).thenReturn("192.168.1.100"); + when(servletRequest.getHeader("x-forwarded-for")).thenReturn("192.168.1.100"); authFilter.filter(containerRequestContext); verify(containerRequestContext).setSecurityContext(any()); @@ -89,7 +90,7 @@ public void trustedNetworkAllowsAccessIfForwardedHeaderMatches() throws IOExcept public void trustedNetworkDeniesAccessIfForwardedHeaderDoesNotMatch() throws IOException { authFilter.activate(Map.of(AuthFilter.CONFIG_IMPLICIT_USER_ROLE, false, AuthFilter.CONFIG_TRUSTED_NETWORKS, "192.168.1.0/24")); - when(containerRequestContext.getHeaderString("x-forwarded-for")).thenReturn("192.168.2.100"); + when(servletRequest.getHeader("x-forwarded-for")).thenReturn("192.168.2.100"); authFilter.filter(containerRequestContext); verify(containerRequestContext, never()).setSecurityContext(any()); diff --git a/bundles/org.openhab.core.io.websocket/pom.xml b/bundles/org.openhab.core.io.websocket/pom.xml index 4104b939b21..3d4b017b3b3 100644 --- a/bundles/org.openhab.core.io.websocket/pom.xml +++ b/bundles/org.openhab.core.io.websocket/pom.xml @@ -20,6 +20,11 @@ org.openhab.core ${project.version} + + org.openhab.core.bundles + org.openhab.core.io.rest.auth + ${project.version} + diff --git a/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/CommonWebSocketServlet.java b/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/CommonWebSocketServlet.java new file mode 100644 index 00000000000..7937ef0e39b --- /dev/null +++ b/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/CommonWebSocketServlet.java @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.websocket; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.Servlet; +import javax.servlet.ServletException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.websocket.server.WebSocketServerFactory; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; +import org.eclipse.jetty.websocket.servlet.WebSocketCreator; +import org.eclipse.jetty.websocket.servlet.WebSocketServlet; +import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; +import org.openhab.core.auth.AuthenticationException; +import org.openhab.core.auth.Role; +import org.openhab.core.io.rest.auth.AuthFilter; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.http.NamespaceException; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletName; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletPattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link CommonWebSocketServlet} provides the servlet for WebSocket connections + * + * @author Jan N. Klug - Initial contribution + * @author Miguel Álvarez Díez - Refactor into a common servlet + */ +@NonNullByDefault +@HttpWhiteboardServletName(CommonWebSocketServlet.SERVLET_PATH) +@HttpWhiteboardServletPattern(CommonWebSocketServlet.SERVLET_PATH + "/*") +@Component(immediate = true, service = { Servlet.class }) +public class CommonWebSocketServlet extends WebSocketServlet { + private static final long serialVersionUID = 1L; + + public static final String SERVLET_PATH = "/ws"; + + public static final String DEFAULT_ADAPTER_ID = EventWebSocketAdapter.ADAPTER_ID; + + private final Map connectionHandlers = new HashMap<>(); + private final AuthFilter authFilter; + + @SuppressWarnings("unused") + private @Nullable WebSocketServerFactory importNeeded; + + @Activate + public CommonWebSocketServlet(@Reference AuthFilter authFilter) throws ServletException, NamespaceException { + this.authFilter = authFilter; + } + + @Override + public void configure(@NonNullByDefault({}) WebSocketServletFactory webSocketServletFactory) { + webSocketServletFactory.getPolicy().setIdleTimeout(10000); + webSocketServletFactory.setCreator(new CommonWebSocketCreator()); + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + protected void addWebSocketAdapter(WebSocketAdapter wsAdapter) { + this.connectionHandlers.put(wsAdapter.getId(), wsAdapter); + } + + protected void removeWebSocketAdapter(WebSocketAdapter wsAdapter) { + this.connectionHandlers.remove(wsAdapter.getId()); + } + + private class CommonWebSocketCreator implements WebSocketCreator { + private final Logger logger = LoggerFactory.getLogger(CommonWebSocketCreator.class); + + @Override + public @Nullable Object createWebSocket(@Nullable ServletUpgradeRequest servletUpgradeRequest, + @Nullable ServletUpgradeResponse servletUpgradeResponse) { + if (servletUpgradeRequest == null || servletUpgradeResponse == null) { + return null; + } + if (isAuthorizedRequest(servletUpgradeRequest)) { + String requestPath = servletUpgradeRequest.getRequestURI().getPath(); + String pathPrefix = SERVLET_PATH + "/"; + boolean useDefaultAdapter = requestPath.equals(pathPrefix) || !requestPath.startsWith(pathPrefix); + WebSocketAdapter wsAdapter; + if (!useDefaultAdapter) { + String adapterId = requestPath.substring(pathPrefix.length()); + wsAdapter = connectionHandlers.get(adapterId); + if (wsAdapter == null) { + logger.warn("Missing WebSocket adapter for path {}", adapterId); + return null; + } + } else { + wsAdapter = connectionHandlers.get(DEFAULT_ADAPTER_ID); + if (wsAdapter == null) { + logger.warn("Default WebSocket adapter is missing"); + return null; + } + } + logger.debug("New connection handled by {}", wsAdapter.getId()); + return wsAdapter.createWebSocket(servletUpgradeRequest, servletUpgradeResponse); + } else { + logger.warn("Unauthenticated request to create a websocket from {}.", + servletUpgradeRequest.getRemoteAddress()); + } + return null; + } + + private boolean isAuthorizedRequest(ServletUpgradeRequest servletUpgradeRequest) { + try { + var securityContext = authFilter.getSecurityContext(servletUpgradeRequest.getHttpServletRequest(), + true); + return securityContext != null + && (securityContext.isUserInRole(Role.USER) || securityContext.isUserInRole(Role.ADMIN)); + } catch (AuthenticationException | IOException e) { + logger.warn("Error handling WebSocket authorization", e); + return false; + } + } + } +} diff --git a/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/EventWebSocket.java b/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/EventWebSocket.java index 78d833c285b..f37bb4cc8e2 100644 --- a/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/EventWebSocket.java +++ b/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/EventWebSocket.java @@ -52,7 +52,7 @@ public class EventWebSocket { private final Logger logger = LoggerFactory.getLogger(EventWebSocket.class); - private final EventWebSocketServlet servlet; + private final EventWebSocketAdapter wsAdapter; private final Gson gson; private final EventPublisher eventPublisher; private final ItemEventUtility itemEventUtility; @@ -64,9 +64,9 @@ public class EventWebSocket { private List typeFilter = List.of(); private List sourceFilter = List.of(); - public EventWebSocket(Gson gson, EventWebSocketServlet servlet, ItemEventUtility itemEventUtility, + public EventWebSocket(Gson gson, EventWebSocketAdapter wsAdapter, ItemEventUtility itemEventUtility, EventPublisher eventPublisher) { - this.servlet = servlet; + this.wsAdapter = wsAdapter; this.gson = gson; this.itemEventUtility = itemEventUtility; this.eventPublisher = eventPublisher; @@ -74,7 +74,7 @@ public EventWebSocket(Gson gson, EventWebSocketServlet servlet, ItemEventUtility @OnWebSocketClose public void onClose(int statusCode, String reason) { - this.servlet.unregisterListener(this); + this.wsAdapter.unregisterListener(this); remoteIdentifier = ""; this.session = null; this.remoteEndpoint = null; @@ -86,7 +86,7 @@ public void onConnect(Session session) { RemoteEndpoint remoteEndpoint = session.getRemote(); this.remoteEndpoint = remoteEndpoint; this.remoteIdentifier = remoteEndpoint.getInetSocketAddress().toString(); - this.servlet.registerListener(this); + this.wsAdapter.registerListener(this); } @OnWebSocketMessage diff --git a/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/EventWebSocketAdapter.java b/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/EventWebSocketAdapter.java new file mode 100644 index 00000000000..6ef61b804cb --- /dev/null +++ b/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/EventWebSocketAdapter.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.websocket; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; +import org.openhab.core.events.Event; +import org.openhab.core.events.EventPublisher; +import org.openhab.core.events.EventSubscriber; +import org.openhab.core.items.ItemRegistry; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import com.google.gson.Gson; + +/** + * The {@link EventWebSocketAdapter} allows subscription to oh events over WebSocket + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +@Component(immediate = true, service = { EventSubscriber.class, WebSocketAdapter.class }) +public class EventWebSocketAdapter implements EventSubscriber, WebSocketAdapter { + public static final String ADAPTER_ID = "event-subscriber"; + private final Gson gson = new Gson(); + private final EventPublisher eventPublisher; + + private final ItemEventUtility itemEventUtility; + private final Set webSockets = new CopyOnWriteArraySet<>(); + + @Activate + public EventWebSocketAdapter(@Reference EventPublisher eventPublisher, @Reference ItemRegistry itemRegistry) { + this.eventPublisher = eventPublisher; + itemEventUtility = new ItemEventUtility(gson, itemRegistry); + } + + @Override + public Set getSubscribedEventTypes() { + return Set.of(EventSubscriber.ALL_EVENT_TYPES); + } + + @Override + public void receive(Event event) { + webSockets.forEach(ws -> ws.processEvent(event)); + } + + public void registerListener(EventWebSocket eventWebSocket) { + webSockets.add(eventWebSocket); + } + + public void unregisterListener(EventWebSocket eventWebSocket) { + webSockets.remove(eventWebSocket); + } + + @Override + public String getId() { + return ADAPTER_ID; + } + + @Override + public Object createWebSocket(ServletUpgradeRequest servletUpgradeRequest, + ServletUpgradeResponse servletUpgradeResponse) { + return new EventWebSocket(gson, EventWebSocketAdapter.this, itemEventUtility, eventPublisher); + } +} diff --git a/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/EventWebSocketServlet.java b/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/EventWebSocketServlet.java deleted file mode 100644 index 1eb176860e3..00000000000 --- a/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/EventWebSocketServlet.java +++ /dev/null @@ -1,160 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.io.websocket; - -import java.util.Base64; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; - -import javax.servlet.Servlet; -import javax.servlet.ServletException; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.websocket.server.WebSocketServerFactory; -import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; -import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; -import org.eclipse.jetty.websocket.servlet.WebSocketCreator; -import org.eclipse.jetty.websocket.servlet.WebSocketServlet; -import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; -import org.openhab.core.auth.Authentication; -import org.openhab.core.auth.AuthenticationException; -import org.openhab.core.auth.Credentials; -import org.openhab.core.auth.Role; -import org.openhab.core.auth.User; -import org.openhab.core.auth.UserApiTokenCredentials; -import org.openhab.core.auth.UserRegistry; -import org.openhab.core.auth.UsernamePasswordCredentials; -import org.openhab.core.events.Event; -import org.openhab.core.events.EventPublisher; -import org.openhab.core.events.EventSubscriber; -import org.openhab.core.items.ItemRegistry; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.http.NamespaceException; -import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletName; -import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletPattern; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.Gson; - -/** - * The {@link EventWebSocketServlet} provides the servlet for WebSocket connections - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -@HttpWhiteboardServletName(EventWebSocketServlet.SERVLET_PATH) -@HttpWhiteboardServletPattern(EventWebSocketServlet.SERVLET_PATH + "/*") -@Component(immediate = true, service = { EventSubscriber.class, Servlet.class }) -public class EventWebSocketServlet extends WebSocketServlet implements EventSubscriber { - private static final long serialVersionUID = 1L; - - public static final String SERVLET_PATH = "/ws"; - private final Gson gson = new Gson(); - private final UserRegistry userRegistry; - private final EventPublisher eventPublisher; - - private final ItemEventUtility itemEventUtility; - private final Set webSockets = new CopyOnWriteArraySet<>(); - - @SuppressWarnings("unused") - private @Nullable WebSocketServerFactory importNeeded; - - @Activate - public EventWebSocketServlet(@Reference UserRegistry userRegistry, @Reference EventPublisher eventPublisher, - @Reference ItemRegistry itemRegistry) throws ServletException, NamespaceException { - this.userRegistry = userRegistry; - this.eventPublisher = eventPublisher; - - itemEventUtility = new ItemEventUtility(gson, itemRegistry); - } - - @Override - public void configure(@NonNullByDefault({}) WebSocketServletFactory webSocketServletFactory) { - webSocketServletFactory.getPolicy().setIdleTimeout(10000); - webSocketServletFactory.setCreator(new EventWebSocketCreator()); - } - - @Override - public Set getSubscribedEventTypes() { - return Set.of(EventSubscriber.ALL_EVENT_TYPES); - } - - @Override - public void receive(Event event) { - webSockets.forEach(ws -> ws.processEvent(event)); - } - - public void registerListener(EventWebSocket eventWebSocket) { - webSockets.add(eventWebSocket); - } - - public void unregisterListener(EventWebSocket eventWebSocket) { - webSockets.remove(eventWebSocket); - } - - private class EventWebSocketCreator implements WebSocketCreator { - private static final String API_TOKEN_PREFIX = "oh."; - - private final Logger logger = LoggerFactory.getLogger(EventWebSocketCreator.class); - - @Override - public @Nullable Object createWebSocket(@Nullable ServletUpgradeRequest servletUpgradeRequest, - @Nullable ServletUpgradeResponse servletUpgradeResponse) { - if (servletUpgradeRequest == null) { - return null; - } - - Map> parameterMap = servletUpgradeRequest.getParameterMap(); - List accessToken = parameterMap.getOrDefault("accessToken", List.of()); - if (accessToken.size() == 1 && authenticateAccessToken(accessToken.get(0))) { - return new EventWebSocket(gson, EventWebSocketServlet.this, itemEventUtility, eventPublisher); - } else { - logger.warn("Unauthenticated request to create a websocket from {}.", - servletUpgradeRequest.getRemoteAddress()); - } - - return null; - } - - private boolean authenticateAccessToken(String token) { - Credentials credentials = null; - if (token.startsWith(API_TOKEN_PREFIX)) { - credentials = new UserApiTokenCredentials(token); - } else { - // try BasicAuthentication - String[] decodedParts = new String(Base64.getDecoder().decode(token)).split(":"); - if (decodedParts.length == 2) { - credentials = new UsernamePasswordCredentials(decodedParts[0], decodedParts[1]); - } - } - - if (credentials != null) { - try { - Authentication auth = userRegistry.authenticate(credentials); - User user = userRegistry.get(auth.getUsername()); - return user != null - && (user.getRoles().contains(Role.USER) || user.getRoles().contains(Role.ADMIN)); - } catch (AuthenticationException ignored) { - } - } - - return false; - } - } -} diff --git a/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/WebSocketAdapter.java b/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/WebSocketAdapter.java new file mode 100644 index 00000000000..5007e1faf10 --- /dev/null +++ b/bundles/org.openhab.core.io.websocket/src/main/java/org/openhab/core/io/websocket/WebSocketAdapter.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.websocket; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; + +/** + * The {@link WebSocketAdapter} can be implemented to register an adapter for a websocket connection. + * It will be accessible on the path /ws/ADAPTER_ID of your server. + * Security is handled by the {@link CommonWebSocketServlet}. + * + * @author Miguel Álvarez Díez - Initial contribution + */ +@NonNullByDefault +public interface WebSocketAdapter { + /** + * The adapter id. + * In combination with the base path {@link CommonWebSocketServlet#SERVLET_PATH} defines the adapter path. + * + * @return the adapter id. + */ + String getId(); + + /** + * Creates a websocket instance. + * It should use the {@link org.eclipse.jetty.websocket.api.annotations} or implement + * {@link org.eclipse.jetty.websocket.api.WebSocketListener}. + * + * @return a websocket instance. + */ + Object createWebSocket(ServletUpgradeRequest servletUpgradeRequest, ServletUpgradeResponse servletUpgradeResponse); +} diff --git a/bundles/org.openhab.core.io.websocket/src/test/java/org/openhab/core/io/websocket/CommonWebSocketServletTest.java b/bundles/org.openhab.core.io.websocket/src/test/java/org/openhab/core/io/websocket/CommonWebSocketServletTest.java new file mode 100644 index 00000000000..17a1cd85fb0 --- /dev/null +++ b/bundles/org.openhab.core.io.websocket/src/test/java/org/openhab/core/io/websocket/CommonWebSocketServletTest.java @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.websocket; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.List; + +import javax.servlet.ServletException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; +import org.eclipse.jetty.websocket.servlet.WebSocketCreator; +import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.openhab.core.auth.AuthenticationException; +import org.openhab.core.io.rest.auth.AnonymousUserSecurityContext; +import org.openhab.core.io.rest.auth.AuthFilter; +import org.osgi.service.http.NamespaceException; + +/** + * The {@link CommonWebSocketServletTest} contains tests for the {@link EventWebSocket} + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class CommonWebSocketServletTest { + private final String testAdapterId = "test-adapter-id"; + + private @NonNullByDefault({}) CommonWebSocketServlet servlet; + private @Mock @NonNullByDefault({}) AuthFilter authFilter; + private @Mock @NonNullByDefault({}) WebSocketServletFactory factory; + private @Mock @NonNullByDefault({}) WebSocketAdapter testDefaultWsAdapter; + private @Mock @NonNullByDefault({}) WebSocketAdapter testWsAdapter; + + private @Mock @NonNullByDefault({}) WebSocketPolicy wsPolicy; + private @Mock @NonNullByDefault({}) ServletUpgradeRequest request; + private @Mock @NonNullByDefault({}) ServletUpgradeResponse response; + private @Captor @NonNullByDefault({}) ArgumentCaptor webSocketCreatorAC; + + @BeforeEach + public void setup() throws ServletException, NamespaceException, AuthenticationException, IOException { + servlet = new CommonWebSocketServlet(authFilter); + when(factory.getPolicy()).thenReturn(wsPolicy); + servlet.configure(factory); + verify(factory).setCreator(webSocketCreatorAC.capture()); + var params = new HashMap>(); + when(request.getParameterMap()).thenReturn(params); + when(authFilter.getSecurityContext(any(), anyBoolean())).thenReturn(new AnonymousUserSecurityContext()); + when(testDefaultWsAdapter.getId()).thenReturn(CommonWebSocketServlet.DEFAULT_ADAPTER_ID); + when(testWsAdapter.getId()).thenReturn(testAdapterId); + servlet.addWebSocketAdapter(testDefaultWsAdapter); + servlet.addWebSocketAdapter(testWsAdapter); + } + + @Test + public void createWebsocketUsingDefaultAdapterPath() throws URISyntaxException { + when(request.getRequestURI()).thenReturn(new URI("http://127.0.0.1:8080/ws")); + webSocketCreatorAC.getValue().createWebSocket(request, response); + verify(testDefaultWsAdapter, times(1)).createWebSocket(request, response); + } + + @Test + public void createWebsocketUsingAdapterPath() throws URISyntaxException { + when(request.getRequestURI()).thenReturn(new URI("http://127.0.0.1:8080/ws/"+ testAdapterId)); + webSocketCreatorAC.getValue().createWebSocket(request, response); + verify(testWsAdapter, times(1)).createWebSocket(request, response); + } +} diff --git a/bundles/org.openhab.core.io.websocket/src/test/java/org/openhab/core/io/websocket/EventWebSocketTest.java b/bundles/org.openhab.core.io.websocket/src/test/java/org/openhab/core/io/websocket/EventWebSocketTest.java index 9c525b96788..49eb37596de 100644 --- a/bundles/org.openhab.core.io.websocket/src/test/java/org/openhab/core/io/websocket/EventWebSocketTest.java +++ b/bundles/org.openhab.core.io.websocket/src/test/java/org/openhab/core/io/websocket/EventWebSocketTest.java @@ -63,7 +63,7 @@ public class EventWebSocketTest { private Gson gson = new Gson(); - private @Mock @NonNullByDefault({}) EventWebSocketServlet servlet; + private @Mock @NonNullByDefault({}) EventWebSocketAdapter servlet; private @Mock @NonNullByDefault({}) ItemRegistry itemRegistry; private @Mock @NonNullByDefault({}) EventPublisher eventPublisher; private @Mock @NonNullByDefault({}) Session session; diff --git a/features/karaf/openhab-core/src/main/feature/feature.xml b/features/karaf/openhab-core/src/main/feature/feature.xml index 5fe065535e4..615a415ab7b 100644 --- a/features/karaf/openhab-core/src/main/feature/feature.xml +++ b/features/karaf/openhab-core/src/main/feature/feature.xml @@ -181,6 +181,8 @@ openhab-core-base + openhab-core-io-rest-auth + mvn:org.eclipse.jetty.websocket/websocket-servlet/${jetty.version} mvn:org.eclipse.jetty.websocket/websocket-server/${jetty.version} mvn:org.openhab.core.bundles/org.openhab.core.io.websocket/${project.version} From 7e734b8540406ed0478689e4aa295bc03f1e4e4a Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 11 Jun 2023 21:22:26 +0200 Subject: [PATCH 104/126] Fix AuthFilter service declaration (#3651) Signed-off-by: Jan N. Klug --- .../src/main/java/org/openhab/core/io/rest/auth/AuthFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/AuthFilter.java b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/AuthFilter.java index 38bbfbca7dc..543f3688d33 100644 --- a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/AuthFilter.java +++ b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/AuthFilter.java @@ -79,7 +79,7 @@ */ @PreMatching @Component(configurationPid = "org.openhab.restauth", property = Constants.SERVICE_PID - + "=org.openhab.restauth", service = AuthFilter.class) + + "=org.openhab.restauth", service = { ContainerRequestFilter.class, AuthFilter.class }) @ConfigurableService(category = "system", label = "API Security", description_uri = AuthFilter.CONFIG_URI) @JaxrsExtension @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") From 545ecfee21700a08eeed441aace605f1195e416d Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 11 Jun 2023 21:25:16 +0200 Subject: [PATCH 105/126] Also adjust system channel types for unit (#3649) Signed-off-by: Jan N. Klug --- .../openhab/core/tools/internal/Upgrader.java | 78 +++++++++++++++++-- 1 file changed, 71 insertions(+), 7 deletions(-) diff --git a/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java b/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java index 5cf8daa7880..ddcbcc5f882 100644 --- a/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java +++ b/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java @@ -12,6 +12,10 @@ */ package org.openhab.core.tools.internal; +import static org.openhab.core.thing.DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_ATMOSPHERIC_HUMIDITY; +import static org.openhab.core.thing.DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_BATTERY_LEVEL; +import static org.openhab.core.thing.DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_COLOR_TEMPERATURE_ABS; + import java.nio.file.Files; import java.nio.file.Path; import java.time.ZonedDateTime; @@ -27,6 +31,7 @@ import org.openhab.core.items.MetadataKey; import org.openhab.core.library.items.NumberItem; import org.openhab.core.storage.json.internal.JsonStorage; +import org.openhab.core.thing.dto.ThingDTO; import org.openhab.core.thing.internal.link.ItemChannelLinkConfigDescriptionProvider; import org.openhab.core.thing.link.ItemChannelLink; import org.openhab.core.types.util.UnitUtils; @@ -53,6 +58,7 @@ public Upgrader(String baseDir, boolean force) { this.force = force; Path upgradeJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.tools.UpgradeTool"); + upgradeRecords = new JsonStorage<>(upgradeJsonDatabasePath.toFile(), null, 5, 0, 0, List.of()); } @@ -67,17 +73,29 @@ private boolean checkUpgradeRecord(String key) { } public void itemCopyUnitToMetadata() { + boolean noLink; + if (!checkUpgradeRecord(ITEM_COPY_UNIT_TO_METADATA)) { return; } Path itemJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.items.Item.json"); Path metadataJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.items.Metadata.json"); + Path linkJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.thing.link.ItemChannelLink.json"); + Path thingJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.thing.Thing.json"); logger.info("Copying item unit from state description to metadata in database '{}'", itemJsonDatabasePath); if (!Files.isReadable(itemJsonDatabasePath)) { logger.error("Cannot access item database '{}', check path and access rights.", itemJsonDatabasePath); return; } + + if (!Files.isReadable(linkJsonDatabasePath) || !Files.isReadable(thingJsonDatabasePath)) { + logger.warn("Cannot access thing or link database '{}', update may be incomplete.", linkJsonDatabasePath); + noLink = true; + } else { + noLink = false; + } + // missing metadata database is also fine, we create one in that case if (!Files.isWritable(metadataJsonDatabasePath) && Files.exists(metadataJsonDatabasePath)) { logger.error("Cannot access metadata database '{}', check path and access rights.", @@ -89,6 +107,10 @@ public void itemCopyUnitToMetadata() { null, 5, 0, 0, List.of()); JsonStorage metadataStorage = new JsonStorage<>(metadataJsonDatabasePath.toFile(), null, 5, 0, 0, List.of()); + JsonStorage linkStorage = noLink ? null + : new JsonStorage<>(linkJsonDatabasePath.toFile(), null, 5, 0, 0, List.of()); + JsonStorage thingStorage = noLink ? null + : new JsonStorage<>(thingJsonDatabasePath.toFile(), null, 5, 0, 0, List.of()); itemStorage.getKeys().forEach(itemName -> { ManagedItemProvider.PersistedItem item = itemStorage.get(itemName); @@ -96,9 +118,49 @@ public void itemCopyUnitToMetadata() { if (metadataStorage.containsKey(NumberItem.UNIT_METADATA_NAMESPACE + ":" + itemName)) { logger.debug("{}: Already contains a 'unit' metadata, skipping it", itemName); } else { + String unit = null; + if (!noLink) { + List links = linkStorage.getValues().stream().map(Objects::requireNonNull) + .filter(link -> itemName.equals(link.getItemName())).toList(); + // check if we can find the channel for these links + for (ItemChannelLink link : links) { + ThingDTO thing = thingStorage.get(link.getLinkedUID().getThingUID().toString()); + if (thing == null) { + logger.info( + "{}: Could not find thing for channel '{}'. Check if you need to set unit metadata.", + itemName, link.getLinkedUID()); + continue; + } + String channelTypeUID = thing.channels.stream() + .filter(channel -> link.getLinkedUID().toString().equals(channel.uid)) + .map(channel -> channel.channelTypeUID).findFirst().orElse(null); + if (channelTypeUID == null) { + continue; + } + // replace . with :, if the database is already correct, we can ignore that + channelTypeUID = channelTypeUID.replace(".", ":"); + if (channelTypeUID.startsWith("system")) { + if (channelTypeUID.equals(SYSTEM_CHANNEL_TYPE_UID_BATTERY_LEVEL.toString()) + || channelTypeUID + .equals(SYSTEM_CHANNEL_TYPE_UID_ATMOSPHERIC_HUMIDITY.toString())) { + unit = "%"; + } else if (channelTypeUID + .equals(SYSTEM_CHANNEL_TYPE_UID_COLOR_TEMPERATURE_ABS.toString())) { + unit = "K"; + } + } else { + logger.warn( + "{}: Could not determine if channel '{}' sets a state description. Check if you need to set unit metadata.", + itemName, link.getLinkedUID()); + } + } + } + + // metadata state description has higher priority, so we check that and override the unit if + // necessary Metadata metadata = metadataStorage.get("stateDescription:" + itemName); if (metadata == null) { - logger.debug("{}: Nothing to do, no state description found.", itemName); + logger.debug("{}: No state description in metadata found.", itemName); } else { String pattern = (String) metadata.getConfiguration().get("pattern"); if (pattern != null) { @@ -109,18 +171,20 @@ public void itemCopyUnitToMetadata() { } else { Unit stateDescriptionUnit = UnitUtils.parseUnit(pattern); if (stateDescriptionUnit != null) { - String unit = stateDescriptionUnit.toString(); - MetadataKey defaultUnitMetadataKey = new MetadataKey( - NumberItem.UNIT_METADATA_NAMESPACE, itemName); - Metadata defaultUnitMetadata = new Metadata(defaultUnitMetadataKey, unit, null); - metadataStorage.put(defaultUnitMetadataKey.toString(), defaultUnitMetadata); - logger.info("{}: Wrote 'unit={}' to metadata.", itemName, unit); + unit = stateDescriptionUnit.toString(); } } } else { logger.debug("{}: Nothing to do, no pattern found.", itemName); } } + if (unit != null) { + MetadataKey defaultUnitMetadataKey = new MetadataKey(NumberItem.UNIT_METADATA_NAMESPACE, + itemName); + Metadata defaultUnitMetadata = new Metadata(defaultUnitMetadataKey, unit, null); + metadataStorage.put(defaultUnitMetadataKey.toString(), defaultUnitMetadata); + logger.info("{}: Wrote 'unit={}' to metadata.", itemName, unit); + } } } }); From 885a8548c8985f296dcab3e021321c89d63626eb Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 11 Jun 2023 22:36:34 +0200 Subject: [PATCH 106/126] Improve community marketplace addon handler (#3641) Allow unusual jar naming convention Signed-off-by: Jan N. Klug --- .../internal/community/CommunityMarketplaceAddonService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/community/CommunityMarketplaceAddonService.java b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/community/CommunityMarketplaceAddonService.java index e5c699cacba..7054b6f94bd 100644 --- a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/community/CommunityMarketplaceAddonService.java +++ b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/community/CommunityMarketplaceAddonService.java @@ -90,7 +90,7 @@ public class CommunityMarketplaceAddonService extends AbstractRemoteAddonService private static final String COMMUNITY_BASE_URL = "https://community.openhab.org"; private static final String COMMUNITY_MARKETPLACE_URL = COMMUNITY_BASE_URL + "/c/marketplace/69/l/latest"; private static final String COMMUNITY_TOPIC_URL = COMMUNITY_BASE_URL + "/t/"; - private static final Pattern BUNDLE_NAME_PATTERN = Pattern.compile(".*/(.*)-\\d+\\.\\d+\\.\\d+.*"); + private static final Pattern BUNDLE_NAME_PATTERN = Pattern.compile(".*/(.*?)-\\d+\\.\\d+\\.\\d+.*"); private static final String SERVICE_ID = "marketplace"; private static final String ADDON_ID_PREFIX = SERVICE_ID + ":"; From 6e83d3f8def866af727e30c59b031a86ccbad18a Mon Sep 17 00:00:00 2001 From: Yannick Schaus Date: Thu, 15 Jun 2023 20:16:14 +0200 Subject: [PATCH 107/126] "Cacheability" option for critical REST resources (#3335) * Closes #3329. This implements a new optional `cacheable` parameter for these REST endpoints: - `/rest/items` - `/rest/things` - `/rest/rules` When this parameter is set, a flat list of all elements excluding non-cacheable fields (e.g. "state", "transformedState", "stateDescription", "commandDescription" for items, "statusInfo", "firmwareStatus", "properties" for things, "status" for rules) will be retrieved along with a `Last-Modified` HTTP response header. When unknown, the Last-Modified header will be set to the date of the request. Also only when this parameter is set, and a `If-Modified-Since` header is found in the request, that header will be compared to the last known modified date for the corresponding cacheable list. The last modified date will be reset when any change is made on the elements of the underlying registry. If the `If-Modified-Since` date is equal or more recent than the last modified date, then a 304 Not Modified response with no content will be served instead of the usual 200 OK, informing the client that its cache is still valid at the provided date. All other request parameters will be ignored except for "metadata" in the `/rest/items` endpoint. When a metadata selector is set, the resulting item list will be considered like a completely different resource, i.e. it will have its own last modified date. Regarding metadata, the approach to invalidating last modified dates is very conservative: when any metadata is changed, all cacheable lists of items will have their last modified date reset even if the change was in a metadata namespace that wasn't requested. This also implements the abovedescribed behavior for the `/rest/ui/components/{namespace}` endpoint, but no `cacheable` parameter is necessary. The last modified date is tracked by namespace. Signed-off-by: Yannick Schaus --- .../rest/internal/RuleResource.java | 69 ++++++++++++- .../rest/core/internal/item/ItemResource.java | 97 ++++++++++++++++++- .../core/internal/thing/ThingResource.java | 63 +++++++++++- .../core/io/rest/ui/internal/UIResource.java | 77 ++++++++++++++- .../internal/item/ItemResourceOSGiTest.java | 39 +++++--- 5 files changed, 322 insertions(+), 23 deletions(-) diff --git a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java index ac226f289e9..2ffb7adb75e 100644 --- a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java +++ b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java @@ -17,9 +17,11 @@ import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.time.Instant; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.function.Predicate; @@ -29,6 +31,7 @@ import javax.annotation.security.RolesAllowed; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; @@ -36,8 +39,10 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.CacheControl; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.SecurityContext; @@ -69,6 +74,7 @@ import org.openhab.core.automation.rest.internal.dto.EnrichedRuleDTOMapper; import org.openhab.core.automation.util.ModuleBuilder; import org.openhab.core.automation.util.RuleBuilder; +import org.openhab.core.common.registry.RegistryChangeListener; import org.openhab.core.config.core.ConfigUtil; import org.openhab.core.config.core.Configuration; import org.openhab.core.events.Event; @@ -80,6 +86,7 @@ import org.openhab.core.library.types.DateTimeType; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants; import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired; @@ -128,8 +135,10 @@ public class RuleResource implements RESTResource { private final RuleManager ruleManager; private final RuleRegistry ruleRegistry; private final ManagedRuleProvider managedRuleProvider; + private final ResetLastModifiedChangeListener resetLastModifiedChangeListener = new ResetLastModifiedChangeListener(); private @Context @NonNullByDefault({}) UriInfo uriInfo; + private @Nullable Date cacheableListLastModified = null; @Activate public RuleResource( // @@ -141,6 +150,13 @@ public RuleResource( // this.ruleManager = ruleManager; this.ruleRegistry = ruleRegistry; this.managedRuleProvider = managedRuleProvider; + + this.ruleRegistry.addRegistryChangeListener(resetLastModifiedChangeListener); + } + + @Deactivate + void deactivate() { + this.ruleRegistry.removeRegistryChangeListener(resetLastModifiedChangeListener); } @GET @@ -148,13 +164,38 @@ public RuleResource( // @Produces(MediaType.APPLICATION_JSON) @Operation(operationId = "getRules", summary = "Get available rules, optionally filtered by tags and/or prefix.", responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = EnrichedRuleDTO.class)))) }) - public Response get(@Context SecurityContext securityContext, @QueryParam("prefix") final @Nullable String prefix, - @QueryParam("tags") final @Nullable List tags, - @QueryParam("summary") @Parameter(description = "summary fields only") @Nullable Boolean summary) { + public Response get(@Context SecurityContext securityContext, @Context Request request, + @QueryParam("prefix") final @Nullable String prefix, @QueryParam("tags") final @Nullable List tags, + @QueryParam("summary") @Parameter(description = "summary fields only") @Nullable Boolean summary, + @DefaultValue("false") @QueryParam("staticDataOnly") @Parameter(description = "provides a cacheable list of values not expected to change regularly and honors the If-Modified-Since header, all other parameters are ignored") boolean staticDataOnly) { + if ((summary == null || !summary) && !securityContext.isUserInRole(Role.ADMIN)) { // users may only access the summary return JSONResponse.createErrorResponse(Status.UNAUTHORIZED, "Authentication required"); } + + if (staticDataOnly) { + if (cacheableListLastModified != null) { + Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(cacheableListLastModified); + if (responseBuilder != null) { + // send 304 Not Modified + return responseBuilder.build(); + } + } else { + cacheableListLastModified = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS)); + } + + Stream rules = ruleRegistry.stream() + .map(rule -> EnrichedRuleDTOMapper.map(rule, ruleManager, managedRuleProvider)); + + CacheControl cc = new CacheControl(); + cc.setMustRevalidate(true); + cc.setPrivate(true); + rules = dtoMapper.limitToFields(rules, "uid,templateUID,name,visibility,description,tags,editable"); + return Response.ok(new Stream2JSONInputStream(rules)).lastModified(cacheableListLastModified) + .cacheControl(cc).build(); + } + // match all Predicate p = r -> true; @@ -567,4 +608,26 @@ public Response setModuleConfigParam(@PathParam("ruleUID") @Parameter(descriptio return null; } } + + private void resetStaticListLastModified() { + cacheableListLastModified = null; + } + + private class ResetLastModifiedChangeListener implements RegistryChangeListener { + + @Override + public void added(Rule element) { + resetStaticListLastModified(); + } + + @Override + public void removed(Rule element) { + resetStaticListLastModified(); + } + + @Override + public void updated(Rule oldElement, Rule element) { + resetStaticListLastModified(); + } + } } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java index 46d734be7a2..28e1b6e5ef0 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java @@ -12,9 +12,12 @@ */ package org.openhab.core.io.rest.core.internal.item; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -40,9 +43,11 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.CacheControl; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; @@ -52,6 +57,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.auth.Role; +import org.openhab.core.common.registry.RegistryChangeListener; import org.openhab.core.events.EventPublisher; import org.openhab.core.io.rest.DTOMapper; import org.openhab.core.io.rest.JSONResponse; @@ -68,6 +74,7 @@ import org.openhab.core.items.ItemBuilderFactory; import org.openhab.core.items.ItemNotFoundException; import org.openhab.core.items.ItemRegistry; +import org.openhab.core.items.ItemRegistryChangeListener; import org.openhab.core.items.ManagedItemProvider; import org.openhab.core.items.Metadata; import org.openhab.core.items.MetadataKey; @@ -88,6 +95,7 @@ import org.openhab.core.types.TypeParser; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants; import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired; @@ -174,6 +182,10 @@ private static void respectForwarded(final UriBuilder uriBuilder, final @Context private final ManagedItemProvider managedItemProvider; private final MetadataRegistry metadataRegistry; private final MetadataSelectorMatcher metadataSelectorMatcher; + private final ItemRegistryChangeListener resetLastModifiedItemChangeListener = new ResetLastModifiedItemChangeListener(); + private final RegistryChangeListener resetLastModifiedMetadataChangeListener = new ResetLastModifiedMetadataChangeListener(); + + private Map<@Nullable String, Date> cacheableListsLastModified = new HashMap<>(); @Activate public ItemResource(// @@ -193,6 +205,15 @@ public ItemResource(// this.managedItemProvider = managedItemProvider; this.metadataRegistry = metadataRegistry; this.metadataSelectorMatcher = metadataSelectorMatcher; + + this.itemRegistry.addRegistryChangeListener(resetLastModifiedItemChangeListener); + this.metadataRegistry.addRegistryChangeListener(resetLastModifiedMetadataChangeListener); + } + + @Deactivate + void deactivate() { + this.itemRegistry.removeRegistryChangeListener(resetLastModifiedItemChangeListener); + this.metadataRegistry.removeRegistryChangeListener(resetLastModifiedMetadataChangeListener); } private UriBuilder uriBuilder(final UriInfo uriInfo, final HttpHeaders httpHeaders) { @@ -207,17 +228,47 @@ private UriBuilder uriBuilder(final UriInfo uriInfo, final HttpHeaders httpHeade @Operation(operationId = "getItems", summary = "Get all available items.", responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = EnrichedItemDTO.class)))) }) public Response getItems(final @Context UriInfo uriInfo, final @Context HttpHeaders httpHeaders, + @Context Request request, @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, @QueryParam("type") @Parameter(description = "item type filter") @Nullable String type, @QueryParam("tags") @Parameter(description = "item tag filter") @Nullable String tags, @DefaultValue(".*") @QueryParam("metadata") @Parameter(description = "metadata selector - a comma separated list or a regular expression (returns all if no value given)") @Nullable String namespaceSelector, @DefaultValue("false") @QueryParam("recursive") @Parameter(description = "get member items recursively") boolean recursive, - @QueryParam("fields") @Parameter(description = "limit output to the given fields (comma separated)") @Nullable String fields) { + @QueryParam("fields") @Parameter(description = "limit output to the given fields (comma separated)") @Nullable String fields, + @DefaultValue("false") @QueryParam("staticDataOnly") @Parameter(description = "provides a cacheable list of values not expected to change regularly and checks the If-Modified-Since header, all other parameters are ignored except \"metadata\"") boolean staticDataOnly) { final Locale locale = localeService.getLocale(language); final Set namespaces = splitAndFilterNamespaces(namespaceSelector, locale); final UriBuilder uriBuilder = uriBuilder(uriInfo, httpHeaders); + if (staticDataOnly) { + Date lastModifiedDate = Date.from(Instant.now()); + if (cacheableListsLastModified.containsKey(namespaceSelector)) { + lastModifiedDate = cacheableListsLastModified.get(namespaceSelector); + Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(lastModifiedDate); + if (responseBuilder != null) { + // send 304 Not Modified + return responseBuilder.build(); + } + } else { + lastModifiedDate = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS)); + cacheableListsLastModified.put(namespaceSelector, lastModifiedDate); + } + + Stream itemStream = getItems(null, null).stream() // + .map(item -> EnrichedItemDTOMapper.map(item, false, null, uriBuilder, locale)) // + .peek(dto -> addMetadata(dto, namespaces, null)) // + .peek(dto -> dto.editable = isEditable(dto.name)); + itemStream = dtoMapper.limitToFields(itemStream, + "name,label,type,groupType,function,category,editable,groupNames,link,tags,metadata"); + + CacheControl cc = new CacheControl(); + cc.setMustRevalidate(true); + cc.setPrivate(true); + return Response.ok(new Stream2JSONInputStream(itemStream)).lastModified(lastModifiedDate).cacheControl(cc) + .build(); + } + Stream itemStream = getItems(type, tags).stream() // .map(item -> EnrichedItemDTOMapper.map(item, recursive, null, uriBuilder, locale)) // .peek(dto -> addMetadata(dto, namespaces, null)) // @@ -935,4 +986,48 @@ private void addMetadata(EnrichedItemDTO dto, Set namespaces, @Nullable private boolean isEditable(String itemName) { return managedItemProvider.get(itemName) != null; } + + private void resetCacheableListsLastModified() { + this.cacheableListsLastModified.clear(); + } + + private class ResetLastModifiedItemChangeListener implements ItemRegistryChangeListener { + @Override + public void added(Item element) { + resetCacheableListsLastModified(); + } + + @Override + public void allItemsChanged(Collection oldItemNames) { + resetCacheableListsLastModified(); + } + + @Override + public void removed(Item element) { + resetCacheableListsLastModified(); + } + + @Override + public void updated(Item oldElement, Item element) { + resetCacheableListsLastModified(); + } + } + + private class ResetLastModifiedMetadataChangeListener implements RegistryChangeListener { + + @Override + public void added(Metadata element) { + resetCacheableListsLastModified(); + } + + @Override + public void removed(Metadata element) { + resetCacheableListsLastModified(); + } + + @Override + public void updated(Metadata oldElement, Metadata element) { + resetCacheableListsLastModified(); + } + } } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java index 324482635ff..84816fa841a 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java @@ -15,9 +15,12 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -39,9 +42,11 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.CacheControl; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; @@ -49,6 +54,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.auth.Role; +import org.openhab.core.common.registry.RegistryChangeListener; import org.openhab.core.config.core.ConfigDescription; import org.openhab.core.config.core.ConfigDescriptionRegistry; import org.openhab.core.config.core.ConfigUtil; @@ -100,6 +106,7 @@ import org.openhab.core.thing.util.ThingHelper; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants; import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired; @@ -164,8 +171,10 @@ public class ThingResource implements RESTResource { private final ThingRegistry thingRegistry; private final ThingStatusInfoI18nLocalizationService thingStatusInfoI18nLocalizationService; private final ThingTypeRegistry thingTypeRegistry; + private final ResetLastModifiedChangeListener resetLastModifiedChangeListener = new ResetLastModifiedChangeListener(); private @Context @NonNullByDefault({}) UriInfo uriInfo; + private @Nullable Date cacheableListLastModified = null; @Activate public ThingResource( // @@ -198,6 +207,13 @@ public ThingResource( // this.thingRegistry = thingRegistry; this.thingStatusInfoI18nLocalizationService = thingStatusInfoI18nLocalizationService; this.thingTypeRegistry = thingTypeRegistry; + + this.thingRegistry.addRegistryChangeListener(resetLastModifiedChangeListener); + } + + @Deactivate + void deactivate() { + this.thingRegistry.removeRegistryChangeListener(resetLastModifiedChangeListener); } /** @@ -291,13 +307,34 @@ public Response create( @Operation(operationId = "getThings", summary = "Get all available things.", security = { @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = EnrichedThingDTO.class), uniqueItems = true))) }) - public Response getAll( + public Response getAll(@Context Request request, @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, - @QueryParam("summary") @Parameter(description = "summary fields only") @Nullable Boolean summary) { + @QueryParam("summary") @Parameter(description = "summary fields only") @Nullable Boolean summary, + @DefaultValue("false") @QueryParam("staticDataOnly") @Parameter(description = "provides a cacheable list of values not expected to change regularly and checks the If-Modified-Since header") boolean staticDataOnly) { final Locale locale = localeService.getLocale(language); Stream thingStream = thingRegistry.stream().map(t -> convertToEnrichedThingDTO(t, locale)) .distinct(); + + if (staticDataOnly) { + if (cacheableListLastModified != null) { + Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(cacheableListLastModified); + if (responseBuilder != null) { + // send 304 Not Modified + return responseBuilder.build(); + } + } else { + cacheableListLastModified = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS)); + } + + CacheControl cc = new CacheControl(); + cc.setMustRevalidate(true); + cc.setPrivate(true); + thingStream = dtoMapper.limitToFields(thingStream, "UID,label,bridgeUID,thingTypeUID,location,editable"); + return Response.ok(new Stream2JSONInputStream(thingStream)).lastModified(cacheableListLastModified) + .cacheControl(cc).build(); + } + if (summary != null && summary) { thingStream = dtoMapper.limitToFields(thingStream, "UID,label,bridgeUID,thingTypeUID,statusInfo,firmwareStatus,location,editable"); @@ -853,4 +890,26 @@ private URI getConfigDescriptionURI(ChannelUID channelUID) { throw new BadRequestException("Invalid URI syntax: " + uriString); } } + + private void resetCacheableListLastModified() { + cacheableListLastModified = null; + } + + private class ResetLastModifiedChangeListener implements RegistryChangeListener { + + @Override + public void added(Thing element) { + resetCacheableListLastModified(); + } + + @Override + public void removed(Thing element) { + resetCacheableListLastModified(); + } + + @Override + public void updated(Thing oldElement, Thing element) { + resetCacheableListLastModified(); + } + } } diff --git a/bundles/org.openhab.core.io.rest.ui/src/main/java/org/openhab/core/io/rest/ui/internal/UIResource.java b/bundles/org.openhab.core.io.rest.ui/src/main/java/org/openhab/core/io/rest/ui/internal/UIResource.java index 166a3863dd5..eb34f73ce11 100644 --- a/bundles/org.openhab.core.io.rest.ui/src/main/java/org/openhab/core/io/rest/ui/internal/UIResource.java +++ b/bundles/org.openhab.core.io.rest.ui/src/main/java/org/openhab/core/io/rest/ui/internal/UIResource.java @@ -13,7 +13,11 @@ package org.openhab.core.io.rest.ui.internal; import java.security.InvalidParameterException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import java.util.Set; import java.util.stream.Stream; @@ -27,13 +31,17 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.CacheControl; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.auth.Role; +import org.openhab.core.common.registry.RegistryChangeListener; import org.openhab.core.io.rest.RESTConstants; import org.openhab.core.io.rest.RESTResource; import org.openhab.core.io.rest.Stream2JSONInputStream; @@ -45,6 +53,7 @@ import org.openhab.core.ui.tiles.TileProvider; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants; import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired; @@ -84,6 +93,9 @@ public class UIResource implements RESTResource { private final UIComponentRegistryFactory componentRegistryFactory; private final TileProvider tileProvider; + private Map lastModifiedDates = new HashMap<>(); + private Map> registryChangeListeners = new HashMap<>(); + @Activate public UIResource( // final @Reference UIComponentRegistryFactory componentRegistryFactory, @@ -92,6 +104,14 @@ public UIResource( // this.tileProvider = tileProvider; } + @Deactivate + public void deactivate() { + registryChangeListeners.forEach((n, l) -> { + UIComponentRegistry registry = componentRegistryFactory.getRegistry(n); + registry.removeRegistryChangeListener(l); + }); + } + @GET @Path("/tiles") @Produces({ MediaType.APPLICATION_JSON }) @@ -107,7 +127,7 @@ public Response getAll() { @Produces({ MediaType.APPLICATION_JSON }) @Operation(operationId = "getRegisteredUIComponentsInNamespace", summary = "Get all registered UI components in the specified namespace.", responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = RootUIComponent.class)))) }) - public Response getAllComponents(@PathParam("namespace") String namespace, + public Response getAllComponents(@Context Request request, @PathParam("namespace") String namespace, @QueryParam("summary") @Parameter(description = "summary fields only") @Nullable Boolean summary) { UIComponentRegistry registry = componentRegistryFactory.getRegistry(namespace); Stream components = registry.getAll().stream(); @@ -126,8 +146,33 @@ public Response getAllComponents(@PathParam("namespace") String namespace, } return component; }); + return Response.ok(new Stream2JSONInputStream(components)).build(); + } else { + if (!registryChangeListeners.containsKey(namespace)) { + RegistryChangeListener changeListener = new ResetLastModifiedChangeListener(namespace); + registryChangeListeners.put(namespace, changeListener); + registry.addRegistryChangeListener(changeListener); + } + + Date lastModifiedDate = Date.from(Instant.now()); + if (lastModifiedDates.containsKey(namespace)) { + lastModifiedDate = lastModifiedDates.get(namespace); + Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(lastModifiedDate); + if (responseBuilder != null) { + // send 304 Not Modified + return responseBuilder.build(); + } + } else { + lastModifiedDate = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS)); + lastModifiedDates.put(namespace, lastModifiedDate); + } + + CacheControl cc = new CacheControl(); + cc.setMustRevalidate(true); + cc.setPrivate(true); + return Response.ok(new Stream2JSONInputStream(components)).lastModified(lastModifiedDate).cacheControl(cc) + .build(); } - return Response.ok(new Stream2JSONInputStream(components)).build(); } @GET @@ -208,4 +253,32 @@ public Response deleteComponent(@PathParam("namespace") String namespace, private TileDTO toTileDTO(Tile tile) { return new TileDTO(tile.getName(), tile.getUrl(), tile.getOverlay(), tile.getImageUrl()); } + + private void resetLastModifiedDate(String namespace) { + lastModifiedDates.remove(namespace); + } + + private class ResetLastModifiedChangeListener implements RegistryChangeListener { + + private String namespace; + + ResetLastModifiedChangeListener(String namespace) { + this.namespace = namespace; + } + + @Override + public void added(RootUIComponent element) { + resetLastModifiedDate(namespace); + } + + @Override + public void removed(RootUIComponent element) { + resetLastModifiedDate(namespace); + } + + @Override + public void updated(RootUIComponent oldElement, RootUIComponent element) { + resetLastModifiedDate(namespace); + } + } } diff --git a/itests/org.openhab.core.io.rest.core.tests/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResourceOSGiTest.java b/itests/org.openhab.core.io.rest.core.tests/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResourceOSGiTest.java index 5e684f4ed62..b39d4a01192 100644 --- a/itests/org.openhab.core.io.rest.core.tests/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResourceOSGiTest.java +++ b/itests/org.openhab.core.io.rest.core.tests/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResourceOSGiTest.java @@ -30,6 +30,7 @@ import java.util.stream.Stream; import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriBuilder; @@ -94,6 +95,7 @@ public class ItemResourceOSGiTest extends JavaOSGiTest { private @Mock @NonNullByDefault({}) ItemProvider itemProviderMock; private @Mock @NonNullByDefault({}) UriBuilder uriBuilderMock; private @Mock @NonNullByDefault({}) UriInfo uriInfoMock; + private @Mock @NonNullByDefault({}) Request request; @BeforeEach public void beforeEach() { @@ -127,7 +129,8 @@ public void beforeEach() { public void shouldReturnUnicodeItems() throws IOException, TransformationException { item4.setLabel(ITEM_LABEL4); - Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, null, null, null, false, null); + Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, null, null, null, false, + null, false); assertThat(readItemLabelsFromResponse(response), hasItems(ITEM_LABEL4)); } @@ -147,28 +150,31 @@ public void shouldFilterItemsByTag() throws Exception { item3.addTag("Tag2"); item4.addTag("Tag4"); - Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, null, "Tag1", null, false, null); + Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, null, "Tag1", null, + false, null, false); assertThat(readItemNamesFromResponse(response), hasItems(ITEM_NAME1, ITEM_NAME2)); - response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, null, "Tag2", null, false, null); + response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, null, "Tag2", null, false, null, + false); assertThat(readItemNamesFromResponse(response), hasItems(ITEM_NAME2, ITEM_NAME3)); - response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, null, "NotExistingTag", null, false, null); + response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, null, "NotExistingTag", null, + false, null, false); assertThat(readItemNamesFromResponse(response), hasSize(0)); } @Test public void shouldFilterItemsByType() throws Exception { - Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, CoreItemFactory.SWITCH, null, - null, false, null); + Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, CoreItemFactory.SWITCH, + null, null, false, null, false); assertThat(readItemNamesFromResponse(response), hasItems(ITEM_NAME1, ITEM_NAME2)); - response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, CoreItemFactory.DIMMER, null, null, false, - null); + response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, CoreItemFactory.DIMMER, null, + null, false, null, false); assertThat(readItemNamesFromResponse(response), hasItems(ITEM_NAME3)); - response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, CoreItemFactory.COLOR, null, null, false, - null); + response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, CoreItemFactory.COLOR, null, null, + false, null, false); assertThat(readItemNamesFromResponse(response), hasSize(0)); } @@ -176,15 +182,18 @@ public void shouldFilterItemsByType() throws Exception { public void shouldAddAndRemoveTags() throws Exception { managedItemProvider.add(new SwitchItem("Switch")); - Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, null, "MyTag", null, false, null); + Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, null, "MyTag", null, + false, null, false); assertThat(readItemNamesFromResponse(response), hasSize(0)); itemResource.addTag("Switch", "MyTag"); - response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, null, "MyTag", null, false, null); + response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, null, "MyTag", null, false, null, + false); assertThat(readItemNamesFromResponse(response), hasSize(1)); itemResource.removeTag("Switch", "MyTag"); - response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, null, "MyTag", null, false, null); + response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, null, "MyTag", null, false, null, + false); assertThat(readItemNamesFromResponse(response), hasSize(0)); } @@ -192,8 +201,8 @@ public void shouldAddAndRemoveTags() throws Exception { public void shouldIncludeRequestedFieldsOnly() throws Exception { managedItemProvider.add(new SwitchItem("Switch")); itemResource.addTag("Switch", "MyTag"); - Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, null, "MyTag", null, false, - "type,name"); + Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, null, "MyTag", null, + false, "type,name", false); JsonElement result = JsonParser .parseString(new String(((InputStream) response.getEntity()).readAllBytes(), StandardCharsets.UTF_8)); From f86635fe96c87f240d5fd2235f0f870c9196e308 Mon Sep 17 00:00:00 2001 From: lolodomo Date: Fri, 16 Jun 2023 16:38:03 +0200 Subject: [PATCH 108/126] [rest] Add semantic tag registry + API to manage user tags (#3646) * Add semantic tag registry + REST API to manage user tags Related to #3619 New registry for semantic tags. New default semantic tags provider for all built-in semantic tags. New managed provider to add/remove/update user semantic tags. Storage of user semantic tags in a JSON DB file. New REST API to add/remove/update user tags in the semantic model. New REST API to get a sub-tree of the semantic tags. Semantic tag class annotations are removed. Semantic tag classes are now created at runtime. Classes Locations, Equipments, Points and Properties are removed Static methods SemanticTags.add removed The adding of semantic tag classes is now managed only by the tag registry. Avoids calling static method SemanticTags.getById when possible SemanticsMetadataProvider service now requires semanticTagRegistry to start. Signed-off-by: Laurent Garnier --- .../rest/core/internal/item/ItemResource.java | 10 +- .../internal/tag/EnrichedSemanticTagDTO.java | 41 +++ .../io/rest/core/internal/tag/TagDTO.java | 38 --- .../rest/core/internal/tag/TagResource.java | 175 +++++++++- .../model/script/actions/SemanticsTest.java | 38 ++- .../model/generateTagClasses.groovy | 224 +++---------- .../org/openhab/core/semantics/Equipment.java | 1 - .../org/openhab/core/semantics/Location.java | 1 - .../semantics/ManagedSemanticTagProvider.java | 72 ++++ .../org/openhab/core/semantics/Point.java | 1 - .../org/openhab/core/semantics/Property.java | 1 - .../openhab/core/semantics/SemanticTag.java | 71 ++++ .../core/semantics/SemanticTagImpl.java | 133 ++++++++ .../Tilt.java => SemanticTagProvider.java} | 12 +- .../core/semantics/SemanticTagRegistry.java | 80 +++++ .../openhab/core/semantics/SemanticTags.java | 244 +------------- .../core/semantics/SemanticsService.java | 33 ++ .../org/openhab/core/semantics/TagInfo.java | 37 --- .../core/semantics/dto/SemanticTagDTO.java | 31 ++ .../semantics/dto/SemanticTagDTOMapper.java | 60 ++++ .../internal/SemanticTagRegistryImpl.java | 285 ++++++++++++++++ .../internal/SemanticsMetadataProvider.java | 9 +- .../internal/SemanticsServiceImpl.java | 52 ++- .../model/DefaultSemanticTagProvider.java | 311 ++++++++++++++++++ .../model/equipment/AlarmSystem.java | 27 -- .../semantics/model/equipment/BackDoor.java | 26 -- .../semantics/model/equipment/Battery.java | 27 -- .../semantics/model/equipment/Blinds.java | 27 -- .../semantics/model/equipment/Boiler.java | 27 -- .../semantics/model/equipment/Camera.java | 27 -- .../core/semantics/model/equipment/Car.java | 27 -- .../semantics/model/equipment/CeilingFan.java | 26 -- .../semantics/model/equipment/CellarDoor.java | 26 -- .../model/equipment/CleaningRobot.java | 27 -- .../semantics/model/equipment/Dishwasher.java | 26 -- .../core/semantics/model/equipment/Door.java | 27 -- .../semantics/model/equipment/Doorbell.java | 27 -- .../core/semantics/model/equipment/Dryer.java | 26 -- .../semantics/model/equipment/Equipments.java | 96 ------ .../core/semantics/model/equipment/Fan.java | 27 -- .../semantics/model/equipment/Freezer.java | 26 -- .../semantics/model/equipment/FrontDoor.java | 26 -- .../semantics/model/equipment/GarageDoor.java | 26 -- .../core/semantics/model/equipment/Gate.java | 26 -- .../core/semantics/model/equipment/HVAC.java | 27 -- .../semantics/model/equipment/InnerDoor.java | 26 -- .../semantics/model/equipment/Inverter.java | 27 -- .../model/equipment/KitchenHood.java | 26 -- .../semantics/model/equipment/LawnMower.java | 27 -- .../model/equipment/LightStripe.java | 26 -- .../semantics/model/equipment/Lightbulb.java | 27 -- .../core/semantics/model/equipment/Lock.java | 27 -- .../model/equipment/MotionDetector.java | 26 -- .../model/equipment/NetworkAppliance.java | 27 -- .../core/semantics/model/equipment/Oven.java | 26 -- .../model/equipment/PowerOutlet.java | 27 -- .../semantics/model/equipment/Projector.java | 27 -- .../core/semantics/model/equipment/Pump.java | 27 -- .../model/equipment/RadiatorControl.java | 27 -- .../semantics/model/equipment/Receiver.java | 27 -- .../model/equipment/Refrigerator.java | 26 -- .../model/equipment/RemoteControl.java | 27 -- .../semantics/model/equipment/Screen.java | 27 -- .../semantics/model/equipment/Sensor.java | 27 -- .../semantics/model/equipment/SideDoor.java | 26 -- .../core/semantics/model/equipment/Siren.java | 27 -- .../semantics/model/equipment/Smartphone.java | 27 -- .../model/equipment/SmokeDetector.java | 26 -- .../semantics/model/equipment/Speaker.java | 27 -- .../semantics/model/equipment/Television.java | 26 -- .../core/semantics/model/equipment/Valve.java | 27 -- .../model/equipment/VoiceAssistant.java | 27 -- .../semantics/model/equipment/WallSwitch.java | 27 -- .../model/equipment/WashingMachine.java | 26 -- .../model/equipment/WeatherService.java | 26 -- .../semantics/model/equipment/WebService.java | 27 -- .../semantics/model/equipment/WhiteGood.java | 27 -- .../semantics/model/equipment/Window.java | 27 -- .../semantics/model/location/Apartment.java | 26 -- .../core/semantics/model/location/Attic.java | 26 -- .../semantics/model/location/Basement.java | 26 -- .../semantics/model/location/Bathroom.java | 26 -- .../semantics/model/location/Bedroom.java | 26 -- .../semantics/model/location/BoilerRoom.java | 26 -- .../semantics/model/location/Building.java | 26 -- .../semantics/model/location/Carport.java | 26 -- .../core/semantics/model/location/Cellar.java | 26 -- .../semantics/model/location/Corridor.java | 26 -- .../semantics/model/location/DiningRoom.java | 26 -- .../semantics/model/location/Driveway.java | 26 -- .../core/semantics/model/location/Entry.java | 26 -- .../semantics/model/location/FamilyRoom.java | 26 -- .../semantics/model/location/FirstFloor.java | 26 -- .../core/semantics/model/location/Floor.java | 26 -- .../core/semantics/model/location/Garage.java | 26 -- .../core/semantics/model/location/Garden.java | 26 -- .../semantics/model/location/GroundFloor.java | 26 -- .../semantics/model/location/GuestRoom.java | 26 -- .../core/semantics/model/location/House.java | 26 -- .../core/semantics/model/location/Indoor.java | 27 -- .../semantics/model/location/Kitchen.java | 26 -- .../semantics/model/location/LaundryRoom.java | 26 -- .../semantics/model/location/LivingRoom.java | 26 -- .../semantics/model/location/Locations.java | 79 ----- .../core/semantics/model/location/Office.java | 26 -- .../semantics/model/location/Outdoor.java | 27 -- .../core/semantics/model/location/Patio.java | 26 -- .../core/semantics/model/location/Porch.java | 26 -- .../core/semantics/model/location/Room.java | 26 -- .../semantics/model/location/SecondFloor.java | 26 -- .../core/semantics/model/location/Shed.java | 26 -- .../semantics/model/location/SummerHouse.java | 26 -- .../semantics/model/location/Terrace.java | 26 -- .../semantics/model/location/ThirdFloor.java | 26 -- .../semantics/model/location/Veranda.java | 26 -- .../core/semantics/model/point/Alarm.java | 27 -- .../core/semantics/model/point/Control.java | 27 -- .../semantics/model/point/LowBattery.java | 26 -- .../semantics/model/point/Measurement.java | 27 -- .../core/semantics/model/point/OpenLevel.java | 26 -- .../core/semantics/model/point/OpenState.java | 26 -- .../core/semantics/model/point/Points.java | 54 --- .../core/semantics/model/point/Setpoint.java | 27 -- .../core/semantics/model/point/Status.java | 27 -- .../core/semantics/model/point/Switch.java | 26 -- .../core/semantics/model/point/Tampered.java | 26 -- .../core/semantics/model/property/CO.java | 27 -- .../core/semantics/model/property/CO2.java | 27 -- .../model/property/ColorTemperature.java | 27 -- .../semantics/model/property/Current.java | 27 -- .../semantics/model/property/Duration.java | 27 -- .../core/semantics/model/property/Energy.java | 27 -- .../semantics/model/property/Frequency.java | 27 -- .../core/semantics/model/property/Gas.java | 27 -- .../semantics/model/property/Humidity.java | 27 -- .../core/semantics/model/property/Level.java | 27 -- .../core/semantics/model/property/Light.java | 27 -- .../core/semantics/model/property/Noise.java | 27 -- .../core/semantics/model/property/Oil.java | 27 -- .../semantics/model/property/Opening.java | 27 -- .../core/semantics/model/property/Power.java | 27 -- .../semantics/model/property/Presence.java | 27 -- .../semantics/model/property/Pressure.java | 27 -- .../semantics/model/property/Properties.java | 70 ---- .../core/semantics/model/property/Rain.java | 27 -- .../core/semantics/model/property/Smoke.java | 27 -- .../semantics/model/property/SoundVolume.java | 27 -- .../semantics/model/property/Temperature.java | 27 -- .../semantics/model/property/Timestamp.java | 27 -- .../semantics/model/property/Ultraviolet.java | 27 -- .../semantics/model/property/Vibration.java | 27 -- .../semantics/model/property/Voltage.java | 27 -- .../core/semantics/model/property/Water.java | 27 -- .../core/semantics/model/property/Wind.java | 27 -- .../core/semantics/SemanticTagsTest.java | 195 ++++------- .../semantics/SemanticsPredicatesTest.java | 30 +- .../internal/SemanticTagRegistryImplTest.java | 120 +++++++ .../SemanticsMetadataProviderTest.java | 15 +- .../internal/SemanticsServiceImplTest.java | 156 ++++++++- 159 files changed, 1784 insertions(+), 4334 deletions(-) create mode 100644 bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/EnrichedSemanticTagDTO.java delete mode 100644 bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagDTO.java create mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/ManagedSemanticTagProvider.java create mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTag.java create mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTagImpl.java rename bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/{model/point/Tilt.java => SemanticTagProvider.java} (59%) create mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTagRegistry.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/TagInfo.java create mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/dto/SemanticTagDTO.java create mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/dto/SemanticTagDTOMapper.java create mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticTagRegistryImpl.java create mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/DefaultSemanticTagProvider.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/AlarmSystem.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/BackDoor.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Battery.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Blinds.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Boiler.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Camera.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Car.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/CeilingFan.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/CellarDoor.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/CleaningRobot.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Dishwasher.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Door.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Doorbell.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Dryer.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Equipments.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Fan.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Freezer.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/FrontDoor.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/GarageDoor.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Gate.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/HVAC.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/InnerDoor.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Inverter.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/KitchenHood.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/LawnMower.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/LightStripe.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Lightbulb.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Lock.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/MotionDetector.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/NetworkAppliance.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Oven.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/PowerOutlet.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Projector.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Pump.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/RadiatorControl.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Receiver.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Refrigerator.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/RemoteControl.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Screen.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Sensor.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/SideDoor.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Siren.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Smartphone.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/SmokeDetector.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Speaker.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Television.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Valve.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/VoiceAssistant.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WallSwitch.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WashingMachine.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WeatherService.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WebService.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WhiteGood.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Window.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Apartment.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Attic.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Basement.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Bathroom.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Bedroom.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/BoilerRoom.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Building.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Carport.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Cellar.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Corridor.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/DiningRoom.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Driveway.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Entry.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/FamilyRoom.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/FirstFloor.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Floor.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Garage.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Garden.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/GroundFloor.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/GuestRoom.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/House.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Indoor.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Kitchen.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/LaundryRoom.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/LivingRoom.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Locations.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Office.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Outdoor.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Patio.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Porch.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Room.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/SecondFloor.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Shed.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/SummerHouse.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Terrace.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/ThirdFloor.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Veranda.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Alarm.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Control.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/LowBattery.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Measurement.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/OpenLevel.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/OpenState.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Points.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Setpoint.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Status.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Switch.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Tampered.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/CO.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/CO2.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/ColorTemperature.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Current.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Duration.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Energy.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Frequency.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Gas.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Humidity.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Level.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Light.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Noise.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Oil.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Opening.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Power.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Presence.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Pressure.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Properties.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Rain.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Smoke.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/SoundVolume.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Temperature.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Timestamp.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Ultraviolet.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Vibration.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Voltage.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Water.java delete mode 100644 bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Wind.java create mode 100644 bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/internal/SemanticTagRegistryImplTest.java diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java index 28e1b6e5ef0..59f8e4c465c 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java @@ -88,7 +88,7 @@ import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.RawType; import org.openhab.core.library.types.UpDownType; -import org.openhab.core.semantics.SemanticTags; +import org.openhab.core.semantics.SemanticTagRegistry; import org.openhab.core.semantics.SemanticsPredicates; import org.openhab.core.types.Command; import org.openhab.core.types.State; @@ -182,6 +182,7 @@ private static void respectForwarded(final UriBuilder uriBuilder, final @Context private final ManagedItemProvider managedItemProvider; private final MetadataRegistry metadataRegistry; private final MetadataSelectorMatcher metadataSelectorMatcher; + private final SemanticTagRegistry semanticTagRegistry; private final ItemRegistryChangeListener resetLastModifiedItemChangeListener = new ResetLastModifiedItemChangeListener(); private final RegistryChangeListener resetLastModifiedMetadataChangeListener = new ResetLastModifiedMetadataChangeListener(); @@ -196,7 +197,8 @@ public ItemResource(// final @Reference LocaleService localeService, // final @Reference ManagedItemProvider managedItemProvider, final @Reference MetadataRegistry metadataRegistry, - final @Reference MetadataSelectorMatcher metadataSelectorMatcher) { + final @Reference MetadataSelectorMatcher metadataSelectorMatcher, + final @Reference SemanticTagRegistry semanticTagRegistry) { this.dtoMapper = dtoMapper; this.eventPublisher = eventPublisher; this.itemBuilderFactory = itemBuilderFactory; @@ -205,6 +207,7 @@ public ItemResource(// this.managedItemProvider = managedItemProvider; this.metadataRegistry = metadataRegistry; this.metadataSelectorMatcher = metadataSelectorMatcher; + this.semanticTagRegistry = semanticTagRegistry; this.itemRegistry.addRegistryChangeListener(resetLastModifiedItemChangeListener); this.metadataRegistry.addRegistryChangeListener(resetLastModifiedMetadataChangeListener); @@ -865,7 +868,8 @@ public Response getSemanticItem(final @Context UriInfo uriInfo, final @Context H @PathParam("semanticClass") @Parameter(description = "semantic class") String semanticClassName) { Locale locale = localeService.getLocale(language); - Class semanticClass = SemanticTags.getById(semanticClassName); + Class semanticClass = semanticTagRegistry + .getTagClassById(semanticClassName); if (semanticClass == null) { return Response.status(Status.NOT_FOUND).build(); } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/EnrichedSemanticTagDTO.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/EnrichedSemanticTagDTO.java new file mode 100644 index 00000000000..f19d33f6802 --- /dev/null +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/EnrichedSemanticTagDTO.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.rest.core.internal.tag; + +import java.util.List; + +import org.openhab.core.semantics.SemanticTag; + +/** + * A DTO representing a {@link SemanticTag}. + * + * @author Jimmy Tanagra - initial contribution + * @author Laurent Garnier - Class renamed and members uid, description and editable added + */ +public class EnrichedSemanticTagDTO { + String uid; + String name; + String label; + String description; + List synonyms; + boolean editable; + + public EnrichedSemanticTagDTO(SemanticTag tag, boolean editable) { + this.uid = tag.getUID(); + this.name = tag.getUID().substring(tag.getUID().lastIndexOf("_") + 1); + this.label = tag.getLabel(); + this.description = tag.getDescription(); + this.synonyms = tag.getSynonyms(); + this.editable = editable; + } +} diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagDTO.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagDTO.java deleted file mode 100644 index 37d68f17cec..00000000000 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagDTO.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.io.rest.core.internal.tag; - -import java.util.List; -import java.util.Locale; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.SemanticTags; -import org.openhab.core.semantics.Tag; - -/** - * A DTO representing a Semantic {@link Tag}. - * - * @author Jimmy Tanagra - initial contribution - */ -@NonNullByDefault -public class TagDTO { - String name; - String label; - List synonyms; - - public TagDTO(Class tag, Locale locale) { - this.name = tag.getSimpleName(); - this.label = SemanticTags.getLabel(tag, locale); - this.synonyms = SemanticTags.getSynonyms(tag, locale); - } -} diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagResource.java index be55ca2d26c..b3d954f3d3c 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/tag/TagResource.java @@ -12,14 +12,19 @@ */ package org.openhab.core.io.rest.core.internal.tag; +import java.util.Comparator; import java.util.List; import java.util.Locale; -import java.util.Map; import javax.annotation.security.RolesAllowed; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; @@ -35,10 +40,10 @@ import org.openhab.core.io.rest.LocaleService; import org.openhab.core.io.rest.RESTConstants; import org.openhab.core.io.rest.RESTResource; -import org.openhab.core.semantics.model.equipment.Equipments; -import org.openhab.core.semantics.model.location.Locations; -import org.openhab.core.semantics.model.point.Points; -import org.openhab.core.semantics.model.property.Properties; +import org.openhab.core.semantics.ManagedSemanticTagProvider; +import org.openhab.core.semantics.SemanticTag; +import org.openhab.core.semantics.SemanticTagImpl; +import org.openhab.core.semantics.SemanticTagRegistry; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; @@ -54,11 +59,13 @@ import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; /** * This class acts as a REST resource for retrieving a list of tags. * * @author Jimmy Tanagra - Initial contribution + * @author Laurent Garnier - Extend REST API to allow adding/updating/removing user tags */ @Component @JaxrsResource @@ -74,28 +81,166 @@ public class TagResource implements RESTResource { public static final String PATH_TAGS = "tags"; private final LocaleService localeService; + private final SemanticTagRegistry semanticTagRegistry; + private final ManagedSemanticTagProvider managedSemanticTagProvider; + + // TODO pattern in @Path @Activate - public TagResource(final @Reference LocaleService localeService) { + public TagResource(final @Reference LocaleService localeService, + final @Reference SemanticTagRegistry semanticTagRegistry, + final @Reference ManagedSemanticTagProvider managedSemanticTagProvider) { this.localeService = localeService; + this.semanticTagRegistry = semanticTagRegistry; + this.managedSemanticTagProvider = managedSemanticTagProvider; } @GET @RolesAllowed({ Role.USER, Role.ADMIN }) @Produces(MediaType.APPLICATION_JSON) - @Operation(operationId = "getTags", summary = "Get all available tags.", responses = { - @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = TagDTO.class)))) }) + @Operation(operationId = "getSemanticTags", summary = "Get all available semantic tags.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = EnrichedSemanticTagDTO.class)))) }) public Response getTags(final @Context UriInfo uriInfo, final @Context HttpHeaders httpHeaders, @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) { final Locale locale = localeService.getLocale(language); - Map> tags = Map.of( // - Locations.class.getSimpleName(), Locations.stream().map(tag -> new TagDTO(tag, locale)).toList(), // - Equipments.class.getSimpleName(), Equipments.stream().map(tag -> new TagDTO(tag, locale)).toList(), // - Points.class.getSimpleName(), Points.stream().map(tag -> new TagDTO(tag, locale)).toList(), // - Properties.class.getSimpleName(), Properties.stream().map(tag -> new TagDTO(tag, locale)).toList() // - ); + List tagsDTO = semanticTagRegistry.getAll().stream() + .sorted(Comparator.comparing(SemanticTag::getUID)) + .map(t -> new EnrichedSemanticTagDTO(t.localized(locale), semanticTagRegistry.isEditable(t))).toList(); + return JSONResponse.createResponse(Status.OK, tagsDTO, null); + } + + @GET + @RolesAllowed({ Role.USER, Role.ADMIN }) + @Path("/{tagId}") + @Produces(MediaType.APPLICATION_JSON) + @Operation(operationId = "getSemanticTagAndSubTags", summary = "Gets a semantic tag and its sub tags.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = EnrichedSemanticTagDTO.class)))), + @ApiResponse(responseCode = "404", description = "Semantic tag not found.") }) + public Response getTagAndSubTags( + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("tagId") @Parameter(description = "tag id") String tagId) { + final Locale locale = localeService.getLocale(language); + String uid = tagId.trim(); + + SemanticTag tag = semanticTagRegistry.get(uid); + if (tag != null) { + List tagsDTO = semanticTagRegistry.getSubTree(tag).stream() + .sorted(Comparator.comparing(SemanticTag::getUID)) + .map(t -> new EnrichedSemanticTagDTO(t.localized(locale), semanticTagRegistry.isEditable(t))) + .toList(); + return JSONResponse.createResponse(Status.OK, tagsDTO, null); + } else { + return JSONResponse.createErrorResponse(Status.NOT_FOUND, "Tag " + uid + " does not exist!"); + } + } + + @POST + @RolesAllowed({ Role.ADMIN }) + @Consumes(MediaType.APPLICATION_JSON) + @Operation(operationId = "createSemanticTag", summary = "Creates a new semantic tag and adds it to the registry.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "201", description = "Created", content = @Content(schema = @Schema(implementation = EnrichedSemanticTagDTO.class))), + @ApiResponse(responseCode = "400", description = "The tag identifier is invalid."), + @ApiResponse(responseCode = "409", description = "A tag with the same identifier already exists.") }) + public Response create( + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @Parameter(description = "tag data", required = true) EnrichedSemanticTagDTO data) { + final Locale locale = localeService.getLocale(language); + + if (data.uid == null) { + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "Tag identifier is required!"); + } + + String uid = data.uid.trim(); + + // check if a tag with this UID already exists + SemanticTag tag = semanticTagRegistry.get(uid); + if (tag != null) { + // report a conflict + return JSONResponse.createResponse(Status.CONFLICT, + new EnrichedSemanticTagDTO(tag.localized(locale), semanticTagRegistry.isEditable(tag)), + "Tag " + uid + " already exists!"); + } + + tag = new SemanticTagImpl(uid, data.label, data.description, data.synonyms); + + // Check that a tag with this uid can be added to the registry + if (!semanticTagRegistry.canBeAdded(tag)) { + return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "Invalid tag identifier " + uid); + } + + managedSemanticTagProvider.add(tag); + + return JSONResponse.createResponse(Status.CREATED, + new EnrichedSemanticTagDTO(tag.localized(locale), semanticTagRegistry.isEditable(tag)), null); + } + + @DELETE + @RolesAllowed({ Role.ADMIN }) + @Path("/{tagId}") + @Operation(operationId = "removeSemanticTag", summary = "Removes a semantic tag and its sub tags from the registry.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK, was deleted."), + @ApiResponse(responseCode = "404", description = "Semantic tag not found."), + @ApiResponse(responseCode = "405", description = "Semantic tag not removable.") }) + public Response remove( + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("tagId") @Parameter(description = "tag id") String tagId) { + final Locale locale = localeService.getLocale(language); + + String uid = tagId.trim(); + + // check whether tag exists and throw 404 if not + SemanticTag tag = semanticTagRegistry.get(uid); + if (tag == null) { + return JSONResponse.createErrorResponse(Status.NOT_FOUND, "Tag " + uid + " does not exist!"); + } + + // Check that tag is removable, 405 otherwise + if (!semanticTagRegistry.isEditable(tag)) { + return JSONResponse.createErrorResponse(Status.METHOD_NOT_ALLOWED, "Tag " + uid + " is not removable."); + } + + semanticTagRegistry.removeSubTree(tag); + + return Response.ok(null, MediaType.TEXT_PLAIN).build(); + } + + @PUT + @RolesAllowed({ Role.ADMIN }) + @Path("/{tagId}") + @Consumes(MediaType.APPLICATION_JSON) + @Operation(operationId = "updateSemanticTag", summary = "Updates a semantic tag.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = EnrichedSemanticTagDTO.class))), + @ApiResponse(responseCode = "404", description = "Semantic tag not found."), + @ApiResponse(responseCode = "405", description = "Semantic tag not editable.") }) + public Response update( + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("tagId") @Parameter(description = "tag id") String tagId, + @Parameter(description = "tag data", required = true) EnrichedSemanticTagDTO data) { + final Locale locale = localeService.getLocale(language); + + String uid = tagId.trim(); + + // check whether tag exists and throw 404 if not + SemanticTag tag = semanticTagRegistry.get(uid); + if (tag == null) { + return JSONResponse.createErrorResponse(Status.NOT_FOUND, "Tag " + uid + " does not exist!"); + } + + // Check that tag is editable, 405 otherwise + if (!semanticTagRegistry.isEditable(tag)) { + return JSONResponse.createErrorResponse(Status.METHOD_NOT_ALLOWED, "Tag " + uid + " is not editable."); + } + + tag = new SemanticTagImpl(uid, data.label != null ? data.label : tag.getLabel(), + data.description != null ? data.description : tag.getDescription(), + data.synonyms != null ? data.synonyms : tag.getSynonyms()); + managedSemanticTagProvider.update(tag); - return JSONResponse.createResponse(Status.OK, tags, null); + return JSONResponse.createResponse(Status.OK, + new EnrichedSemanticTagDTO(tag.localized(locale), semanticTagRegistry.isEditable(tag)), null); } } diff --git a/bundles/org.openhab.core.model.script/src.moved/test/java/org/openhab/core/model/script/actions/SemanticsTest.java b/bundles/org.openhab.core.model.script/src.moved/test/java/org/openhab/core/model/script/actions/SemanticsTest.java index cfd575d6fa6..be07ec03be5 100644 --- a/bundles/org.openhab.core.model.script/src.moved/test/java/org/openhab/core/model/script/actions/SemanticsTest.java +++ b/bundles/org.openhab.core.model.script/src.moved/test/java/org/openhab/core/model/script/actions/SemanticsTest.java @@ -12,12 +12,13 @@ */ package org.openhab.core.model.script.actions; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.when; +import java.util.List; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -33,10 +34,10 @@ import org.openhab.core.items.ItemRegistry; import org.openhab.core.library.CoreItemFactory; import org.openhab.core.model.script.internal.engine.action.SemanticsActionService; -import org.openhab.core.semantics.model.equipment.Battery; -import org.openhab.core.semantics.model.equipment.CleaningRobot; -import org.openhab.core.semantics.model.location.Bathroom; -import org.openhab.core.semantics.model.location.Indoor; +import org.openhab.core.semantics.ManagedSemanticTagProvider; +import org.openhab.core.semantics.Tag; +import org.openhab.core.semantics.internal.SemanticTagRegistryImpl; +import org.openhab.core.semantics.model.DefaultSemanticTagProvider; /** * This are tests for {@link Semantics} actions. @@ -50,6 +51,7 @@ public class SemanticsTest { private @Mock @NonNullByDefault({}) ItemRegistry itemRegistryMock; private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock; + private @Mock @NonNullByDefault({}) ManagedSemanticTagProvider managedSemanticTagProviderMock; private @NonNullByDefault({}) GroupItem indoorLocationItem; private @NonNullByDefault({}) GroupItem bathroomLocationItem; @@ -58,6 +60,11 @@ public class SemanticsTest { private @NonNullByDefault({}) GenericItem humidityPointItem; private @NonNullByDefault({}) GenericItem subEquipmentItem; + private @NonNullByDefault({}) Class indoorTagClass; + private @NonNullByDefault({}) Class bathroomTagClass; + private @NonNullByDefault({}) Class cleaningRobotTagClass; + private @NonNullByDefault({}) Class batteryTagClass; + @BeforeEach public void setup() throws ItemNotFoundException { CoreItemFactory itemFactory = new CoreItemFactory(unitProviderMock); @@ -98,6 +105,15 @@ public void setup() throws ItemNotFoundException { equipmentItem.addMember(subEquipmentItem); subEquipmentItem.addGroupName(equipmentItem.getName()); + when(managedSemanticTagProviderMock.getAll()).thenReturn(List.of()); + SemanticTagRegistryImpl semanticTagRegistryImpl = new SemanticTagRegistryImpl(new DefaultSemanticTagProvider(), + managedSemanticTagProviderMock); + + indoorTagClass = semanticTagRegistryImpl.getTagClassById("Location_Indoor"); + bathroomTagClass = semanticTagRegistryImpl.getTagClassById("Location_Indoor_Room_Bathroom"); + cleaningRobotTagClass = semanticTagRegistryImpl.getTagClassById("Equipment_CleaningRobot"); + batteryTagClass = semanticTagRegistryImpl.getTagClassById("Equipment_Battery"); + when(itemRegistryMock.getItem("TestHouse")).thenReturn(indoorLocationItem); when(itemRegistryMock.getItem("TestBathRoom")).thenReturn(bathroomLocationItem); when(itemRegistryMock.getItem("Test08")).thenReturn(equipmentItem); @@ -122,9 +138,9 @@ public void testGetLocation() { @Test public void testGetLocationType() { - assertThat(Semantics.getLocationType(indoorLocationItem), is(Indoor.class)); + assertThat(Semantics.getLocationType(indoorLocationItem), is(indoorTagClass)); - assertThat(Semantics.getLocationType(bathroomLocationItem), is(Bathroom.class)); + assertThat(Semantics.getLocationType(bathroomLocationItem), is(bathroomTagClass)); assertNull(Semantics.getLocationType(humidityPointItem)); } @@ -142,11 +158,11 @@ public void testGetEquipment() { @Test public void testGetEquipmentType() { - assertThat(Semantics.getEquipmentType(equipmentItem), is(CleaningRobot.class)); + assertThat(Semantics.getEquipmentType(equipmentItem), is(cleaningRobotTagClass)); - assertThat(Semantics.getEquipmentType(temperaturePointItem), is(CleaningRobot.class)); + assertThat(Semantics.getEquipmentType(temperaturePointItem), is(cleaningRobotTagClass)); - assertThat(Semantics.getEquipmentType(subEquipmentItem), is(Battery.class)); + assertThat(Semantics.getEquipmentType(subEquipmentItem), is(batteryTagClass)); assertNull(Semantics.getEquipmentType(humidityPointItem)); } diff --git a/bundles/org.openhab.core.semantics/model/generateTagClasses.groovy b/bundles/org.openhab.core.semantics/model/generateTagClasses.groovy index 20f9cb58e79..0012ff6be0b 100755 --- a/bundles/org.openhab.core.semantics/model/generateTagClasses.groovy +++ b/bundles/org.openhab.core.semantics/model/generateTagClasses.groovy @@ -22,10 +22,6 @@ baseDir = Paths.get(getClass().protectionDomain.codeSource.location.toURI()).get header = header() def tagSets = new TreeMap() -def locations = new TreeSet() -def equipments = new TreeSet() -def points = new TreeSet() -def properties = new TreeSet() def labelsFile = new FileWriter("${baseDir}/src/main/resources/tags.properties") labelsFile.write("# Generated content - do not edit!\n") @@ -36,63 +32,26 @@ for (line in parseCsv(new FileReader("${baseDir}/model/SemanticTags.csv"), separ def tagSet = (line.Parent ? tagSets.get(line.Parent) : line.Type) + "_" + line.Tag tagSets.put(line.Tag,tagSet) - createTagSetClass(line, tagSet) appendLabelsFile(labelsFile, line, tagSet) switch(line.Type) { - case "Location" : locations.add(line.Tag); break; - case "Equipment" : equipments.add(line.Tag); break; - case "Point" : points.add(line.Tag); break; - case "Property" : properties.add(line.Tag); break; + case "Location" : break; + case "Equipment" : break; + case "Point" : break; + case "Property" : break; default : println "Unrecognized type " + line.Type } } labelsFile.close() -createLocationsFile(locations) -createEquipmentsFile(equipments) -createPointsFile(points) -createPropertiesFile(properties) +createDefaultProviderFile(tagSets) println "\n\nTagSets:" for (String tagSet : tagSets) { println tagSet } -def createTagSetClass(def line, String tagSet) { - def tag = line.Tag - def type = line.Type - def label = line.Label - def synonyms = line.Synonyms - def desc = line.Description - def parent = line.Parent - def parentClass = parent ? parent : type - def pkg = type.toLowerCase() - def ch = label.toLowerCase().charAt(0) - def article = ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u' ? "an" : "a" - def file = new FileWriter("${baseDir}/src/main/java/org/openhab/core/semantics/model/${pkg}/${tag}.java") - file.write(header) - file.write("package org.openhab.core.semantics.model." + pkg + ";\n\n") - file.write("import org.eclipse.jdt.annotation.NonNullByDefault;\n") - if (!parent) { - file.write("import org.openhab.core.semantics." + type + ";\n") - } - file.write("""import org.openhab.core.semantics.TagInfo; - -/** - * This class defines ${article} ${label}. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "${tagSet}", label = "${label}", synonyms = "${synonyms}", description = "${desc}") -public interface ${tag} extends ${parentClass} { -} -""") - file.close() -} - def appendLabelsFile(FileWriter file, def line, String tagSet) { file.write(tagSet + "=" + line.Label) if (line.Synonyms) { @@ -101,168 +60,59 @@ def appendLabelsFile(FileWriter file, def line, String tagSet) { file.write("\n") } -def createLocationsFile(Set locations) { - def file = new FileWriter("${baseDir}/src/main/java/org/openhab/core/semantics/model/location/Locations.java") +def createDefaultProviderFile(def tagSets) { + def file = new FileWriter("${baseDir}/src/main/java/org/openhab/core/semantics/model/DefaultSemanticTagProvider.java") file.write(header) - file.write("""package org.openhab.core.semantics.model.location; + file.write("""package org.openhab.core.semantics.model; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Stream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Location; +import org.openhab.core.common.registry.ProviderChangeListener; +import org.openhab.core.semantics.SemanticTag; +import org.openhab.core.semantics.SemanticTagImpl; +import org.openhab.core.semantics.SemanticTagProvider; +import org.osgi.service.component.annotations.Component; /** - * This class provides a stream of all defined locations. + * This class defines a provider of all default semantic tags. * * @author Generated from generateTagClasses.groovy - Initial contribution */ @NonNullByDefault -public class Locations { +@Component(immediate = true, service = { SemanticTagProvider.class, DefaultSemanticTagProvider.class }) +public class DefaultSemanticTagProvider implements SemanticTagProvider { - static final Set> LOCATIONS = new HashSet<>(); + private List defaultTags; - static { - LOCATIONS.add(Location.class); + public DefaultSemanticTagProvider() { + this.defaultTags = new ArrayList<>(); + defaultTags.add(new SemanticTagImpl("Equipment", "", "", "")); + defaultTags.add(new SemanticTagImpl("Location", "", "", "")); + defaultTags.add(new SemanticTagImpl("Point", "", "", "")); + defaultTags.add(new SemanticTagImpl("Property", "", "", "")); """) - for (String location : locations) { - file.write(" LOCATIONS.add(${location}.class);\n") - } - file.write(""" } - - public static Stream> stream() { - return LOCATIONS.stream(); - } - - public static boolean add(Class tag) { - return LOCATIONS.add(tag); - } -} -""") - file.close() -} - -def createEquipmentsFile(Set equipments) { - def file = new FileWriter("${baseDir}/src/main/java/org/openhab/core/semantics/model/equipment/Equipments.java") - file.write(header) - file.write("""package org.openhab.core.semantics.model.equipment; - -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Stream; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; - -/** - * This class provides a stream of all defined equipments. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -public class Equipments { - - static final Set> EQUIPMENTS = new HashSet<>(); - - static { - EQUIPMENTS.add(Equipment.class); -""") - for (String equipment : equipments) { - file.write(" EQUIPMENTS.add(${equipment}.class);\n") - } - file.write(""" } - - public static Stream> stream() { - return EQUIPMENTS.stream(); - } - - public static boolean add(Class tag) { - return EQUIPMENTS.add(tag); - } -} + for (line in parseCsv(new FileReader("${baseDir}/model/SemanticTags.csv"), separator: ',')) { + def tagId = (line.Parent ? tagSets.get(line.Parent) : line.Type) + "_" + line.Tag + file.write(""" defaultTags.add(new SemanticTagImpl("${tagId}", // + "${line.Label}", "${line.Description}", "${line.Synonyms}")); """) - file.close() -} - -def createPointsFile(Set points) { - def file = new FileWriter("${baseDir}/src/main/java/org/openhab/core/semantics/model/point/Points.java") - file.write(header) - file.write("""package org.openhab.core.semantics.model.point; - -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Stream; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Point; - -/** - * This class provides a stream of all defined points. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -public class Points { - - static final Set> POINTS = new HashSet<>(); - - static { - POINTS.add(Point.class); -""") - for (String point : points) { - file.write(" POINTS.add(${point}.class);\n") } file.write(""" } - public static Stream> stream() { - return POINTS.stream(); - } - - public static boolean add(Class tag) { - return POINTS.add(tag); + @Override + public Collection getAll() { + return defaultTags; } -} -""") - file.close() -} - -def createPropertiesFile(Set properties) { - def file = new FileWriter("${baseDir}/src/main/java/org/openhab/core/semantics/model/property/Properties.java") - file.write(header) - file.write("""package org.openhab.core.semantics.model.property; - -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Stream; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; - -/** - * This class provides a stream of all defined properties. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -public class Properties { - - static final Set> PROPERTIES = new HashSet<>(); - - static { - PROPERTIES.add(Property.class); -""") - for (String property : properties) { - file.write(" PROPERTIES.add(${property}.class);\n") - } - file.write(""" } - public static Stream> stream() { - return PROPERTIES.stream(); + @Override + public void addProviderChangeListener(ProviderChangeListener listener) { } - public static boolean add(Class tag) { - return PROPERTIES.add(tag); + @Override + public void removeProviderChangeListener(ProviderChangeListener listener) { } } """) diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Equipment.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Equipment.java index f3de8702dc0..096093cfa31 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Equipment.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Equipment.java @@ -22,7 +22,6 @@ * @author Kai Kreuzer - Initial contribution */ @NonNullByDefault -@TagInfo(id = "Equipment") public interface Equipment extends Tag { @Nullable diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Location.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Location.java index cc4497205e1..cc5a890dfb3 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Location.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Location.java @@ -22,7 +22,6 @@ * @author Kai Kreuzer - Initial contribution */ @NonNullByDefault -@TagInfo(id = "Location") public interface Location extends Tag { static String name() { diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/ManagedSemanticTagProvider.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/ManagedSemanticTagProvider.java new file mode 100644 index 00000000000..fbb7be82559 --- /dev/null +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/ManagedSemanticTagProvider.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.semantics; + +import java.util.Collection; +import java.util.Comparator; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.common.registry.AbstractManagedProvider; +import org.openhab.core.semantics.dto.SemanticTagDTO; +import org.openhab.core.semantics.dto.SemanticTagDTOMapper; +import org.openhab.core.storage.StorageService; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * {@link ManagedSemanticTagProvider} is an OSGi service, that allows to add or remove + * semantic tags at runtime by calling {@link ManagedSemanticTagProvider#add(SemanticTag)} + * or {@link ManagedSemanticTagProvider#remove(String)}. + * An added semantic tag is automatically exposed to the {@link SemanticTagRegistry}. + * Persistence of added semantic tags is handled by a {@link StorageService}. + * + * @author Laurent Garnier - Initial contribution + */ +@NonNullByDefault +@Component(immediate = true, service = { SemanticTagProvider.class, ManagedSemanticTagProvider.class }) +public class ManagedSemanticTagProvider extends AbstractManagedProvider + implements SemanticTagProvider { + + @Activate + public ManagedSemanticTagProvider(final @Reference StorageService storageService) { + super(storageService); + } + + @Override + protected String getStorageName() { + return SemanticTag.class.getName(); + } + + @Override + protected String keyToString(String key) { + return key; + } + + @Override + public Collection getAll() { + // Sort tags by uid to be sure that tag classes will be created in the right order + return super.getAll().stream().sorted(Comparator.comparing(SemanticTag::getUID)).toList(); + } + + @Override + protected @Nullable SemanticTag toElement(String uid, SemanticTagDTO persistedTag) { + return SemanticTagDTOMapper.map(persistedTag); + } + + @Override + protected SemanticTagDTO toPersistableElement(SemanticTag tag) { + return SemanticTagDTOMapper.map(tag); + } +} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Point.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Point.java index a707ae9976d..8971feb32ce 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Point.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Point.java @@ -22,7 +22,6 @@ * @author Kai Kreuzer - Initial contribution */ @NonNullByDefault -@TagInfo(id = "Point") public interface Point extends Tag { @Nullable diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Property.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Property.java index b2a003023a7..d119925c14d 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Property.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/Property.java @@ -20,6 +20,5 @@ * @author Kai Kreuzer - Initial contribution */ @NonNullByDefault -@TagInfo(id = "Property") public interface Property extends Tag { } diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTag.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTag.java new file mode 100644 index 00000000000..d1782911521 --- /dev/null +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTag.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.semantics; + +import java.util.List; +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.common.registry.Identifiable; + +/** + * This interface defines the core features of an openHAB semantic tag. + * + * @author Laurent Garnier - Initial contribution + */ +@NonNullByDefault +public interface SemanticTag extends Identifiable { + + /** + * Returns the name of the semantic tag. + * + * @return the name of the semantic tag + */ + String getName(); + + /** + * Returns the UID of the parent tag. + * + * @return the UID of the parent tag + */ + String getParentUID(); + + /** + * Returns the label of the semantic tag. + * + * @return semantic tag label or an empty string if undefined + */ + String getLabel(); + + /** + * Returns the description of the semantic tag. + * + * @return semantic tag description or an empty string if undefined + */ + String getDescription(); + + /** + * Returns the synonyms of the semantic tag. + * + * @return semantic tag synonyms as a List + */ + List getSynonyms(); + + /** + * Returns the localized semantic tag. + * + * @param locale the locale to be used + * @return the localized semantic tag + */ + SemanticTag localized(Locale locale); +} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTagImpl.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTagImpl.java new file mode 100644 index 00000000000..37c7cd0381d --- /dev/null +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTagImpl.java @@ -0,0 +1,133 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.semantics; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.ResourceBundle.Control; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * This is the main implementing class of the {@link SemanticTag} interface. + * + * @author Laurent Garnier - Initial contribution + */ +@NonNullByDefault +public class SemanticTagImpl implements SemanticTag { + + private static final String TAGS_BUNDLE_NAME = "tags"; + + private String uid; + private String name; + private String parent; + private String label; + private String description; + private List synonyms; + + public SemanticTagImpl(String uid, @Nullable String label, @Nullable String description, + @Nullable List synonyms) { + this(uid, label, description); + if (synonyms != null) { + this.synonyms = new ArrayList<>(); + for (String synonym : synonyms) { + this.synonyms.add(synonym.trim()); + } + } + } + + public SemanticTagImpl(String uid, @Nullable String label, @Nullable String description, + @Nullable String synonyms) { + this(uid, label, description); + if (synonyms != null && !synonyms.isBlank()) { + this.synonyms = new ArrayList<>(); + for (String synonym : synonyms.split(",")) { + this.synonyms.add(synonym.trim()); + } + } + } + + private SemanticTagImpl(String uid, @Nullable String label, @Nullable String description) { + this.uid = uid; + int idx = uid.lastIndexOf("_"); + if (idx < 0) { + this.name = uid.trim(); + this.parent = ""; + } else { + this.name = uid.substring(idx + 1).trim(); + this.parent = uid.substring(0, idx).trim(); + } + this.label = label == null ? "" : label.trim(); + this.description = description == null ? "" : description.trim(); + this.synonyms = List.of(); + } + + @Override + public String getUID() { + return uid; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getParentUID() { + return parent; + } + + @Override + public String getLabel() { + return label; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public List getSynonyms() { + return synonyms; + } + + @Override + public SemanticTag localized(Locale locale) { + ResourceBundle rb = ResourceBundle.getBundle(TAGS_BUNDLE_NAME, locale, + Control.getNoFallbackControl(Control.FORMAT_PROPERTIES)); + String label; + List synonyms; + try { + String entry = rb.getString(uid); + int idx = entry.indexOf(","); + if (idx >= 0) { + label = entry.substring(0, idx); + String synonymsCsv = entry.substring(idx + 1); + synonyms = synonymsCsv.isBlank() ? null : List.of(synonymsCsv.split(",")); + } else { + label = entry; + synonyms = null; + } + } catch (MissingResourceException e) { + label = getLabel(); + synonyms = getSynonyms(); + } + + return new SemanticTagImpl(uid, label, getDescription(), synonyms); + } +} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Tilt.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTagProvider.java similarity index 59% rename from bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Tilt.java rename to bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTagProvider.java index 23a86709233..3d4c0a5a14f 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Tilt.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTagProvider.java @@ -10,17 +10,17 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.semantics.model.point; +package org.openhab.core.semantics; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; +import org.openhab.core.common.registry.Provider; /** - * This class defines a Tilt. + * The {@link SemanticTagProvider} is responsible for providing semantic tags. * - * @author Generated from generateTagClasses.groovy - Initial contribution + * @author Laurent Garnier - Initial contribution */ @NonNullByDefault -@TagInfo(id = "Point_Status_Tilt", label = "Tilt", synonyms = "", description = "") -public interface Tilt extends Status { +public interface SemanticTagProvider extends Provider { + } diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTagRegistry.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTagRegistry.java new file mode 100644 index 00000000000..7c84ab65c2c --- /dev/null +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTagRegistry.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.semantics; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.common.registry.Registry; + +/** + * {@link SemanticTagRegistry} tracks all {@link SemanticTag}s from different {@link SemanticTagProvider}s + * and provides access to them. + * + * @author Laurent Garnier - Initial contribution + */ +@NonNullByDefault +public interface SemanticTagRegistry extends Registry { + + /** + * Retrieves the class for a given id. + * + * @param tagId the id of the tag. The id can be fully qualified (e.g. "Location_Room_Bedroom") or a segment, if + * this uniquely identifies the tag + * (e.g. "Bedroom"). + * @return the class for the id or null, if non exists. + */ + @Nullable + Class getTagClassById(String tagId); + + /** + * Checks if a tag with a given id can be added to the registry. + * + * To be added, no tag with this id must already exist in the registry, the tag name extracted from this id + * must have a valid syntax, the parent tag extracted from this id must already exists in the registry and + * should be either a default semantic tag or a managed semantic tag, and no tag with a same name must already + * exist in the registry. + * + * @param tag a tag to be added to the registry + * @return true if the tag can be added, false if not + */ + boolean canBeAdded(SemanticTag tag); + + /** + * Returns the provided tag + all tags having the provided tag as ancestor. + * + * @param tag a tag in the registry + * @return a list of all tags having the provided tag as ancestor, including the provided tag itself + */ + List getSubTree(SemanticTag tag); + + /** + * Indicates if a tag is editable. + * + * To be editable, a tag must be managed. + * + * @param tag a tag in the registry + * @return true if the provided tag is editable, false if not + */ + boolean isEditable(SemanticTag tag); + + /** + * Removes the provided tag and all tags having the provided tag as ancestor. + * + * Only removable (managed) tags are removed. + * + * @param tag a tag in the registry + */ + void removeSubTree(SemanticTag tag); +} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTags.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTags.java index 58982dab6ce..fecebc542ab 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTags.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticTags.java @@ -12,32 +12,15 @@ */ package org.openhab.core.semantics; -import java.util.List; -import java.util.Locale; +import java.util.Collections; import java.util.Map; -import java.util.MissingResourceException; -import java.util.Optional; -import java.util.ResourceBundle; -import java.util.ResourceBundle.Control; import java.util.Set; import java.util.TreeMap; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Opcodes; import org.openhab.core.items.Item; -import org.openhab.core.semantics.model.equipment.Equipments; -import org.openhab.core.semantics.model.location.Locations; -import org.openhab.core.semantics.model.point.Measurement; -import org.openhab.core.semantics.model.point.Points; -import org.openhab.core.semantics.model.property.Properties; import org.openhab.core.types.StateDescription; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * This is a class that gives static access to the semantic tag library. @@ -45,22 +28,18 @@ * * @author Kai Kreuzer - Initial contribution * @author Jimmy Tanagra - Add the ability to add new tags at runtime + * @author Laurent Garnier - Several methods moved into class SemanticsService or SemanticTagRegistry */ @NonNullByDefault public class SemanticTags { - private static final String TAGS_BUNDLE_NAME = "tags"; - - private static final Map> TAGS = new TreeMap<>(); - - private static final Logger LOGGER = LoggerFactory.getLogger(SemanticTags.class); - private static final SemanticClassLoader CLASS_LOADER = new SemanticClassLoader(); + private static final Map> TAGS = Collections.synchronizedMap(new TreeMap<>()); static { - Locations.stream().forEach(location -> addTagSet(location)); - Equipments.stream().forEach(equipment -> addTagSet(equipment)); - Points.stream().forEach(point -> addTagSet(point)); - Properties.stream().forEach(property -> addTagSet(property)); + addTagSet("Location", Location.class); + addTagSet("Equipment", Equipment.class); + addTagSet("Point", Point.class); + addTagSet("Property", Property.class); } /** @@ -75,69 +54,6 @@ public class SemanticTags { return TAGS.get(tagId); } - public static @Nullable Class getByLabel(String tagLabel, Locale locale) { - Optional> tag = TAGS.values().stream().distinct() - .filter(t -> getLabel(t, locale).equalsIgnoreCase(tagLabel)).findFirst(); - return tag.isPresent() ? tag.get() : null; - } - - public static List> getByLabelOrSynonym(String tagLabelOrSynonym, Locale locale) { - return TAGS.values().stream().distinct() - .filter(t -> getLabelAndSynonyms(t, locale).contains(tagLabelOrSynonym.toLowerCase(locale))) - .collect(Collectors.toList()); - } - - public static List getLabelAndSynonyms(Class tag, Locale locale) { - ResourceBundle rb = ResourceBundle.getBundle(TAGS_BUNDLE_NAME, locale, - Control.getNoFallbackControl(Control.FORMAT_PROPERTIES)); - TagInfo tagInfo = tag.getAnnotation(TagInfo.class); - try { - String entry = rb.getString(tagInfo.id()); - return List.of(entry.toLowerCase(locale).split(",")); - } catch (MissingResourceException e) { - Stream label = Stream.of(tagInfo.label()); - Stream synonyms = Stream.of(tagInfo.synonyms().split(",")).map(String::trim); - return Stream.concat(label, synonyms).map(s -> s.toLowerCase(locale)).distinct().toList(); - } - } - - public static String getLabel(Class tag, Locale locale) { - ResourceBundle rb = ResourceBundle.getBundle(TAGS_BUNDLE_NAME, locale, - Control.getNoFallbackControl(Control.FORMAT_PROPERTIES)); - TagInfo tagInfo = tag.getAnnotation(TagInfo.class); - try { - String entry = rb.getString(tagInfo.id()); - if (entry.contains(",")) { - return entry.substring(0, entry.indexOf(",")); - } else { - return entry; - } - } catch (MissingResourceException e) { - return tagInfo.label(); - } - } - - public static List getSynonyms(Class tag, Locale locale) { - ResourceBundle rb = ResourceBundle.getBundle(TAGS_BUNDLE_NAME, locale, - Control.getNoFallbackControl(Control.FORMAT_PROPERTIES)); - String synonyms = ""; - TagInfo tagInfo = tag.getAnnotation(TagInfo.class); - try { - String entry = rb.getString(tagInfo.id()); - int start = entry.indexOf(",") + 1; - if (start > 0 && entry.length() > start) { - synonyms = entry.substring(start); - } - } catch (MissingResourceException e) { - synonyms = tagInfo.synonyms(); - } - return Stream.of(synonyms.split(",")).map(String::trim).toList(); - } - - public static String getDescription(Class tag, Locale locale) { - return tag.getAnnotation(TagInfo.class).description(); - } - /** * Determines the semantic type of an {@link Item} i.e. a sub-type of {@link Location}, {@link Equipment} or * {@link Point}. @@ -156,9 +72,9 @@ public static String getDescription(Class tag, Locale locale) { if (getProperty(item) != null) { StateDescription stateDescription = item.getStateDescription(); if (stateDescription != null && stateDescription.isReadOnly()) { - return Measurement.class; + return getById("Point_Measurement"); } else { - return org.openhab.core.semantics.model.point.Control.class; + return getById("Point_Control"); } } else { return null; @@ -234,147 +150,11 @@ public static String getDescription(Class tag, Locale locale) { return null; } - /** - * Adds a new semantic tag with inferred label, empty synonyms and description. - * - * The label will be inferred from the tag name by splitting the CamelCase with a space. - * - * @param name the tag name to add - * @param parent the parent tag that the new tag should belong to - * @return the created semantic tag class, or null if it was already added. - */ - public static @Nullable Class add(String name, String parent) { - return add(name, parent, null, null, null); - } - - /** - * Adds a new semantic tag. - * - * @param name the tag name to add - * @param parent the parent tag that the new tag should belong to - * @param label an optional label. When null, the label will be inferred from the tag name, - * splitting the CamelCase with a space. - * @param synonyms a comma separated list of synonyms - * @param description the tag description - * @return the created semantic tag class, or null if it was already added. - */ - public static @Nullable Class add(String name, String parent, @Nullable String label, - @Nullable String synonyms, @Nullable String description) { - Class parentClass = getById(parent); - if (parentClass == null) { - LOGGER.warn("Adding semantic tag '{}' failed because parent tag '{}' is not found.", name, parent); - return null; - } - return add(name, parentClass, label, synonyms, description); - } - - /** - * Adds a new semantic tag with inferred label, empty synonyms and description. - * - * The label will be inferred from the tag name by splitting the CamelCase with a space. - * - * @param name the tag name to add - * @param parent the parent tag that the new tag should belong to - * @return the created semantic tag class, or null if it was already added. - */ - public static @Nullable Class add(String name, Class parent) { - return add(name, parent, null, null, null); - } - - /** - * Adds a new semantic tag. - * - * @param name the tag name to add - * @param parent the parent tag that the new tag should belong to - * @param label an optional label. When null, the label will be inferred from the tag name, - * splitting the CamelCase with a space. - * @param synonyms a comma separated list of synonyms - * @param description the tag description - * @return the created semantic tag class, or null if it was already added. - */ - public static @Nullable Class add(String name, Class parent, @Nullable String label, - @Nullable String synonyms, @Nullable String description) { - if (getById(name) != null) { - return null; - } - - if (!name.matches("[A-Z][a-zA-Z0-9]+")) { - throw new IllegalArgumentException( - "The tag name '" + name + "' must start with a capital letter and contain only alphanumerics."); - } - - String parentId = parent.getAnnotation(TagInfo.class).id(); - String type = parentId.split("_")[0]; - String className = "org.openhab.core.semantics.model." + type.toLowerCase() + "." + name; - - // Infer label from name, splitting up CamelCaseALL99 -> Camel Case ALL 99 - label = Optional.ofNullable(label).orElseGet(() -> name.replaceAll("([A-Z][a-z]+|[A-Z][A-Z]+|[0-9]+)", " $1")) - .trim(); - synonyms = Optional.ofNullable(synonyms).orElse("").replaceAll("\\s*,\\s*", ",").trim(); - - // Create the tag interface - ClassWriter classWriter = new ClassWriter(0); - classWriter.visit(Opcodes.V11, Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT + Opcodes.ACC_INTERFACE, - className.replace('.', '/'), null, "java/lang/Object", - new String[] { parent.getName().replace('.', '/') }); - - // Add TagInfo Annotation - classWriter.visitSource("Status.java", null); - - AnnotationVisitor annotation = classWriter.visitAnnotation("Lorg/openhab/core/semantics/TagInfo;", true); - annotation.visit("id", parentId + "_" + name); - annotation.visit("label", label); - annotation.visit("synonyms", synonyms); - annotation.visit("description", Optional.ofNullable(description).orElse("").trim()); - annotation.visitEnd(); - - classWriter.visitEnd(); - byte[] byteCode = classWriter.toByteArray(); - Class newTag = null; - try { - newTag = CLASS_LOADER.defineClass(className, byteCode); - } catch (Exception e) { - LOGGER.warn("Failed creating a new semantic tag '{}': {}", className, e.getMessage()); - return null; - } - addToModel(newTag); - addTagSet(newTag); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("'{}' semantic {} tag added.", className, type); - } - return newTag; - } - - private static void addTagSet(Class tagSet) { - String id = tagSet.getAnnotation(TagInfo.class).id(); - while (id.indexOf("_") != -1) { - TAGS.put(id, tagSet); - id = id.substring(id.indexOf("_") + 1); - } + public static void addTagSet(String id, Class tagSet) { TAGS.put(id, tagSet); } - private static boolean addToModel(Class tag) { - if (Location.class.isAssignableFrom(tag)) { - return Locations.add((Class) tag); - } else if (Equipment.class.isAssignableFrom(tag)) { - return Equipments.add((Class) tag); - } else if (Point.class.isAssignableFrom(tag)) { - return Points.add((Class) tag); - } else if (Property.class.isAssignableFrom(tag)) { - return Properties.add((Class) tag); - } - throw new IllegalArgumentException("Unknown type of tag " + tag); - } - - private static class SemanticClassLoader extends ClassLoader { - public SemanticClassLoader() { - super(SemanticTags.class.getClassLoader()); - } - - public Class defineClass(String className, byte[] byteCode) { - // defineClass is protected in the normal ClassLoader - return defineClass(className, byteCode, 0, byteCode.length); - } + public static void removeTagSet(String id, Class tagSet) { + TAGS.remove(id, tagSet); } } diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticsService.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticsService.java index 7e321aaaf78..df36cc33cd2 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticsService.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/SemanticsService.java @@ -12,16 +12,19 @@ */ package org.openhab.core.semantics; +import java.util.List; import java.util.Locale; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.items.Item; /** * This interface defines a service, which offers functionality regarding semantic tags. * * @author Kai Kreuzer - Initial contribution + * @author Laurent Garnier - Few methods moved from class SemanticTags in order to use the semantic tag registry */ @NonNullByDefault public interface SemanticsService { @@ -45,4 +48,34 @@ public interface SemanticsService { * @return as set of items that are located in the given location(s) */ Set getItemsInLocation(String labelOrSynonym, Locale locale); + + /** + * Retrieves the first semantic tag having label matching the given parameter. + * Case is ignored. + * + * @param tagLabel the searched label + * @param locale the locale to be considered + * @return the tag class of the first matching semantic tag or null if no matching found + */ + @Nullable + Class getByLabel(String tagLabel, Locale locale); + + /** + * Retrieves all semantic tags having label or a synonym matching the given parameter. + * Case is ignored. + * + * @param tagLabelOrSynonym the searched label or synonym + * @param locale the locale to be considered + * @return the List of tag classes of all matching semantic tags + */ + List> getByLabelOrSynonym(String tagLabelOrSynonym, Locale locale); + + /** + * Gets the label and all synonyms of a semantic tag using the given locale. + * + * @param tagClass the tag class + * @param locale the locale to be considered + * @return the list containing the label and all synonyms of a semantic tag + */ + List getLabelAndSynonyms(Class tagClass, Locale locale); } diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/TagInfo.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/TagInfo.java deleted file mode 100644 index 89b53e7e607..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/TagInfo.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics; - -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * This is an annotation to be used on semantic tag classes for providing their ids, labels and descriptions. - * - * @author Kai Kreuzer - Initial contribution - */ -@Retention(RUNTIME) -@Target(TYPE) -public @interface TagInfo { - - String id(); - - String label() default ""; - - String synonyms() default ""; - - String description() default ""; -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/dto/SemanticTagDTO.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/dto/SemanticTagDTO.java new file mode 100644 index 00000000000..db00e10f968 --- /dev/null +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/dto/SemanticTagDTO.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.semantics.dto; + +import java.util.List; + +/** + * This is a data transfer object that is used to serialize semantic tags. + * + * @author Laurent Garnier - Initial contribution + */ +public class SemanticTagDTO { + + public String uid; + public String label; + public String description; + public List synonyms; + + public SemanticTagDTO() { + } +} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/dto/SemanticTagDTOMapper.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/dto/SemanticTagDTOMapper.java new file mode 100644 index 00000000000..0987bb6e1d4 --- /dev/null +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/dto/SemanticTagDTOMapper.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.semantics.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.semantics.SemanticTag; +import org.openhab.core.semantics.SemanticTagImpl; + +/** + * The {@link SemanticTagDTOMapper} is an utility class to map semantic tags into + * semantic tag data transfer objects (DTOs). + * + * @author Laurent Garnier - Initial contribution + */ +@NonNullByDefault +public class SemanticTagDTOMapper { + + /** + * Maps semantic tag DTO into semantic tag object. + * + * @param tagDTO the DTO + * @return the semantic tag object + */ + public static @Nullable SemanticTag map(@Nullable SemanticTagDTO tagDTO) { + if (tagDTO == null) { + throw new IllegalArgumentException("The argument 'tagDTO' must not be null."); + } + if (tagDTO.uid == null) { + throw new IllegalArgumentException("The argument 'tagDTO.uid' must not be null."); + } + + return new SemanticTagImpl(tagDTO.uid, tagDTO.label, tagDTO.description, tagDTO.synonyms); + } + + /** + * Maps semantic tag object into semantic tag DTO. + * + * @param tag the semantic tag + * @return the semantic tag DTO + */ + public static SemanticTagDTO map(SemanticTag tag) { + SemanticTagDTO tagDTO = new SemanticTagDTO(); + tagDTO.uid = tag.getUID(); + tagDTO.label = tag.getLabel(); + tagDTO.description = tag.getDescription(); + tagDTO.synonyms = tag.getSynonyms(); + return tagDTO; + } +} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticTagRegistryImpl.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticTagRegistryImpl.java new file mode 100644 index 00000000000..55a9d01151c --- /dev/null +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticTagRegistryImpl.java @@ -0,0 +1,285 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.semantics.internal; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.openhab.core.common.registry.AbstractRegistry; +import org.openhab.core.common.registry.Provider; +import org.openhab.core.semantics.Equipment; +import org.openhab.core.semantics.Location; +import org.openhab.core.semantics.ManagedSemanticTagProvider; +import org.openhab.core.semantics.Point; +import org.openhab.core.semantics.Property; +import org.openhab.core.semantics.SemanticTag; +import org.openhab.core.semantics.SemanticTagProvider; +import org.openhab.core.semantics.SemanticTagRegistry; +import org.openhab.core.semantics.SemanticTags; +import org.openhab.core.semantics.Tag; +import org.openhab.core.semantics.model.DefaultSemanticTagProvider; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is the main implementing class of the {@link SemanticTagRegistry} interface. It + * keeps track of all declared semantic tags of all semantic tags providers and keeps + * their current state in memory. + * + * @author Laurent Garnier - Initial contribution + */ +@NonNullByDefault +@Component(immediate = true) +public class SemanticTagRegistryImpl extends AbstractRegistry + implements SemanticTagRegistry { + + private static final SemanticClassLoader CLASS_LOADER = new SemanticClassLoader(); + + private final Logger logger = LoggerFactory.getLogger(SemanticTagRegistryImpl.class); + + private final DefaultSemanticTagProvider defaultSemanticTagProvider; + private final ManagedSemanticTagProvider managedProvider; + + @Activate + public SemanticTagRegistryImpl(@Reference DefaultSemanticTagProvider defaultSemanticTagProvider, + @Reference ManagedSemanticTagProvider managedProvider) { + super(SemanticTagProvider.class); + this.defaultSemanticTagProvider = defaultSemanticTagProvider; + this.managedProvider = managedProvider; + // Add the default semantic tags provider first, before all others + super.addProvider(defaultSemanticTagProvider); + super.addProvider(managedProvider); + setManagedProvider(managedProvider); + } + + @Override + protected void addProvider(Provider provider) { + // Ignore the default semantic tags provider and the managed provider (they are added in the constructor) + if (!provider.equals(defaultSemanticTagProvider) && !provider.equals(managedProvider)) { + logger.trace("addProvider {} => calling super.addProvider", provider.getClass().getSimpleName()); + super.addProvider(provider); + } else { + logger.trace("addProvider {} => ignoring it", provider.getClass().getSimpleName()); + } + } + + @Override + public @Nullable Class getTagClassById(String tagId) { + return SemanticTags.getById(tagId); + } + + /** + * Builds the fully qualified id for a semantic tag class. + * + * @param tag the semantic tag class + * @return the fully qualified id + */ + public static String buildId(Class tag) { + return buildId("", tag); + } + + private static String buildId(String relativeId, Class tag) { + if (!Location.class.isAssignableFrom(tag) && !Equipment.class.isAssignableFrom(tag) + && !Point.class.isAssignableFrom(tag) && !Property.class.isAssignableFrom(tag)) { + return relativeId; + } + String id = tag.getSimpleName(); + if (!relativeId.isEmpty()) { + id += "_" + relativeId; + } + return buildId(id, tag.getInterfaces()[0]); + } + + @Override + public boolean canBeAdded(SemanticTag tag) { + String id = tag.getUID(); + // check that a tag with this id does not already exist in the registry + if (get(id) != null) { + return false; + } + // Extract the tag name and the parent tag + int lastSeparator = id.lastIndexOf("_"); + if (lastSeparator <= 0) { + return false; + } + String name = id.substring(lastSeparator + 1); + String parentId = id.substring(0, lastSeparator); + SemanticTag parent = get(parentId); + // Check that the tag name has a valid syntax and the parent tag already exists + // and is either a default tag or a managed tag + // Check also that a semantic tag class with the same name does not already exist + return name.matches("[A-Z][a-zA-Z0-9]+") && parent != null + && (managedProvider.get(parentId) != null || defaultSemanticTagProvider.getAll().contains(parent)) + && getTagClassById(name) == null; + } + + @Override + public List getSubTree(SemanticTag tag) { + List ids = getAll().stream().map(t -> t.getUID()).filter(uid -> uid.startsWith(tag.getUID() + "_")) + .toList(); + List tags = new ArrayList<>(); + tags.add(tag); + ids.forEach(id -> { + SemanticTag t = get(id); + if (t != null) { + tags.add(t); + } + }); + return tags; + } + + @Override + public boolean isEditable(SemanticTag tag) { + return managedProvider.get(tag.getUID()) != null; + } + + @Override + public void removeSubTree(SemanticTag tag) { + // Get tags id in reverse order + List ids = getSubTree(tag).stream().filter(this::isEditable).map(SemanticTag::getUID) + .sorted(Comparator.reverseOrder()).toList(); + ids.forEach(managedProvider::remove); + } + + @Override + protected void onAddElement(SemanticTag tag) throws IllegalArgumentException { + logger.trace("onAddElement {}", tag.getUID()); + super.onAddElement(tag); + + String uid = tag.getUID(); + Class tagClass = getTagClassById(uid); + if (tagClass != null) { + // Class already exists + return; + } + + String type; + String className; + Class newTag; + int lastSeparator = uid.lastIndexOf("_"); + if (lastSeparator < 0) { + switch (uid) { + case "Equipment": + newTag = Equipment.class; + break; + case "Location": + newTag = Location.class; + break; + case "Point": + newTag = Point.class; + break; + case "Property": + newTag = Property.class; + break; + default: + throw new IllegalArgumentException("Failed to create semantic tag '" + uid + + "': only Equipment, Location, Point and Property are allowed as root tags."); + } + type = uid; + className = newTag.getClass().getName(); + } else { + String name = uid.substring(lastSeparator + 1); + String parentId = uid.substring(0, lastSeparator); + Class parentTagClass = getTagClassById(parentId); + if (parentTagClass == null) { + throw new IllegalArgumentException( + "Failed to create semantic tag '" + uid + "': no existing parent tag with id " + parentId); + } + if (!name.matches("[A-Z][a-zA-Z0-9]+")) { + throw new IllegalArgumentException("Failed to create semantic tag '" + uid + + "': tag name must start with a capital letter and contain only alphanumerics."); + } + tagClass = getTagClassById(name); + if (tagClass != null) { + throw new IllegalArgumentException( + "Failed to create semantic tag '" + uid + "': tag '" + buildId(tagClass) + "' already exists."); + } + + type = parentId.split("_")[0]; + className = "org.openhab.core.semantics.model." + type.toLowerCase() + "." + name; + try { + newTag = (Class) Class.forName(className, false, CLASS_LOADER); + logger.debug("'{}' semantic {} tag already exists.", className, type); + } catch (ClassNotFoundException e) { + // Create the tag interface + ClassWriter classWriter = new ClassWriter(0); + classWriter.visit(Opcodes.V11, Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT + Opcodes.ACC_INTERFACE, + className.replace('.', '/'), null, "java/lang/Object", + new String[] { parentTagClass.getName().replace('.', '/') }); + classWriter.visitSource("Status.java", null); + classWriter.visitEnd(); + byte[] byteCode = classWriter.toByteArray(); + try { + newTag = (Class) CLASS_LOADER.defineClass(className, byteCode); + logger.debug("'{}' semantic {} tag created.", className, type); + } catch (Exception ex) { + logger.warn("Failed to create semantic tag '{}': {}", className, ex.getMessage()); + throw new IllegalArgumentException("Failed to create semantic tag '" + className + "'", ex); + } + } + } + + addTagSet(uid, newTag); + logger.debug("'{}' semantic {} tag added.", className, type); + } + + @Override + protected void onRemoveElement(SemanticTag tag) { + logger.trace("onRemoveElement {}", tag.getUID()); + super.onRemoveElement(tag); + removeTagSet(tag.getUID()); + } + + private void addTagSet(String tagId, Class tagSet) { + logger.trace("addTagSet {}", tagId); + String id = tagId; + while (id.indexOf("_") != -1) { + SemanticTags.addTagSet(id, tagSet); + id = id.substring(id.indexOf("_") + 1); + } + SemanticTags.addTagSet(id, tagSet); + } + + private void removeTagSet(String tagId) { + logger.trace("removeTagSet {}", tagId); + Class tagSet = getTagClassById(tagId); + if (tagSet == null) { + return; + } + String id = tagId; + while (id.indexOf("_") != -1) { + SemanticTags.removeTagSet(id, tagSet); + id = id.substring(id.indexOf("_") + 1); + } + SemanticTags.removeTagSet(id, tagSet); + } + + private static class SemanticClassLoader extends ClassLoader { + public SemanticClassLoader() { + super(SemanticTagRegistryImpl.class.getClassLoader()); + } + + public Class defineClass(String className, byte[] byteCode) { + // defineClass is protected in the normal ClassLoader + return defineClass(className, byteCode, 0, byteCode.length); + } + } +} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticsMetadataProvider.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticsMetadataProvider.java index 2f64548c0e7..535fc3f4a3e 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticsMetadataProvider.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticsMetadataProvider.java @@ -33,9 +33,9 @@ import org.openhab.core.semantics.Location; import org.openhab.core.semantics.Point; import org.openhab.core.semantics.Property; +import org.openhab.core.semantics.SemanticTagRegistry; import org.openhab.core.semantics.SemanticTags; import org.openhab.core.semantics.Tag; -import org.openhab.core.semantics.TagInfo; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; @@ -75,7 +75,8 @@ public int compare(String s1, String s2) { private final ItemRegistry itemRegistry; @Activate - public SemanticsMetadataProvider(final @Reference ItemRegistry itemRegistry) { + public SemanticsMetadataProvider(final @Reference ItemRegistry itemRegistry, + final @Reference SemanticTagRegistry semanticTagRegistry) { this.itemRegistry = itemRegistry; } @@ -111,7 +112,7 @@ private void processItem(Item item) { if (type != null) { processProperties(item, configuration); processHierarchy(item, configuration); - Metadata md = new Metadata(key, type.getAnnotation(TagInfo.class).id(), configuration); + Metadata md = new Metadata(key, SemanticTagRegistryImpl.buildId(type), configuration); Metadata oldMd = semantics.put(item.getName(), md); if (oldMd == null) { notifyListenersAboutAddedElement(md); @@ -148,7 +149,7 @@ private void processProperties(Item item, Map configuration) { if (entityClass.isAssignableFrom(type)) { Class p = SemanticTags.getProperty(item); if (p != null) { - configuration.put(relation.getValue(), p.getAnnotation(TagInfo.class).id()); + configuration.put(relation.getValue(), SemanticTagRegistryImpl.buildId(p)); } } } diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticsServiceImpl.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticsServiceImpl.java index 4ef13018d74..61a27a40b43 100644 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticsServiceImpl.java +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/internal/SemanticsServiceImpl.java @@ -12,14 +12,19 @@ */ package org.openhab.core.semantics.internal; +import java.util.ArrayList; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.items.GroupItem; import org.openhab.core.items.Item; import org.openhab.core.items.ItemPredicates; @@ -30,7 +35,8 @@ import org.openhab.core.semantics.Equipment; import org.openhab.core.semantics.Location; import org.openhab.core.semantics.Point; -import org.openhab.core.semantics.SemanticTags; +import org.openhab.core.semantics.SemanticTag; +import org.openhab.core.semantics.SemanticTagRegistry; import org.openhab.core.semantics.SemanticsPredicates; import org.openhab.core.semantics.SemanticsService; import org.openhab.core.semantics.Tag; @@ -42,6 +48,7 @@ * The internal implementation of the {@link SemanticsService} interface, which is registered as an OSGi service. * * @author Kai Kreuzer - Initial contribution + * @author Laurent Garnier - Few methods moved from class SemanticTags in order to use the semantic tag registry */ @NonNullByDefault @Component @@ -51,12 +58,15 @@ public class SemanticsServiceImpl implements SemanticsService { private final ItemRegistry itemRegistry; private final MetadataRegistry metadataRegistry; + private final SemanticTagRegistry semanticTagRegistry; @Activate public SemanticsServiceImpl(final @Reference ItemRegistry itemRegistry, - final @Reference MetadataRegistry metadataRegistry) { + final @Reference MetadataRegistry metadataRegistry, + final @Reference SemanticTagRegistry semanticTagRegistry) { this.itemRegistry = itemRegistry; this.metadataRegistry = metadataRegistry; + this.semanticTagRegistry = semanticTagRegistry; } @Override @@ -77,7 +87,7 @@ public Set getItemsInLocation(Class locationType) { @Override public Set getItemsInLocation(String labelOrSynonym, Locale locale) { Set items = new HashSet<>(); - List> tagList = SemanticTags.getByLabelOrSynonym(labelOrSynonym, locale); + List> tagList = getByLabelOrSynonym(labelOrSynonym, locale); if (!tagList.isEmpty()) { for (Class tag : tagList) { if (Location.class.isAssignableFrom(tag)) { @@ -112,4 +122,40 @@ private Predicate hasSynonym(String labelOrSynonym) { return false; }; } + + @Override + public @Nullable Class getByLabel(String tagLabel, Locale locale) { + Optional tag = semanticTagRegistry.getAll().stream() + .filter(t -> t.localized(locale).getLabel().equalsIgnoreCase(tagLabel)) + .sorted(Comparator.comparing(SemanticTag::getUID)).findFirst(); + return tag.isPresent() ? semanticTagRegistry.getTagClassById(tag.get().getUID()) : null; + } + + @Override + public List> getByLabelOrSynonym(String tagLabelOrSynonym, Locale locale) { + List tags = semanticTagRegistry.getAll().stream() + .filter(t -> getLabelAndSynonyms(t, locale).contains(tagLabelOrSynonym.toLowerCase(locale))) + .sorted(Comparator.comparing(SemanticTag::getUID)).toList(); + List> tagList = new ArrayList<>(); + tags.forEach(t -> { + Class tag = semanticTagRegistry.getTagClassById(t.getUID()); + if (tag != null) { + tagList.add(tag); + } + }); + return tagList; + } + + @Override + public List getLabelAndSynonyms(Class tagClass, Locale locale) { + SemanticTag tag = semanticTagRegistry.get(SemanticTagRegistryImpl.buildId(tagClass)); + return tag == null ? List.of() : getLabelAndSynonyms(tag, locale); + } + + private List getLabelAndSynonyms(SemanticTag tag, Locale locale) { + SemanticTag localizedTag = tag.localized(locale); + Stream label = Stream.of(localizedTag.getLabel()); + Stream synonyms = localizedTag.getSynonyms().stream(); + return Stream.concat(label, synonyms).map(s -> s.toLowerCase(locale)).distinct().toList(); + } } diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/DefaultSemanticTagProvider.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/DefaultSemanticTagProvider.java new file mode 100644 index 00000000000..177d0657e95 --- /dev/null +++ b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/DefaultSemanticTagProvider.java @@ -0,0 +1,311 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.semantics.model; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.common.registry.ProviderChangeListener; +import org.openhab.core.semantics.SemanticTag; +import org.openhab.core.semantics.SemanticTagImpl; +import org.openhab.core.semantics.SemanticTagProvider; +import org.osgi.service.component.annotations.Component; + +/** + * This class defines a provider of all default semantic tags. + * + * @author Generated from generateTagClasses.groovy - Initial contribution + */ +@NonNullByDefault +@Component(immediate = true, service = { SemanticTagProvider.class, DefaultSemanticTagProvider.class }) +public class DefaultSemanticTagProvider implements SemanticTagProvider { + + private List defaultTags; + + public DefaultSemanticTagProvider() { + this.defaultTags = new ArrayList<>(); + defaultTags.add(new SemanticTagImpl("Equipment", "", "", "")); + defaultTags.add(new SemanticTagImpl("Location", "", "", "")); + defaultTags.add(new SemanticTagImpl("Point", "", "", "")); + defaultTags.add(new SemanticTagImpl("Property", "", "", "")); + defaultTags.add(new SemanticTagImpl("Location_Indoor", // + "Indoor", "Anything that is inside a closed building", "")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Apartment", // + "Apartment", "", "Apartments")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Building", // + "Building", "", "Buildings")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Building_Garage", // + "Garage", "", "Garages")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Building_House", // + "House", "", "Houses")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Building_Shed", // + "Shed", "", "Sheds")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Building_SummerHouse", // + "Summer House", "", "Summer Houses, Second Home, Second Homes")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Floor", // + "Floor", "", "Floors")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Floor_GroundFloor", // + "Ground Floor", "", "Ground Floors, Downstairs")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Floor_FirstFloor", // + "First Floor", "", "First Floors, Upstairs")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Floor_SecondFloor", // + "Second Floor", "", "Second Floors")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Floor_ThirdFloor", // + "Third Floor", "", "Third Floors")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Floor_Attic", // + "Attic", "", "Attics")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Floor_Basement", // + "Basement", "", "Basements")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Corridor", // + "Corridor", "", "Corridors, Hallway, Hallways")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Room", // + "Room", "", "Rooms")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Room_Bathroom", // + "Bathroom", "", "Bathrooms, Bath, Baths, Powder Room, Powder Rooms")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Room_Bedroom", // + "Bedroom", "", "Bedrooms")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Room_BoilerRoom", // + "Boiler Room", "", "Boiler Rooms")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Room_Cellar", // + "Cellar", "", "Cellars")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Room_DiningRoom", // + "Dining Room", "", "Dining Rooms")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Room_Entry", // + "Entry", "", "Entries, Foyer, Foyers")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Room_FamilyRoom", // + "Family Room", "", "Family Rooms")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Room_GuestRoom", // + "Guest Room", "", "Guest Rooms")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Room_Kitchen", // + "Kitchen", "", "Kitchens")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Room_LaundryRoom", // + "Laundry Room", "", "Laundry Rooms")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Room_LivingRoom", // + "Living Room", "", "Living Rooms")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Room_Office", // + "Office", "", "Offices")); + defaultTags.add(new SemanticTagImpl("Location_Indoor_Room_Veranda", // + "Veranda", "", "Verandas")); + defaultTags.add(new SemanticTagImpl("Location_Outdoor", // + "Outdoor", "", "")); + defaultTags.add(new SemanticTagImpl("Location_Outdoor_Carport", // + "Carport", "", "Carports")); + defaultTags.add(new SemanticTagImpl("Location_Outdoor_Driveway", // + "Driveway", "", "Driveways")); + defaultTags.add(new SemanticTagImpl("Location_Outdoor_Garden", // + "Garden", "", "Gardens")); + defaultTags.add(new SemanticTagImpl("Location_Outdoor_Patio", // + "Patio", "", "Patios")); + defaultTags.add(new SemanticTagImpl("Location_Outdoor_Porch", // + "Porch", "", "Porches")); + defaultTags.add(new SemanticTagImpl("Location_Outdoor_Terrace", // + "Terrace", "", "Terraces, Deck, Decks")); + defaultTags.add(new SemanticTagImpl("Property_Temperature", // + "Temperature", "", "Temperatures")); + defaultTags.add(new SemanticTagImpl("Property_Light", // + "Light", "", "Lights, Lighting")); + defaultTags.add(new SemanticTagImpl("Property_ColorTemperature", // + "Color Temperature", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Humidity", // + "Humidity", "", "Moisture")); + defaultTags.add(new SemanticTagImpl("Property_Presence", // + "Presence", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Pressure", // + "Pressure", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Smoke", // + "Smoke", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Noise", // + "Noise", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Rain", // + "Rain", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Wind", // + "Wind", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Water", // + "Water", "", "")); + defaultTags.add(new SemanticTagImpl("Property_CO2", // + "CO2", "", "Carbon Dioxide")); + defaultTags.add(new SemanticTagImpl("Property_CO", // + "CO", "", "Carbon Monoxide")); + defaultTags.add(new SemanticTagImpl("Property_Energy", // + "Energy", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Power", // + "Power", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Voltage", // + "Voltage", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Current", // + "Current", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Frequency", // + "Frequency", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Gas", // + "Gas", "", "")); + defaultTags.add(new SemanticTagImpl("Property_SoundVolume", // + "Sound Volume", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Oil", // + "Oil", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Duration", // + "Duration", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Level", // + "Level", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Opening", // + "Opening", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Timestamp", // + "Timestamp", "", "")); + defaultTags.add(new SemanticTagImpl("Property_Ultraviolet", // + "Ultraviolet", "", "UV")); + defaultTags.add(new SemanticTagImpl("Property_Vibration", // + "Vibration", "", "")); + defaultTags.add(new SemanticTagImpl("Point_Alarm", // + "Alarm", "", "")); + defaultTags.add(new SemanticTagImpl("Point_Control", // + "Control", "", "")); + defaultTags.add(new SemanticTagImpl("Point_Control_Switch", // + "Switch", "", "")); + defaultTags.add(new SemanticTagImpl("Point_Measurement", // + "Measurement", "", "")); + defaultTags.add(new SemanticTagImpl("Point_Setpoint", // + "Setpoint", "", "")); + defaultTags.add(new SemanticTagImpl("Point_Status", // + "Status", "", "")); + defaultTags.add(new SemanticTagImpl("Point_Status_LowBattery", // + "LowBattery", "", "")); + defaultTags.add(new SemanticTagImpl("Point_Status_OpenLevel", // + "OpenLevel", "", "")); + defaultTags.add(new SemanticTagImpl("Point_Status_OpenState", // + "OpenState", "", "")); + defaultTags.add(new SemanticTagImpl("Point_Status_Tampered", // + "Tampered", "", "")); + defaultTags.add(new SemanticTagImpl("Point_Status_Tilt", // + "Tilt", "", "")); + defaultTags.add(new SemanticTagImpl("Equipment_AlarmSystem", // + "Alarm System", "", "Alarm Systems")); + defaultTags.add(new SemanticTagImpl("Equipment_Battery", // + "Battery", "", "Batteries")); + defaultTags.add(new SemanticTagImpl("Equipment_Blinds", // + "Blinds", "", "Rollershutter, Rollershutters, Roller shutter, Roller shutters, Shutter, Shutters")); + defaultTags.add(new SemanticTagImpl("Equipment_Boiler", // + "Boiler", "", "Boilers")); + defaultTags.add(new SemanticTagImpl("Equipment_Camera", // + "Camera", "", "Cameras")); + defaultTags.add(new SemanticTagImpl("Equipment_Car", // + "Car", "", "Cars")); + defaultTags.add(new SemanticTagImpl("Equipment_CleaningRobot", // + "Cleaning Robot", "", "Cleaning Robots, Vacuum robot, Vacuum robots")); + defaultTags.add(new SemanticTagImpl("Equipment_Door", // + "Door", "", "Doors")); + defaultTags.add(new SemanticTagImpl("Equipment_Door_BackDoor", // + "Back Door", "", "Back Doors")); + defaultTags.add(new SemanticTagImpl("Equipment_Door_CellarDoor", // + "Cellar Door", "", "Cellar Doors")); + defaultTags.add(new SemanticTagImpl("Equipment_Door_FrontDoor", // + "Front Door", "", "Front Doors, Frontdoor, Frontdoors")); + defaultTags.add(new SemanticTagImpl("Equipment_Door_GarageDoor", // + "Garage Door", "", "Garage Doors")); + defaultTags.add(new SemanticTagImpl("Equipment_Door_Gate", // + "Gate", "", "Gates")); + defaultTags.add(new SemanticTagImpl("Equipment_Door_InnerDoor", // + "Inner Door", "", "Inner Doors")); + defaultTags.add(new SemanticTagImpl("Equipment_Door_SideDoor", // + "Side Door", "", "Side Doors")); + defaultTags.add(new SemanticTagImpl("Equipment_Doorbell", // + "Doorbell", "", "Doorbells")); + defaultTags.add(new SemanticTagImpl("Equipment_Fan", // + "Fan", "", "Fans")); + defaultTags.add(new SemanticTagImpl("Equipment_Fan_CeilingFan", // + "Ceiling Fan", "", "Ceiling Fans")); + defaultTags.add(new SemanticTagImpl("Equipment_Fan_KitchenHood", // + "Kitchen Hood", "", "Kitchen Hoods")); + defaultTags.add(new SemanticTagImpl("Equipment_HVAC", // + "HVAC", "", "Heating, Ventilation, Air Conditioning, A/C, A/Cs, AC")); + defaultTags.add(new SemanticTagImpl("Equipment_Inverter", // + "Inverter", "", "Inverters")); + defaultTags.add(new SemanticTagImpl("Equipment_LawnMower", // + "Lawn Mower", "", "Lawn Mowers")); + defaultTags.add(new SemanticTagImpl("Equipment_Lightbulb", // + "Lightbulb", "", "Lightbulbs, Bulb, Bulbs, Lamp, Lamps, Lights, Lighting")); + defaultTags.add(new SemanticTagImpl("Equipment_Lightbulb_LightStripe", // + "Light Stripe", "", "Light Stripes")); + defaultTags.add(new SemanticTagImpl("Equipment_Lock", // + "Lock", "", "Locks")); + defaultTags.add(new SemanticTagImpl("Equipment_NetworkAppliance", // + "Network Appliance", "", "Network Appliances")); + defaultTags.add(new SemanticTagImpl("Equipment_PowerOutlet", // + "Power Outlet", "", "Power Outlets, Outlet, Outlets")); + defaultTags.add(new SemanticTagImpl("Equipment_Projector", // + "Projector", "", "Projectors, Beamer, Beamers")); + defaultTags.add(new SemanticTagImpl("Equipment_Pump", // + "Pump", "", "Pumps")); + defaultTags.add(new SemanticTagImpl("Equipment_RadiatorControl", // + "Radiator Control", "", "Radiator Controls, Radiator, Radiators")); + defaultTags.add(new SemanticTagImpl("Equipment_Receiver", // + "Receiver", "", "Receivers, Audio Receiver, Audio Receivers, AV Receiver, AV Receivers")); + defaultTags.add(new SemanticTagImpl("Equipment_RemoteControl", // + "Remote Control", "", "Remote Controls")); + defaultTags.add(new SemanticTagImpl("Equipment_Screen", // + "Screen", "", "Screens")); + defaultTags.add(new SemanticTagImpl("Equipment_Screen_Television", // + "Television", "", "Televisions, TV, TVs")); + defaultTags.add(new SemanticTagImpl("Equipment_Sensor", // + "Sensor", "", "Sensors")); + defaultTags.add(new SemanticTagImpl("Equipment_Sensor_MotionDetector", // + "Motion Detector", "", "Motion Detectors, Motion sensor, Motion sensors")); + defaultTags.add(new SemanticTagImpl("Equipment_Sensor_SmokeDetector", // + "Smoke Detector", "", "Smoke Detectors")); + defaultTags.add(new SemanticTagImpl("Equipment_Siren", // + "Siren", "", "Sirens")); + defaultTags.add(new SemanticTagImpl("Equipment_Smartphone", // + "Smartphone", "", "Smartphones, Phone, Phones")); + defaultTags.add(new SemanticTagImpl("Equipment_Speaker", // + "Speaker", "", "Speakers")); + defaultTags.add(new SemanticTagImpl("Equipment_Valve", // + "Valve", "", "Valves")); + defaultTags.add(new SemanticTagImpl("Equipment_VoiceAssistant", // + "Voice Assistant", "", "Voice Assistants")); + defaultTags.add(new SemanticTagImpl("Equipment_WallSwitch", // + "Wall Switch", "", "Wall Switches")); + defaultTags.add(new SemanticTagImpl("Equipment_WebService", // + "Web Service", "", "Web Services")); + defaultTags.add(new SemanticTagImpl("Equipment_WebService_WeatherService", // + "Weather Service", "", "Weather Services")); + defaultTags.add(new SemanticTagImpl("Equipment_WhiteGood", // + "White Good", "", "White Goods")); + defaultTags.add(new SemanticTagImpl("Equipment_WhiteGood_Dishwasher", // + "Dishwasher", "", "Dishwashers")); + defaultTags.add(new SemanticTagImpl("Equipment_WhiteGood_Dryer", // + "Dryer", "", "Dryers")); + defaultTags.add(new SemanticTagImpl("Equipment_WhiteGood_Freezer", // + "Freezer", "", "Freezers")); + defaultTags.add(new SemanticTagImpl("Equipment_WhiteGood_Oven", // + "Oven", "", "Ovens")); + defaultTags.add(new SemanticTagImpl("Equipment_WhiteGood_Refrigerator", // + "Refrigerator", "", "Refrigerators")); + defaultTags.add(new SemanticTagImpl("Equipment_WhiteGood_WashingMachine", // + "Washing Machine", "", "Washing Machines")); + defaultTags.add(new SemanticTagImpl("Equipment_Window", // + "Window", "", "Windows")); + } + + @Override + public Collection getAll() { + return defaultTags; + } + + @Override + public void addProviderChangeListener(ProviderChangeListener listener) { + } + + @Override + public void removeProviderChangeListener(ProviderChangeListener listener) { + } +} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/AlarmSystem.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/AlarmSystem.java deleted file mode 100644 index bc810386bdf..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/AlarmSystem.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an Alarm System. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_AlarmSystem", label = "Alarm System", synonyms = "Alarm Systems", description = "") -public interface AlarmSystem extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/BackDoor.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/BackDoor.java deleted file mode 100644 index f693b7df429..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/BackDoor.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Back Door. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Door_BackDoor", label = "Back Door", synonyms = "Back Doors", description = "") -public interface BackDoor extends Door { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Battery.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Battery.java deleted file mode 100644 index f1bdad31eda..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Battery.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Battery. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Battery", label = "Battery", synonyms = "Batteries", description = "") -public interface Battery extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Blinds.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Blinds.java deleted file mode 100644 index cf282d1ce37..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Blinds.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Blinds. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Blinds", label = "Blinds", synonyms = "Rollershutter, Rollershutters, Roller shutter, Roller shutters, Shutter, Shutters", description = "") -public interface Blinds extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Boiler.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Boiler.java deleted file mode 100644 index 0ea0d34b930..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Boiler.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Boiler. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Boiler", label = "Boiler", synonyms = "Boilers", description = "") -public interface Boiler extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Camera.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Camera.java deleted file mode 100644 index bd983ae404e..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Camera.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Camera. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Camera", label = "Camera", synonyms = "Cameras", description = "") -public interface Camera extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Car.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Car.java deleted file mode 100644 index 2875a98b5dd..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Car.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Car. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Car", label = "Car", synonyms = "Cars", description = "") -public interface Car extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/CeilingFan.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/CeilingFan.java deleted file mode 100644 index 61628444b2b..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/CeilingFan.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Ceiling Fan. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Fan_CeilingFan", label = "Ceiling Fan", synonyms = "Ceiling Fans", description = "") -public interface CeilingFan extends Fan { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/CellarDoor.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/CellarDoor.java deleted file mode 100644 index c61ce7ee805..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/CellarDoor.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Cellar Door. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Door_CellarDoor", label = "Cellar Door", synonyms = "Cellar Doors", description = "") -public interface CellarDoor extends Door { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/CleaningRobot.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/CleaningRobot.java deleted file mode 100644 index ff5c995fb1c..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/CleaningRobot.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Cleaning Robot. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_CleaningRobot", label = "Cleaning Robot", synonyms = "Cleaning Robots, Vacuum robot, Vacuum robots", description = "") -public interface CleaningRobot extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Dishwasher.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Dishwasher.java deleted file mode 100644 index ecb707ab016..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Dishwasher.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Dishwasher. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_WhiteGood_Dishwasher", label = "Dishwasher", synonyms = "Dishwashers", description = "") -public interface Dishwasher extends WhiteGood { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Door.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Door.java deleted file mode 100644 index 584d5ebf5c4..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Door.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Door. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Door", label = "Door", synonyms = "Doors", description = "") -public interface Door extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Doorbell.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Doorbell.java deleted file mode 100644 index 61875ce66fa..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Doorbell.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Doorbell. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Doorbell", label = "Doorbell", synonyms = "Doorbells", description = "") -public interface Doorbell extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Dryer.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Dryer.java deleted file mode 100644 index 8860419ffb9..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Dryer.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Dryer. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_WhiteGood_Dryer", label = "Dryer", synonyms = "Dryers", description = "") -public interface Dryer extends WhiteGood { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Equipments.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Equipments.java deleted file mode 100644 index d99172b88c6..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Equipments.java +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Stream; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; - -/** - * This class provides a stream of all defined equipments. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -public class Equipments { - - static final Set> EQUIPMENTS = new HashSet<>(); - - static { - EQUIPMENTS.add(Equipment.class); - EQUIPMENTS.add(AlarmSystem.class); - EQUIPMENTS.add(BackDoor.class); - EQUIPMENTS.add(Battery.class); - EQUIPMENTS.add(Blinds.class); - EQUIPMENTS.add(Boiler.class); - EQUIPMENTS.add(Camera.class); - EQUIPMENTS.add(Car.class); - EQUIPMENTS.add(CeilingFan.class); - EQUIPMENTS.add(CellarDoor.class); - EQUIPMENTS.add(CleaningRobot.class); - EQUIPMENTS.add(Dishwasher.class); - EQUIPMENTS.add(Door.class); - EQUIPMENTS.add(Doorbell.class); - EQUIPMENTS.add(Dryer.class); - EQUIPMENTS.add(Fan.class); - EQUIPMENTS.add(Freezer.class); - EQUIPMENTS.add(FrontDoor.class); - EQUIPMENTS.add(GarageDoor.class); - EQUIPMENTS.add(Gate.class); - EQUIPMENTS.add(HVAC.class); - EQUIPMENTS.add(InnerDoor.class); - EQUIPMENTS.add(Inverter.class); - EQUIPMENTS.add(KitchenHood.class); - EQUIPMENTS.add(LawnMower.class); - EQUIPMENTS.add(LightStripe.class); - EQUIPMENTS.add(Lightbulb.class); - EQUIPMENTS.add(Lock.class); - EQUIPMENTS.add(MotionDetector.class); - EQUIPMENTS.add(NetworkAppliance.class); - EQUIPMENTS.add(Oven.class); - EQUIPMENTS.add(PowerOutlet.class); - EQUIPMENTS.add(Projector.class); - EQUIPMENTS.add(Pump.class); - EQUIPMENTS.add(RadiatorControl.class); - EQUIPMENTS.add(Receiver.class); - EQUIPMENTS.add(Refrigerator.class); - EQUIPMENTS.add(RemoteControl.class); - EQUIPMENTS.add(Screen.class); - EQUIPMENTS.add(Sensor.class); - EQUIPMENTS.add(SideDoor.class); - EQUIPMENTS.add(Siren.class); - EQUIPMENTS.add(Smartphone.class); - EQUIPMENTS.add(SmokeDetector.class); - EQUIPMENTS.add(Speaker.class); - EQUIPMENTS.add(Television.class); - EQUIPMENTS.add(Valve.class); - EQUIPMENTS.add(VoiceAssistant.class); - EQUIPMENTS.add(WallSwitch.class); - EQUIPMENTS.add(WashingMachine.class); - EQUIPMENTS.add(WeatherService.class); - EQUIPMENTS.add(WebService.class); - EQUIPMENTS.add(WhiteGood.class); - EQUIPMENTS.add(Window.class); - } - - public static Stream> stream() { - return EQUIPMENTS.stream(); - } - - public static boolean add(Class tag) { - return EQUIPMENTS.add(tag); - } -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Fan.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Fan.java deleted file mode 100644 index 8df120d40b7..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Fan.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Fan. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Fan", label = "Fan", synonyms = "Fans", description = "") -public interface Fan extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Freezer.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Freezer.java deleted file mode 100644 index da57322726f..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Freezer.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Freezer. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_WhiteGood_Freezer", label = "Freezer", synonyms = "Freezers", description = "") -public interface Freezer extends WhiteGood { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/FrontDoor.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/FrontDoor.java deleted file mode 100644 index 4fd6ce52306..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/FrontDoor.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Front Door. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Door_FrontDoor", label = "Front Door", synonyms = "Front Doors, Frontdoor, Frontdoors", description = "") -public interface FrontDoor extends Door { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/GarageDoor.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/GarageDoor.java deleted file mode 100644 index 177c0eb1981..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/GarageDoor.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Garage Door. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Door_GarageDoor", label = "Garage Door", synonyms = "Garage Doors", description = "") -public interface GarageDoor extends Door { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Gate.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Gate.java deleted file mode 100644 index beadc24bce1..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Gate.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Gate. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Door_Gate", label = "Gate", synonyms = "Gates", description = "") -public interface Gate extends Door { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/HVAC.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/HVAC.java deleted file mode 100644 index 0073408c846..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/HVAC.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a HVAC. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_HVAC", label = "HVAC", synonyms = "Heating, Ventilation, Air Conditioning, A/C, A/Cs, AC", description = "") -public interface HVAC extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/InnerDoor.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/InnerDoor.java deleted file mode 100644 index bfc3f11d2e9..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/InnerDoor.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an Inner Door. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Door_InnerDoor", label = "Inner Door", synonyms = "Inner Doors", description = "") -public interface InnerDoor extends Door { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Inverter.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Inverter.java deleted file mode 100644 index 303893113d3..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Inverter.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an Inverter. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Inverter", label = "Inverter", synonyms = "Inverters", description = "") -public interface Inverter extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/KitchenHood.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/KitchenHood.java deleted file mode 100644 index e53b7f56860..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/KitchenHood.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Kitchen Hood. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Fan_KitchenHood", label = "Kitchen Hood", synonyms = "Kitchen Hoods", description = "") -public interface KitchenHood extends Fan { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/LawnMower.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/LawnMower.java deleted file mode 100644 index 9b354437dbb..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/LawnMower.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Lawn Mower. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_LawnMower", label = "Lawn Mower", synonyms = "Lawn Mowers", description = "") -public interface LawnMower extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/LightStripe.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/LightStripe.java deleted file mode 100644 index 7e628d3b9cf..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/LightStripe.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Light Stripe. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Lightbulb_LightStripe", label = "Light Stripe", synonyms = "Light Stripes", description = "") -public interface LightStripe extends Lightbulb { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Lightbulb.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Lightbulb.java deleted file mode 100644 index 76eb0d4992d..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Lightbulb.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Lightbulb. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Lightbulb", label = "Lightbulb", synonyms = "Lightbulbs, Bulb, Bulbs, Lamp, Lamps, Lights, Lighting", description = "") -public interface Lightbulb extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Lock.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Lock.java deleted file mode 100644 index 26c303b1329..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Lock.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Lock. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Lock", label = "Lock", synonyms = "Locks", description = "") -public interface Lock extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/MotionDetector.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/MotionDetector.java deleted file mode 100644 index 3a7c0f40ffa..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/MotionDetector.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Motion Detector. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Sensor_MotionDetector", label = "Motion Detector", synonyms = "Motion Detectors, Motion sensor, Motion sensors", description = "") -public interface MotionDetector extends Sensor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/NetworkAppliance.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/NetworkAppliance.java deleted file mode 100644 index e873a53d2a0..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/NetworkAppliance.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Network Appliance. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_NetworkAppliance", label = "Network Appliance", synonyms = "Network Appliances", description = "") -public interface NetworkAppliance extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Oven.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Oven.java deleted file mode 100644 index 18578cd0d6c..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Oven.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an Oven. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_WhiteGood_Oven", label = "Oven", synonyms = "Ovens", description = "") -public interface Oven extends WhiteGood { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/PowerOutlet.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/PowerOutlet.java deleted file mode 100644 index bbeb001eb4f..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/PowerOutlet.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Power Outlet. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_PowerOutlet", label = "Power Outlet", synonyms = "Power Outlets, Outlet, Outlets", description = "") -public interface PowerOutlet extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Projector.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Projector.java deleted file mode 100644 index 5fb617c4692..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Projector.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Projector. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Projector", label = "Projector", synonyms = "Projectors, Beamer, Beamers", description = "") -public interface Projector extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Pump.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Pump.java deleted file mode 100644 index 02cd22bebb0..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Pump.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Pump. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Pump", label = "Pump", synonyms = "Pumps", description = "") -public interface Pump extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/RadiatorControl.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/RadiatorControl.java deleted file mode 100644 index 882289fa68f..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/RadiatorControl.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Radiator Control. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_RadiatorControl", label = "Radiator Control", synonyms = "Radiator Controls, Radiator, Radiators", description = "") -public interface RadiatorControl extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Receiver.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Receiver.java deleted file mode 100644 index d1cdfe7b87a..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Receiver.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Receiver. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Receiver", label = "Receiver", synonyms = "Receivers, Audio Receiver, Audio Receivers, AV Receiver, AV Receivers", description = "") -public interface Receiver extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Refrigerator.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Refrigerator.java deleted file mode 100644 index fa855447615..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Refrigerator.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Refrigerator. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_WhiteGood_Refrigerator", label = "Refrigerator", synonyms = "Refrigerators", description = "") -public interface Refrigerator extends WhiteGood { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/RemoteControl.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/RemoteControl.java deleted file mode 100644 index 4befed3c843..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/RemoteControl.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Remote Control. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_RemoteControl", label = "Remote Control", synonyms = "Remote Controls", description = "") -public interface RemoteControl extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Screen.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Screen.java deleted file mode 100644 index c3cb10e0c2e..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Screen.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Screen. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Screen", label = "Screen", synonyms = "Screens", description = "") -public interface Screen extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Sensor.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Sensor.java deleted file mode 100644 index ef60408ada6..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Sensor.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Sensor. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Sensor", label = "Sensor", synonyms = "Sensors", description = "") -public interface Sensor extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/SideDoor.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/SideDoor.java deleted file mode 100644 index ba2e3bb1595..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/SideDoor.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Side Door. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Door_SideDoor", label = "Side Door", synonyms = "Side Doors", description = "") -public interface SideDoor extends Door { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Siren.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Siren.java deleted file mode 100644 index e436b39ebbc..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Siren.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Siren. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Siren", label = "Siren", synonyms = "Sirens", description = "") -public interface Siren extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Smartphone.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Smartphone.java deleted file mode 100644 index fbecc781aab..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Smartphone.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Smartphone. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Smartphone", label = "Smartphone", synonyms = "Smartphones, Phone, Phones", description = "") -public interface Smartphone extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/SmokeDetector.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/SmokeDetector.java deleted file mode 100644 index 2ee50b2ea44..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/SmokeDetector.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Smoke Detector. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Sensor_SmokeDetector", label = "Smoke Detector", synonyms = "Smoke Detectors", description = "") -public interface SmokeDetector extends Sensor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Speaker.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Speaker.java deleted file mode 100644 index ce42af983c8..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Speaker.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Speaker. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Speaker", label = "Speaker", synonyms = "Speakers", description = "") -public interface Speaker extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Television.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Television.java deleted file mode 100644 index c84af6d7cc4..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Television.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Television. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Screen_Television", label = "Television", synonyms = "Televisions, TV, TVs", description = "") -public interface Television extends Screen { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Valve.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Valve.java deleted file mode 100644 index a30ad2fd9a6..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Valve.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Valve. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Valve", label = "Valve", synonyms = "Valves", description = "") -public interface Valve extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/VoiceAssistant.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/VoiceAssistant.java deleted file mode 100644 index af28a055e85..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/VoiceAssistant.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Voice Assistant. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_VoiceAssistant", label = "Voice Assistant", synonyms = "Voice Assistants", description = "") -public interface VoiceAssistant extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WallSwitch.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WallSwitch.java deleted file mode 100644 index d2d68e2d85d..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WallSwitch.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Wall Switch. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_WallSwitch", label = "Wall Switch", synonyms = "Wall Switches", description = "") -public interface WallSwitch extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WashingMachine.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WashingMachine.java deleted file mode 100644 index b70aa93db22..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WashingMachine.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Washing Machine. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_WhiteGood_WashingMachine", label = "Washing Machine", synonyms = "Washing Machines", description = "") -public interface WashingMachine extends WhiteGood { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WeatherService.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WeatherService.java deleted file mode 100644 index 2f3118d3bc0..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WeatherService.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Weather Service. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_WebService_WeatherService", label = "Weather Service", synonyms = "Weather Services", description = "") -public interface WeatherService extends WebService { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WebService.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WebService.java deleted file mode 100644 index 33f14f5500e..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WebService.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Web Service. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_WebService", label = "Web Service", synonyms = "Web Services", description = "") -public interface WebService extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WhiteGood.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WhiteGood.java deleted file mode 100644 index 687e056dbd6..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/WhiteGood.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a White Good. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_WhiteGood", label = "White Good", synonyms = "White Goods", description = "") -public interface WhiteGood extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Window.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Window.java deleted file mode 100644 index d2f305cb128..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/equipment/Window.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.equipment; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Equipment; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Window. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Equipment_Window", label = "Window", synonyms = "Windows", description = "") -public interface Window extends Equipment { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Apartment.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Apartment.java deleted file mode 100644 index 389e7844dc9..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Apartment.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an Apartment. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Apartment", label = "Apartment", synonyms = "Apartments", description = "") -public interface Apartment extends Indoor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Attic.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Attic.java deleted file mode 100644 index f766e622e1d..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Attic.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an Attic. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Floor_Attic", label = "Attic", synonyms = "Attics", description = "") -public interface Attic extends Floor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Basement.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Basement.java deleted file mode 100644 index 8fc8cb3baad..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Basement.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Basement. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Floor_Basement", label = "Basement", synonyms = "Basements", description = "") -public interface Basement extends Floor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Bathroom.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Bathroom.java deleted file mode 100644 index 4b6c3fb78b4..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Bathroom.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Bathroom. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Room_Bathroom", label = "Bathroom", synonyms = "Bathrooms, Bath, Baths, Powder Room, Powder Rooms", description = "") -public interface Bathroom extends Room { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Bedroom.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Bedroom.java deleted file mode 100644 index 8c5c0fc987b..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Bedroom.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Bedroom. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Room_Bedroom", label = "Bedroom", synonyms = "Bedrooms", description = "") -public interface Bedroom extends Room { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/BoilerRoom.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/BoilerRoom.java deleted file mode 100644 index d149ef4468d..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/BoilerRoom.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Boiler Room. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Room_BoilerRoom", label = "Boiler Room", synonyms = "Boiler Rooms", description = "") -public interface BoilerRoom extends Room { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Building.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Building.java deleted file mode 100644 index 46577c537f6..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Building.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Building. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Building", label = "Building", synonyms = "Buildings", description = "") -public interface Building extends Indoor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Carport.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Carport.java deleted file mode 100644 index 322ddfe103e..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Carport.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Carport. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Outdoor_Carport", label = "Carport", synonyms = "Carports", description = "") -public interface Carport extends Outdoor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Cellar.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Cellar.java deleted file mode 100644 index e5f0ab2709b..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Cellar.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Cellar. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Room_Cellar", label = "Cellar", synonyms = "Cellars", description = "") -public interface Cellar extends Room { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Corridor.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Corridor.java deleted file mode 100644 index 6509b82659e..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Corridor.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Corridor. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Corridor", label = "Corridor", synonyms = "Corridors, Hallway, Hallways", description = "") -public interface Corridor extends Indoor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/DiningRoom.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/DiningRoom.java deleted file mode 100644 index 21c7affc1d7..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/DiningRoom.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Dining Room. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Room_DiningRoom", label = "Dining Room", synonyms = "Dining Rooms", description = "") -public interface DiningRoom extends Room { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Driveway.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Driveway.java deleted file mode 100644 index 44930624558..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Driveway.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Driveway. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Outdoor_Driveway", label = "Driveway", synonyms = "Driveways", description = "") -public interface Driveway extends Outdoor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Entry.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Entry.java deleted file mode 100644 index f88c4f10bd7..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Entry.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an Entry. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Room_Entry", label = "Entry", synonyms = "Entries, Foyer, Foyers", description = "") -public interface Entry extends Room { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/FamilyRoom.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/FamilyRoom.java deleted file mode 100644 index d4c095f059d..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/FamilyRoom.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Family Room. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Room_FamilyRoom", label = "Family Room", synonyms = "Family Rooms", description = "") -public interface FamilyRoom extends Room { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/FirstFloor.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/FirstFloor.java deleted file mode 100644 index 977606b7ea5..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/FirstFloor.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a First Floor. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Floor_FirstFloor", label = "First Floor", synonyms = "First Floors, Upstairs", description = "") -public interface FirstFloor extends Floor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Floor.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Floor.java deleted file mode 100644 index e6a71e4e6a5..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Floor.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Floor. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Floor", label = "Floor", synonyms = "Floors", description = "") -public interface Floor extends Indoor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Garage.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Garage.java deleted file mode 100644 index 8f592538f52..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Garage.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Garage. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Building_Garage", label = "Garage", synonyms = "Garages", description = "") -public interface Garage extends Building { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Garden.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Garden.java deleted file mode 100644 index 1efedd39b76..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Garden.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Garden. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Outdoor_Garden", label = "Garden", synonyms = "Gardens", description = "") -public interface Garden extends Outdoor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/GroundFloor.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/GroundFloor.java deleted file mode 100644 index b8b2942d71c..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/GroundFloor.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Ground Floor. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Floor_GroundFloor", label = "Ground Floor", synonyms = "Ground Floors, Downstairs", description = "") -public interface GroundFloor extends Floor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/GuestRoom.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/GuestRoom.java deleted file mode 100644 index cfafb8f99ff..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/GuestRoom.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Guest Room. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Room_GuestRoom", label = "Guest Room", synonyms = "Guest Rooms", description = "") -public interface GuestRoom extends Room { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/House.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/House.java deleted file mode 100644 index b22410e8c55..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/House.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a House. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Building_House", label = "House", synonyms = "Houses", description = "") -public interface House extends Building { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Indoor.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Indoor.java deleted file mode 100644 index 48c652aaa85..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Indoor.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Location; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an Indoor. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor", label = "Indoor", synonyms = "", description = "Anything that is inside a closed building") -public interface Indoor extends Location { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Kitchen.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Kitchen.java deleted file mode 100644 index ffdd06d2786..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Kitchen.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Kitchen. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Room_Kitchen", label = "Kitchen", synonyms = "Kitchens", description = "") -public interface Kitchen extends Room { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/LaundryRoom.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/LaundryRoom.java deleted file mode 100644 index 98f46395ba3..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/LaundryRoom.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Laundry Room. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Room_LaundryRoom", label = "Laundry Room", synonyms = "Laundry Rooms", description = "") -public interface LaundryRoom extends Room { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/LivingRoom.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/LivingRoom.java deleted file mode 100644 index c23557395b7..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/LivingRoom.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Living Room. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Room_LivingRoom", label = "Living Room", synonyms = "Living Rooms", description = "") -public interface LivingRoom extends Room { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Locations.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Locations.java deleted file mode 100644 index 6c5063dfb1d..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Locations.java +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Stream; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Location; - -/** - * This class provides a stream of all defined locations. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -public class Locations { - - static final Set> LOCATIONS = new HashSet<>(); - - static { - LOCATIONS.add(Location.class); - LOCATIONS.add(Apartment.class); - LOCATIONS.add(Attic.class); - LOCATIONS.add(Basement.class); - LOCATIONS.add(Bathroom.class); - LOCATIONS.add(Bedroom.class); - LOCATIONS.add(BoilerRoom.class); - LOCATIONS.add(Building.class); - LOCATIONS.add(Carport.class); - LOCATIONS.add(Cellar.class); - LOCATIONS.add(Corridor.class); - LOCATIONS.add(DiningRoom.class); - LOCATIONS.add(Driveway.class); - LOCATIONS.add(Entry.class); - LOCATIONS.add(FamilyRoom.class); - LOCATIONS.add(FirstFloor.class); - LOCATIONS.add(Floor.class); - LOCATIONS.add(Garage.class); - LOCATIONS.add(Garden.class); - LOCATIONS.add(GroundFloor.class); - LOCATIONS.add(GuestRoom.class); - LOCATIONS.add(House.class); - LOCATIONS.add(Indoor.class); - LOCATIONS.add(Kitchen.class); - LOCATIONS.add(LaundryRoom.class); - LOCATIONS.add(LivingRoom.class); - LOCATIONS.add(Office.class); - LOCATIONS.add(Outdoor.class); - LOCATIONS.add(Patio.class); - LOCATIONS.add(Porch.class); - LOCATIONS.add(Room.class); - LOCATIONS.add(SecondFloor.class); - LOCATIONS.add(Shed.class); - LOCATIONS.add(SummerHouse.class); - LOCATIONS.add(Terrace.class); - LOCATIONS.add(ThirdFloor.class); - LOCATIONS.add(Veranda.class); - } - - public static Stream> stream() { - return LOCATIONS.stream(); - } - - public static boolean add(Class tag) { - return LOCATIONS.add(tag); - } -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Office.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Office.java deleted file mode 100644 index e5b29630351..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Office.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an Office. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Room_Office", label = "Office", synonyms = "Offices", description = "") -public interface Office extends Room { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Outdoor.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Outdoor.java deleted file mode 100644 index 5e0b0ede4e4..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Outdoor.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Location; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an Outdoor. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Outdoor", label = "Outdoor", synonyms = "", description = "") -public interface Outdoor extends Location { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Patio.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Patio.java deleted file mode 100644 index 673db0ae90f..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Patio.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Patio. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Outdoor_Patio", label = "Patio", synonyms = "Patios", description = "") -public interface Patio extends Outdoor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Porch.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Porch.java deleted file mode 100644 index 239fb09fecb..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Porch.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Porch. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Outdoor_Porch", label = "Porch", synonyms = "Porches", description = "") -public interface Porch extends Outdoor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Room.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Room.java deleted file mode 100644 index fcde06a6013..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Room.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Room. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Room", label = "Room", synonyms = "Rooms", description = "") -public interface Room extends Indoor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/SecondFloor.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/SecondFloor.java deleted file mode 100644 index 9dd926a9a5d..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/SecondFloor.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Second Floor. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Floor_SecondFloor", label = "Second Floor", synonyms = "Second Floors", description = "") -public interface SecondFloor extends Floor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Shed.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Shed.java deleted file mode 100644 index 016b82fd6ce..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Shed.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Shed. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Building_Shed", label = "Shed", synonyms = "Sheds", description = "") -public interface Shed extends Building { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/SummerHouse.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/SummerHouse.java deleted file mode 100644 index 2aabcebaa04..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/SummerHouse.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Summer House. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Building_SummerHouse", label = "Summer House", synonyms = "Summer Houses, Second Home, Second Homes", description = "") -public interface SummerHouse extends Building { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Terrace.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Terrace.java deleted file mode 100644 index 9d1ac8800b2..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Terrace.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Terrace. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Outdoor_Terrace", label = "Terrace", synonyms = "Terraces, Deck, Decks", description = "") -public interface Terrace extends Outdoor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/ThirdFloor.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/ThirdFloor.java deleted file mode 100644 index 02f6fe4a2a9..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/ThirdFloor.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Third Floor. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Floor_ThirdFloor", label = "Third Floor", synonyms = "Third Floors", description = "") -public interface ThirdFloor extends Floor { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Veranda.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Veranda.java deleted file mode 100644 index 2138ef6aba3..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/location/Veranda.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.location; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Veranda. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Location_Indoor_Room_Veranda", label = "Veranda", synonyms = "Verandas", description = "") -public interface Veranda extends Room { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Alarm.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Alarm.java deleted file mode 100644 index 580573b6c22..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Alarm.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.point; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Point; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an Alarm. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Point_Alarm", label = "Alarm", synonyms = "", description = "") -public interface Alarm extends Point { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Control.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Control.java deleted file mode 100644 index 963d242091b..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Control.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.point; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Point; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Control. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Point_Control", label = "Control", synonyms = "", description = "") -public interface Control extends Point { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/LowBattery.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/LowBattery.java deleted file mode 100644 index 52b9708f90a..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/LowBattery.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.point; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a LowBattery. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Point_Status_LowBattery", label = "LowBattery", synonyms = "", description = "") -public interface LowBattery extends Status { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Measurement.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Measurement.java deleted file mode 100644 index 7cf717f6c12..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Measurement.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.point; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Point; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Measurement. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Point_Measurement", label = "Measurement", synonyms = "", description = "") -public interface Measurement extends Point { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/OpenLevel.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/OpenLevel.java deleted file mode 100644 index 374d422f57d..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/OpenLevel.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.point; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an OpenLevel. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Point_Status_OpenLevel", label = "OpenLevel", synonyms = "", description = "") -public interface OpenLevel extends Status { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/OpenState.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/OpenState.java deleted file mode 100644 index a03d0918e02..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/OpenState.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.point; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an OpenState. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Point_Status_OpenState", label = "OpenState", synonyms = "", description = "") -public interface OpenState extends Status { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Points.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Points.java deleted file mode 100644 index d7950695f1e..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Points.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.point; - -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Stream; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Point; - -/** - * This class provides a stream of all defined points. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -public class Points { - - static final Set> POINTS = new HashSet<>(); - - static { - POINTS.add(Point.class); - POINTS.add(Alarm.class); - POINTS.add(Control.class); - POINTS.add(LowBattery.class); - POINTS.add(Measurement.class); - POINTS.add(OpenLevel.class); - POINTS.add(OpenState.class); - POINTS.add(Setpoint.class); - POINTS.add(Status.class); - POINTS.add(Switch.class); - POINTS.add(Tampered.class); - POINTS.add(Tilt.class); - } - - public static Stream> stream() { - return POINTS.stream(); - } - - public static boolean add(Class tag) { - return POINTS.add(tag); - } -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Setpoint.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Setpoint.java deleted file mode 100644 index 68bc3136b12..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Setpoint.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.point; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Point; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Setpoint. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Point_Setpoint", label = "Setpoint", synonyms = "", description = "") -public interface Setpoint extends Point { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Status.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Status.java deleted file mode 100644 index d3a844a9c79..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Status.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.point; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Point; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Status. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Point_Status", label = "Status", synonyms = "", description = "") -public interface Status extends Point { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Switch.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Switch.java deleted file mode 100644 index 0fbb27945f0..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Switch.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.point; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Switch. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Point_Control_Switch", label = "Switch", synonyms = "", description = "") -public interface Switch extends Control { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Tampered.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Tampered.java deleted file mode 100644 index ac65fdb63ab..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/point/Tampered.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.point; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Tampered. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Point_Status_Tampered", label = "Tampered", synonyms = "", description = "") -public interface Tampered extends Status { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/CO.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/CO.java deleted file mode 100644 index cf5c8824a29..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/CO.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a CO. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_CO", label = "CO", synonyms = "Carbon Monoxide", description = "") -public interface CO extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/CO2.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/CO2.java deleted file mode 100644 index 13d29f113f7..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/CO2.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a CO2. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_CO2", label = "CO2", synonyms = "Carbon Dioxide", description = "") -public interface CO2 extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/ColorTemperature.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/ColorTemperature.java deleted file mode 100644 index 3c1e2ee1f89..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/ColorTemperature.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Color Temperature. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_ColorTemperature", label = "Color Temperature", synonyms = "", description = "") -public interface ColorTemperature extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Current.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Current.java deleted file mode 100644 index 2f944e0c003..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Current.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Current. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Current", label = "Current", synonyms = "", description = "") -public interface Current extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Duration.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Duration.java deleted file mode 100644 index 7ef6b3c766f..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Duration.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Duration. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Duration", label = "Duration", synonyms = "", description = "") -public interface Duration extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Energy.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Energy.java deleted file mode 100644 index e52abd5ae9c..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Energy.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an Energy. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Energy", label = "Energy", synonyms = "", description = "") -public interface Energy extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Frequency.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Frequency.java deleted file mode 100644 index 5458939f559..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Frequency.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Frequency. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Frequency", label = "Frequency", synonyms = "", description = "") -public interface Frequency extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Gas.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Gas.java deleted file mode 100644 index 94d71e28c1b..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Gas.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Gas. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Gas", label = "Gas", synonyms = "", description = "") -public interface Gas extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Humidity.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Humidity.java deleted file mode 100644 index 6d2781b9d87..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Humidity.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Humidity. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Humidity", label = "Humidity", synonyms = "Moisture", description = "") -public interface Humidity extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Level.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Level.java deleted file mode 100644 index 85411489ebb..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Level.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Level. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Level", label = "Level", synonyms = "", description = "") -public interface Level extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Light.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Light.java deleted file mode 100644 index 81d999cea9c..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Light.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Light. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Light", label = "Light", synonyms = "Lights, Lighting", description = "") -public interface Light extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Noise.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Noise.java deleted file mode 100644 index f59869d58bb..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Noise.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Noise. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Noise", label = "Noise", synonyms = "", description = "") -public interface Noise extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Oil.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Oil.java deleted file mode 100644 index c90daa7e054..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Oil.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an Oil. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Oil", label = "Oil", synonyms = "", description = "") -public interface Oil extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Opening.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Opening.java deleted file mode 100644 index 1f1ba1dde1d..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Opening.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an Opening. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Opening", label = "Opening", synonyms = "", description = "") -public interface Opening extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Power.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Power.java deleted file mode 100644 index 3da7279a987..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Power.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Power. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Power", label = "Power", synonyms = "", description = "") -public interface Power extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Presence.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Presence.java deleted file mode 100644 index 1b3d2adeaf8..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Presence.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Presence. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Presence", label = "Presence", synonyms = "", description = "") -public interface Presence extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Pressure.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Pressure.java deleted file mode 100644 index c71a2a4a667..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Pressure.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Pressure. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Pressure", label = "Pressure", synonyms = "", description = "") -public interface Pressure extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Properties.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Properties.java deleted file mode 100644 index 06de5ebba85..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Properties.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Stream; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; - -/** - * This class provides a stream of all defined properties. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -public class Properties { - - static final Set> PROPERTIES = new HashSet<>(); - - static { - PROPERTIES.add(Property.class); - PROPERTIES.add(CO.class); - PROPERTIES.add(CO2.class); - PROPERTIES.add(ColorTemperature.class); - PROPERTIES.add(Current.class); - PROPERTIES.add(Duration.class); - PROPERTIES.add(Energy.class); - PROPERTIES.add(Frequency.class); - PROPERTIES.add(Gas.class); - PROPERTIES.add(Humidity.class); - PROPERTIES.add(Level.class); - PROPERTIES.add(Light.class); - PROPERTIES.add(Noise.class); - PROPERTIES.add(Oil.class); - PROPERTIES.add(Opening.class); - PROPERTIES.add(Power.class); - PROPERTIES.add(Presence.class); - PROPERTIES.add(Pressure.class); - PROPERTIES.add(Rain.class); - PROPERTIES.add(Smoke.class); - PROPERTIES.add(SoundVolume.class); - PROPERTIES.add(Temperature.class); - PROPERTIES.add(Timestamp.class); - PROPERTIES.add(Ultraviolet.class); - PROPERTIES.add(Vibration.class); - PROPERTIES.add(Voltage.class); - PROPERTIES.add(Water.class); - PROPERTIES.add(Wind.class); - } - - public static Stream> stream() { - return PROPERTIES.stream(); - } - - public static boolean add(Class tag) { - return PROPERTIES.add(tag); - } -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Rain.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Rain.java deleted file mode 100644 index f4f53b5e98b..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Rain.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Rain. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Rain", label = "Rain", synonyms = "", description = "") -public interface Rain extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Smoke.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Smoke.java deleted file mode 100644 index 4aeea0f353a..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Smoke.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Smoke. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Smoke", label = "Smoke", synonyms = "", description = "") -public interface Smoke extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/SoundVolume.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/SoundVolume.java deleted file mode 100644 index 4a820588833..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/SoundVolume.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Sound Volume. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_SoundVolume", label = "Sound Volume", synonyms = "", description = "") -public interface SoundVolume extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Temperature.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Temperature.java deleted file mode 100644 index 94a4b4cbf0f..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Temperature.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Temperature. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Temperature", label = "Temperature", synonyms = "Temperatures", description = "") -public interface Temperature extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Timestamp.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Timestamp.java deleted file mode 100644 index b429c1b4e99..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Timestamp.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Timestamp. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Timestamp", label = "Timestamp", synonyms = "", description = "") -public interface Timestamp extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Ultraviolet.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Ultraviolet.java deleted file mode 100644 index 0c6266a845d..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Ultraviolet.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines an Ultraviolet. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Ultraviolet", label = "Ultraviolet", synonyms = "UV", description = "") -public interface Ultraviolet extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Vibration.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Vibration.java deleted file mode 100644 index 99f0aa58f90..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Vibration.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Vibration. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Vibration", label = "Vibration", synonyms = "", description = "") -public interface Vibration extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Voltage.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Voltage.java deleted file mode 100644 index e8e3c163d6a..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Voltage.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Voltage. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Voltage", label = "Voltage", synonyms = "", description = "") -public interface Voltage extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Water.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Water.java deleted file mode 100644 index 6bbe90ad11b..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Water.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Water. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Water", label = "Water", synonyms = "", description = "") -public interface Water extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Wind.java b/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Wind.java deleted file mode 100644 index 92a1e7c0e9d..00000000000 --- a/bundles/org.openhab.core.semantics/src/main/java/org/openhab/core/semantics/model/property/Wind.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.semantics.model.property; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.semantics.Property; -import org.openhab.core.semantics.TagInfo; - -/** - * This class defines a Wind. - * - * @author Generated from generateTagClasses.groovy - Initial contribution - */ -@NonNullByDefault -@TagInfo(id = "Property_Wind", label = "Wind", synonyms = "", description = "") -public interface Wind extends Property { -} diff --git a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticTagsTest.java b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticTagsTest.java index 56043f29e34..d17c798ea25 100644 --- a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticTagsTest.java +++ b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticTagsTest.java @@ -12,43 +12,48 @@ */ package org.openhab.core.semantics; -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.*; -import java.util.Locale; +import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.GenericItem; import org.openhab.core.items.GroupItem; import org.openhab.core.library.CoreItemFactory; -import org.openhab.core.semantics.model.equipment.CleaningRobot; -import org.openhab.core.semantics.model.equipment.Equipments; -import org.openhab.core.semantics.model.location.Bathroom; -import org.openhab.core.semantics.model.location.Kitchen; -import org.openhab.core.semantics.model.location.Locations; -import org.openhab.core.semantics.model.location.Room; -import org.openhab.core.semantics.model.point.Measurement; -import org.openhab.core.semantics.model.point.Points; -import org.openhab.core.semantics.model.property.Light; -import org.openhab.core.semantics.model.property.Properties; -import org.openhab.core.semantics.model.property.SoundVolume; -import org.openhab.core.semantics.model.property.Temperature; +import org.openhab.core.semantics.internal.SemanticTagRegistryImpl; +import org.openhab.core.semantics.model.DefaultSemanticTagProvider; /** * @author Kai Kreuzer - Initial contribution */ @NonNullByDefault +@ExtendWith(MockitoExtension.class) public class SemanticTagsTest { + private static final String CUSTOM_LOCATION = "CustomLocation"; + private static final String CUSTOM_EQUIPMENT = "CustomEquipment"; + private static final String CUSTOM_POINT = "CustomPoint"; + private static final String CUSTOM_PROPERTY = "CustomProperty"; + + private @Mock @NonNullByDefault({}) ManagedSemanticTagProvider managedSemanticTagProviderMock; + private @NonNullByDefault({}) GroupItem locationItem; private @NonNullByDefault({}) GroupItem equipmentItem; private @NonNullByDefault({}) GenericItem pointItem; + private @NonNullByDefault({}) Class roomTagClass; + private @NonNullByDefault({}) Class bathroomTagClass; + private @NonNullByDefault({}) Class cleaningRobotTagClass; + private @NonNullByDefault({}) Class measurementTagClass; + private @NonNullByDefault({}) Class temperatureTagClass; + @BeforeEach public void setup() { CoreItemFactory itemFactory = new CoreItemFactory(mock(UnitProvider.class)); @@ -62,85 +67,75 @@ public void setup() { pointItem = itemFactory.createItem(CoreItemFactory.NUMBER, "TestTemperature"); pointItem.addTag("Measurement"); pointItem.addTag("Temperature"); - } - - @Test - public void testByTagId() { - assertEquals(Location.class, SemanticTags.getById("Location")); - assertEquals(Room.class, SemanticTags.getById("Room")); - assertEquals(Room.class, SemanticTags.getById("Location_Indoor_Room")); - assertEquals(Bathroom.class, SemanticTags.getById("Bathroom")); - assertEquals(Bathroom.class, SemanticTags.getById("Room_Bathroom")); - assertEquals(Bathroom.class, SemanticTags.getById("Indoor_Room_Bathroom")); - assertEquals(Bathroom.class, SemanticTags.getById("Location_Indoor_Room_Bathroom")); - } - - @Test - public void testByLabel() { - assertEquals(Kitchen.class, SemanticTags.getByLabel("Kitchen", Locale.ENGLISH)); - assertEquals(Kitchen.class, SemanticTags.getByLabel("Küche", Locale.GERMAN)); - assertNull(SemanticTags.getByLabel("Bad", Locale.GERMAN)); - } - @Test - public void testByLabelOrSynonym() { - assertEquals(Kitchen.class, SemanticTags.getByLabelOrSynonym("Kitchen", Locale.ENGLISH).iterator().next()); - assertEquals(Kitchen.class, SemanticTags.getByLabelOrSynonym("Küche", Locale.GERMAN).iterator().next()); - assertEquals(Bathroom.class, SemanticTags.getByLabelOrSynonym("Badezimmer", Locale.GERMAN).iterator().next()); - } + SemanticTag customLocationTag = new SemanticTagImpl("Location_" + CUSTOM_LOCATION, null, null, List.of()); + SemanticTag customEquipmentTag = new SemanticTagImpl("Equipment_" + CUSTOM_EQUIPMENT, null, null, List.of()); + SemanticTag customPointTag = new SemanticTagImpl("Point_" + CUSTOM_POINT, null, null, List.of()); + SemanticTag customPropertyTag = new SemanticTagImpl("Property_" + CUSTOM_PROPERTY, null, null, List.of()); + when(managedSemanticTagProviderMock.getAll()) + .thenReturn(List.of(customLocationTag, customEquipmentTag, customPointTag, customPropertyTag)); + new SemanticTagRegistryImpl(new DefaultSemanticTagProvider(), managedSemanticTagProviderMock); - @Test - public void testGetLabel() { - assertEquals("Kitchen", SemanticTags.getLabel(Kitchen.class, Locale.ENGLISH)); - assertEquals("Sound Volume", SemanticTags.getLabel(SoundVolume.class, Locale.ENGLISH)); + roomTagClass = SemanticTags.getById("Location_Indoor_Room"); + bathroomTagClass = SemanticTags.getById("Location_Indoor_Room_Bathroom"); + cleaningRobotTagClass = SemanticTags.getById("Equipment_CleaningRobot"); + measurementTagClass = SemanticTags.getById("Point_Measurement"); + temperatureTagClass = SemanticTags.getById("Property_Temperature"); } @Test - public void testGetSynonyms() { - assertThat(SemanticTags.getSynonyms(Light.class, Locale.ENGLISH), hasItems("Lights", "Lighting")); + public void testTagClasses() { + assertNotNull(roomTagClass); + assertNotNull(bathroomTagClass); + assertNotNull(cleaningRobotTagClass); + assertNotNull(measurementTagClass); + assertNotNull(temperatureTagClass); } @Test - public void testGetDescription() { - Class tag = SemanticTags.add("TestDesc", Light.class, null, null, "Test Description"); - assertEquals("Test Description", SemanticTags.getDescription(tag, Locale.ENGLISH)); + public void testByTagId() { + assertEquals(Location.class, SemanticTags.getById("Location")); + assertEquals(roomTagClass, SemanticTags.getById("Room")); + assertEquals(roomTagClass, SemanticTags.getById("Indoor_Room")); + assertEquals(roomTagClass, SemanticTags.getById("Location_Indoor_Room")); + assertEquals(bathroomTagClass, SemanticTags.getById("Bathroom")); + assertEquals(bathroomTagClass, SemanticTags.getById("Room_Bathroom")); + assertEquals(bathroomTagClass, SemanticTags.getById("Indoor_Room_Bathroom")); + assertEquals(bathroomTagClass, SemanticTags.getById("Location_Indoor_Room_Bathroom")); } @Test public void testGetSemanticType() { - assertEquals(Bathroom.class, SemanticTags.getSemanticType(locationItem)); - assertEquals(CleaningRobot.class, SemanticTags.getSemanticType(equipmentItem)); - assertEquals(Measurement.class, SemanticTags.getSemanticType(pointItem)); + assertEquals(bathroomTagClass, SemanticTags.getSemanticType(locationItem)); + assertEquals(cleaningRobotTagClass, SemanticTags.getSemanticType(equipmentItem)); + assertEquals(measurementTagClass, SemanticTags.getSemanticType(pointItem)); } @Test public void testGetLocation() { - assertEquals(Bathroom.class, SemanticTags.getLocation(locationItem)); + assertEquals(bathroomTagClass, SemanticTags.getLocation(locationItem)); } @Test public void testGetEquipment() { - assertEquals(CleaningRobot.class, SemanticTags.getEquipment(equipmentItem)); + assertEquals(cleaningRobotTagClass, SemanticTags.getEquipment(equipmentItem)); } @Test public void testGetPoint() { - assertEquals(Measurement.class, SemanticTags.getPoint(pointItem)); + assertEquals(measurementTagClass, SemanticTags.getPoint(pointItem)); } @Test public void testGetProperty() { - assertEquals(Temperature.class, SemanticTags.getProperty(pointItem)); + assertEquals(temperatureTagClass, SemanticTags.getProperty(pointItem)); } @Test public void testAddLocation() { - String tagName = "CustomLocation"; - Class customTag = SemanticTags.add(tagName, Location.class); + String tagName = CUSTOM_LOCATION; + Class customTag = SemanticTags.getById(tagName); assertNotNull(customTag); - assertEquals(customTag, SemanticTags.getById(tagName)); - assertEquals(customTag, SemanticTags.getByLabel("Custom Location", Locale.getDefault())); - assertTrue(Locations.stream().toList().contains(customTag)); GroupItem myItem = new GroupItem("MyLocation"); myItem.addTag(tagName); @@ -148,22 +143,11 @@ public void testAddLocation() { assertEquals(customTag, SemanticTags.getLocation(myItem)); } - @Test - public void testAddLocationWithParentString() { - String tagName = "CustomLocationParentString"; - Class customTag = SemanticTags.add(tagName, "Location"); - assertNotNull(customTag); - assertTrue(Locations.stream().toList().contains(customTag)); - } - @Test public void testAddEquipment() { - String tagName = "CustomEquipment"; - Class customTag = SemanticTags.add(tagName, Equipment.class); + String tagName = CUSTOM_EQUIPMENT; + Class customTag = SemanticTags.getById(tagName); assertNotNull(customTag); - assertEquals(customTag, SemanticTags.getById(tagName)); - assertEquals(customTag, SemanticTags.getByLabel("Custom Equipment", Locale.getDefault())); - assertTrue(Equipments.stream().toList().contains(customTag)); GroupItem myItem = new GroupItem("MyEquipment"); myItem.addTag(tagName); @@ -171,22 +155,11 @@ public void testAddEquipment() { assertEquals(customTag, SemanticTags.getEquipment(myItem)); } - @Test - public void testAddEquipmentWithParentString() { - String tagName = "CustomEquipmentParentString"; - Class customTag = SemanticTags.add(tagName, "Television"); - assertNotNull(customTag); - assertTrue(Equipments.stream().toList().contains(customTag)); - } - @Test public void testAddPoint() { - String tagName = "CustomPoint"; - Class customTag = SemanticTags.add(tagName, Point.class); + String tagName = CUSTOM_POINT; + Class customTag = SemanticTags.getById(tagName); assertNotNull(customTag); - assertEquals(customTag, SemanticTags.getById(tagName)); - assertEquals(customTag, SemanticTags.getByLabel("Custom Point", Locale.getDefault())); - assertTrue(Points.stream().toList().contains(customTag)); GroupItem myItem = new GroupItem("MyItem"); myItem.addTag(tagName); @@ -194,57 +167,15 @@ public void testAddPoint() { assertEquals(customTag, SemanticTags.getPoint(myItem)); } - @Test - public void testAddPointParentString() { - String tagName = "CustomPointParentString"; - Class customTag = SemanticTags.add(tagName, "Control"); - assertNotNull(customTag); - assertTrue(Points.stream().toList().contains(customTag)); - } - @Test public void testAddProperty() { - String tagName = "CustomProperty"; - Class customTag = SemanticTags.add(tagName, Property.class); + String tagName = CUSTOM_PROPERTY; + Class customTag = SemanticTags.getById(tagName); assertNotNull(customTag); - assertEquals(customTag, SemanticTags.getById(tagName)); - assertEquals(customTag, SemanticTags.getByLabel("Custom Property", Locale.getDefault())); - assertTrue(Properties.stream().toList().contains(customTag)); GroupItem myItem = new GroupItem("MyItem"); myItem.addTag(tagName); assertEquals(customTag, SemanticTags.getProperty(myItem)); } - - @Test - public void testAddPropertyParentString() { - String tagName = "CustomPropertyParentString"; - Class customTag = SemanticTags.add(tagName, "Property"); - assertNotNull(customTag); - assertTrue(Properties.stream().toList().contains(customTag)); - } - - @Test - public void testAddingExistingTagShouldFail() { - assertNull(SemanticTags.add("Room", Location.class)); - - assertNotNull(SemanticTags.add("CustomLocation1", Location.class)); - assertNull(SemanticTags.add("CustomLocation1", Location.class)); - } - - @Test - public void testAddWithCustomLabel() { - Class tag = SemanticTags.add("CustomProperty2", Property.class, " Custom Label ", null, null); - assertEquals(tag, SemanticTags.getByLabel("Custom Label", Locale.getDefault())); - } - - @Test - public void testAddWithSynonyms() { - String synonyms = " Synonym1, Synonym2 , Synonym With Space "; - Class tag = SemanticTags.add("CustomProperty3", Property.class, null, synonyms, null); - assertEquals(tag, SemanticTags.getByLabelOrSynonym("Synonym1", Locale.getDefault()).get(0)); - assertEquals(tag, SemanticTags.getByLabelOrSynonym("Synonym2", Locale.getDefault()).get(0)); - assertEquals(tag, SemanticTags.getByLabelOrSynonym("Synonym With Space", Locale.getDefault()).get(0)); - } } diff --git a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticsPredicatesTest.java b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticsPredicatesTest.java index 988a5a46c56..947ecaca92f 100644 --- a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticsPredicatesTest.java +++ b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/SemanticsPredicatesTest.java @@ -13,17 +13,23 @@ package org.openhab.core.semantics; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.*; + +import java.util.List; +import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.GenericItem; import org.openhab.core.items.GroupItem; import org.openhab.core.library.CoreItemFactory; -import org.openhab.core.semantics.model.property.Humidity; -import org.openhab.core.semantics.model.property.Temperature; +import org.openhab.core.semantics.internal.SemanticTagRegistryImpl; +import org.openhab.core.semantics.model.DefaultSemanticTagProvider; /** * These are tests for {@link SemanticsPredicates}. @@ -31,8 +37,11 @@ * @author Christoph Weitkamp - Initial contribution */ @NonNullByDefault +@ExtendWith(MockitoExtension.class) public class SemanticsPredicatesTest { + private @Mock @NonNullByDefault({}) ManagedSemanticTagProvider managedSemanticTagProviderMock; + private @NonNullByDefault({}) GroupItem locationItem; private @NonNullByDefault({}) GroupItem equipmentItem; private @NonNullByDefault({}) GenericItem pointItem; @@ -50,6 +59,9 @@ public void setup() { pointItem = itemFactory.createItem(CoreItemFactory.NUMBER, "TestTemperature"); pointItem.addTag("Measurement"); pointItem.addTag("Temperature"); + + when(managedSemanticTagProviderMock.getAll()).thenReturn(List.of()); + new SemanticTagRegistryImpl(new DefaultSemanticTagProvider(), managedSemanticTagProviderMock); } @Test @@ -75,9 +87,13 @@ public void testIsPoint() { @Test public void testRelatesTo() { - assertFalse(SemanticsPredicates.relatesTo(Temperature.class).test(locationItem)); - assertFalse(SemanticsPredicates.relatesTo(Temperature.class).test(equipmentItem)); - assertTrue(SemanticsPredicates.relatesTo(Temperature.class).test(pointItem)); - assertFalse(SemanticsPredicates.relatesTo(Humidity.class).test(equipmentItem)); + Class temperatureTagClass = (Class) Objects + .requireNonNull(SemanticTags.getById("Property_Temperature")); + Class humidityTagClass = (Class) Objects + .requireNonNull(SemanticTags.getById("Property_Humidity")); + assertFalse(SemanticsPredicates.relatesTo(temperatureTagClass).test(locationItem)); + assertFalse(SemanticsPredicates.relatesTo(temperatureTagClass).test(equipmentItem)); + assertTrue(SemanticsPredicates.relatesTo(temperatureTagClass).test(pointItem)); + assertFalse(SemanticsPredicates.relatesTo(humidityTagClass).test(equipmentItem)); } } diff --git a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/internal/SemanticTagRegistryImplTest.java b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/internal/SemanticTagRegistryImplTest.java new file mode 100644 index 00000000000..80532c3d92c --- /dev/null +++ b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/internal/SemanticTagRegistryImplTest.java @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.semantics.internal; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openhab.core.semantics.Equipment; +import org.openhab.core.semantics.Location; +import org.openhab.core.semantics.ManagedSemanticTagProvider; +import org.openhab.core.semantics.Point; +import org.openhab.core.semantics.Property; +import org.openhab.core.semantics.SemanticTag; +import org.openhab.core.semantics.SemanticTagImpl; +import org.openhab.core.semantics.SemanticTagRegistry; +import org.openhab.core.semantics.Tag; +import org.openhab.core.semantics.model.DefaultSemanticTagProvider; + +/** + * @author Laurent Garnier - Initial contribution + */ +@ExtendWith(MockitoExtension.class) +@NonNullByDefault +public class SemanticTagRegistryImplTest { + + private @Mock @NonNullByDefault({}) ManagedSemanticTagProvider managedSemanticTagProviderMock; + private @NonNullByDefault({}) SemanticTagRegistry semanticTagRegistry; + + private @NonNullByDefault({}) SemanticTag userLocationTag; + private @NonNullByDefault({}) SemanticTag userSubLocationTag; + + private @NonNullByDefault({}) Class roomTagClass; + private @NonNullByDefault({}) Class bathroomTagClass; + private @NonNullByDefault({}) Class cleaningRobotTagClass; + private @NonNullByDefault({}) Class measurementTagClass; + private @NonNullByDefault({}) Class temperatureTagClass; + + @BeforeEach + public void setup() throws Exception { + userLocationTag = new SemanticTagImpl("Location_UserLocation", "Custom label", "Custom description", + " Synonym1, Synonym2 , Synonym With Space "); + userSubLocationTag = new SemanticTagImpl("Location_UserLocation_UserSubLocation", null, null, List.of()); + when(managedSemanticTagProviderMock.getAll()).thenReturn(List.of(userLocationTag, userSubLocationTag)); + semanticTagRegistry = new SemanticTagRegistryImpl(new DefaultSemanticTagProvider(), + managedSemanticTagProviderMock); + + roomTagClass = semanticTagRegistry.getTagClassById("Location_Indoor_Room"); + bathroomTagClass = semanticTagRegistry.getTagClassById("Location_Indoor_Room_Bathroom"); + cleaningRobotTagClass = semanticTagRegistry.getTagClassById("Equipment_CleaningRobot"); + measurementTagClass = semanticTagRegistry.getTagClassById("Point_Measurement"); + temperatureTagClass = semanticTagRegistry.getTagClassById("Property_Temperature"); + } + + @Test + public void testGetById() { + assertEquals(Location.class, semanticTagRegistry.getTagClassById("Location")); + assertEquals(roomTagClass, semanticTagRegistry.getTagClassById("Room")); + assertEquals(roomTagClass, semanticTagRegistry.getTagClassById("Indoor_Room")); + assertEquals(roomTagClass, semanticTagRegistry.getTagClassById("Location_Indoor_Room")); + assertEquals(bathroomTagClass, semanticTagRegistry.getTagClassById("Bathroom")); + assertEquals(bathroomTagClass, semanticTagRegistry.getTagClassById("Room_Bathroom")); + assertEquals(bathroomTagClass, semanticTagRegistry.getTagClassById("Indoor_Room_Bathroom")); + assertEquals(bathroomTagClass, semanticTagRegistry.getTagClassById("Location_Indoor_Room_Bathroom")); + } + + @Test + public void testBuildId() { + assertEquals("Location", SemanticTagRegistryImpl.buildId(Location.class)); + assertEquals("Location_Indoor_Room", SemanticTagRegistryImpl.buildId(roomTagClass)); + assertEquals("Location_Indoor_Room_Bathroom", SemanticTagRegistryImpl.buildId(bathroomTagClass)); + assertEquals("Equipment", SemanticTagRegistryImpl.buildId(Equipment.class)); + assertEquals("Equipment_CleaningRobot", SemanticTagRegistryImpl.buildId(cleaningRobotTagClass)); + assertEquals("Point", SemanticTagRegistryImpl.buildId(Point.class)); + assertEquals("Point_Measurement", SemanticTagRegistryImpl.buildId(measurementTagClass)); + assertEquals("Property", SemanticTagRegistryImpl.buildId(Property.class)); + assertEquals("Property_Temperature", SemanticTagRegistryImpl.buildId(temperatureTagClass)); + } + + @Test + public void testIsEditable() { + when(managedSemanticTagProviderMock.get(eq("Location"))).thenReturn(null); + when(managedSemanticTagProviderMock.get(eq("Location_Indoor"))).thenReturn(null); + when(managedSemanticTagProviderMock.get(eq("Location_Indoor_Room"))).thenReturn(null); + when(managedSemanticTagProviderMock.get(eq("Location_Indoor_Room_Bathroom"))).thenReturn(null); + when(managedSemanticTagProviderMock.get(eq("Location_UserLocation"))).thenReturn(userLocationTag); + when(managedSemanticTagProviderMock.get(eq("Location_UserLocation_UserSubLocation"))) + .thenReturn(userSubLocationTag); + + assertFalse(semanticTagRegistry.isEditable(Objects.requireNonNull(semanticTagRegistry.get("Location")))); + assertFalse(semanticTagRegistry.isEditable(Objects.requireNonNull(semanticTagRegistry.get("Location_Indoor")))); + assertFalse(semanticTagRegistry + .isEditable(Objects.requireNonNull(semanticTagRegistry.get("Location_Indoor_Room")))); + assertFalse(semanticTagRegistry + .isEditable(Objects.requireNonNull(semanticTagRegistry.get("Location_Indoor_Room_Bathroom")))); + assertTrue(semanticTagRegistry + .isEditable(Objects.requireNonNull(semanticTagRegistry.get("Location_UserLocation")))); + assertTrue(semanticTagRegistry + .isEditable(Objects.requireNonNull(semanticTagRegistry.get("Location_UserLocation_UserSubLocation")))); + } +} diff --git a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/internal/SemanticsMetadataProviderTest.java b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/internal/SemanticsMetadataProviderTest.java index 90a5edc6cff..a8de24199fc 100644 --- a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/internal/SemanticsMetadataProviderTest.java +++ b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/internal/SemanticsMetadataProviderTest.java @@ -16,6 +16,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; +import java.util.List; import java.util.Objects; import org.eclipse.jdt.annotation.NonNull; @@ -36,6 +37,9 @@ import org.openhab.core.items.ItemRegistry; import org.openhab.core.items.Metadata; import org.openhab.core.library.items.SwitchItem; +import org.openhab.core.semantics.ManagedSemanticTagProvider; +import org.openhab.core.semantics.SemanticTagRegistry; +import org.openhab.core.semantics.model.DefaultSemanticTagProvider; /** * @author Simon Lamon - Initial contribution @@ -49,14 +53,19 @@ public class SemanticsMetadataProviderTest { private static final String GROUP_ITEM_NAME = "groupItem"; - private @NonNullByDefault({}) @Mock ItemRegistry itemRegistry; - private @NonNullByDefault({}) @Mock ProviderChangeListener<@NonNull Metadata> changeListener; + private @Mock @NonNullByDefault({}) ItemRegistry itemRegistry; + private @Mock @NonNullByDefault({}) ProviderChangeListener<@NonNull Metadata> changeListener; + private @Mock @NonNullByDefault({}) ManagedSemanticTagProvider managedSemanticTagProviderMock; private @NonNullByDefault({}) SemanticsMetadataProvider semanticsMetadataProvider; @BeforeEach public void beforeEach() throws Exception { - semanticsMetadataProvider = new SemanticsMetadataProvider(itemRegistry) { + when(managedSemanticTagProviderMock.getAll()).thenReturn(List.of()); + SemanticTagRegistry semanticTagRegistry = new SemanticTagRegistryImpl(new DefaultSemanticTagProvider(), + managedSemanticTagProviderMock); + + semanticsMetadataProvider = new SemanticsMetadataProvider(itemRegistry, semanticTagRegistry) { { addProviderChangeListener(changeListener); } diff --git a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/internal/SemanticsServiceImplTest.java b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/internal/SemanticsServiceImplTest.java index 3ff77cb163d..4e95ba4044d 100644 --- a/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/internal/SemanticsServiceImplTest.java +++ b/bundles/org.openhab.core.semantics/src/test/java/org/openhab/core/semantics/internal/SemanticsServiceImplTest.java @@ -12,9 +12,11 @@ */ package org.openhab.core.semantics.internal; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import java.util.List; import java.util.Locale; import java.util.Set; import java.util.stream.Stream; @@ -32,11 +34,17 @@ import org.openhab.core.items.ItemRegistry; import org.openhab.core.items.MetadataRegistry; import org.openhab.core.library.CoreItemFactory; -import org.openhab.core.semantics.model.location.Bathroom; -import org.openhab.core.semantics.model.location.LivingRoom; +import org.openhab.core.semantics.Location; +import org.openhab.core.semantics.ManagedSemanticTagProvider; +import org.openhab.core.semantics.SemanticTag; +import org.openhab.core.semantics.SemanticTagImpl; +import org.openhab.core.semantics.SemanticTagRegistry; +import org.openhab.core.semantics.Tag; +import org.openhab.core.semantics.model.DefaultSemanticTagProvider; /** * @author Kai Kreuzer - Initial contribution + * @author Laurent Garnier - Tests added for methods moved from SemanticTags to SemanticsService */ @ExtendWith(MockitoExtension.class) @NonNullByDefault @@ -45,11 +53,18 @@ public class SemanticsServiceImplTest { private @Mock @NonNullByDefault({}) ItemRegistry itemRegistryMock; private @Mock @NonNullByDefault({}) MetadataRegistry metadataRegistryMock; private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock; + private @Mock @NonNullByDefault({}) ManagedSemanticTagProvider managedSemanticTagProviderMock; private @NonNullByDefault({}) GroupItem locationItem; private @NonNullByDefault({}) GroupItem equipmentItem; private @NonNullByDefault({}) GenericItem pointItem; + private @NonNullByDefault({}) Class roomTagClass; + private @NonNullByDefault({}) Class bathroomTagClass; + private @NonNullByDefault({}) Class livingRoomTagClass; + private @NonNullByDefault({}) Class userLocationTagClass; + private @NonNullByDefault({}) Class cleaningRobotTagClass; + private @NonNullByDefault({}) SemanticsServiceImpl service; @BeforeEach @@ -69,28 +84,149 @@ public void setup() throws Exception { pointItem.addGroupName(locationItem.getName()); locationItem.addMember(pointItem); - when(itemRegistryMock.stream()).thenReturn(Stream.of(locationItem, equipmentItem, pointItem)) - .thenReturn(Stream.of(locationItem, equipmentItem, pointItem)) - .thenReturn(Stream.of(locationItem, equipmentItem, pointItem)); + SemanticTag userLocationTag = new SemanticTagImpl("Location_UserLocation", "Custom label", "Custom description", + " Synonym1, Synonym2 , Synonym With Space "); + when(managedSemanticTagProviderMock.getAll()).thenReturn(List.of(userLocationTag)); + SemanticTagRegistry semanticTagRegistry = new SemanticTagRegistryImpl(new DefaultSemanticTagProvider(), + managedSemanticTagProviderMock); + + roomTagClass = semanticTagRegistry.getTagClassById("Location_Indoor_Room"); + bathroomTagClass = semanticTagRegistry.getTagClassById("Location_Indoor_Room_Bathroom"); + livingRoomTagClass = semanticTagRegistry.getTagClassById("Location_Indoor_Room_LivingRoom"); + userLocationTagClass = semanticTagRegistry.getTagClassById("Location_UserLocation"); + cleaningRobotTagClass = semanticTagRegistry.getTagClassById("Equipment_CleaningRobot"); - service = new SemanticsServiceImpl(itemRegistryMock, metadataRegistryMock); + service = new SemanticsServiceImpl(itemRegistryMock, metadataRegistryMock, semanticTagRegistry); } @Test public void testGetItemsInLocation() throws Exception { - Set items = service.getItemsInLocation(Bathroom.class); + when(itemRegistryMock.stream()).thenReturn(Stream.of(locationItem, equipmentItem, pointItem)) + .thenReturn(Stream.of(locationItem, equipmentItem, pointItem)) + .thenReturn(Stream.of(locationItem, equipmentItem, pointItem)); + + Set items = service.getItemsInLocation((Class) bathroomTagClass); + assertEquals(1, items.size()); assertTrue(items.contains(pointItem)); - items = service.getItemsInLocation("Room", Locale.ENGLISH); + items = service.getItemsInLocation((Class) roomTagClass); + assertEquals(1, items.size()); assertTrue(items.contains(pointItem)); + + items = service.getItemsInLocation((Class) livingRoomTagClass); + assertTrue(items.isEmpty()); } @Test public void testGetItemsInLocationByString() throws Exception { + when(itemRegistryMock.stream()).thenReturn(Stream.of(locationItem, equipmentItem, pointItem)) + .thenReturn(Stream.of(locationItem, equipmentItem, pointItem)) + .thenReturn(Stream.of(locationItem, equipmentItem, pointItem)) + .thenReturn(Stream.of(locationItem, equipmentItem, pointItem)) + .thenReturn(Stream.of(locationItem, equipmentItem, pointItem)) + .thenReturn(Stream.of(locationItem, equipmentItem, pointItem)); + when(metadataRegistryMock.get(any())).thenReturn(null); + + // Label of a location group item Set items = service.getItemsInLocation("joe's room", Locale.ENGLISH); + assertEquals(1, items.size()); assertTrue(items.contains(pointItem)); - items = service.getItemsInLocation(LivingRoom.class); + // Location tag label + items = service.getItemsInLocation("bathroom", Locale.ENGLISH); + assertEquals(1, items.size()); + assertTrue(items.contains(pointItem)); + + // Location tag synonym + items = service.getItemsInLocation("powder room", Locale.ENGLISH); + assertEquals(1, items.size()); + assertTrue(items.contains(pointItem)); + + // Location parent tag label + items = service.getItemsInLocation("Room", Locale.ENGLISH); + assertEquals(1, items.size()); + assertTrue(items.contains(pointItem)); + + // Existing item label + items = service.getItemsInLocation("my Test label", Locale.ENGLISH); + assertTrue(items.isEmpty()); + + // Unknown item label + items = service.getItemsInLocation("wrong label", Locale.ENGLISH); assertTrue(items.isEmpty()); } + + @Test + public void testGetLabelAndSynonyms() { + List result = service.getLabelAndSynonyms(bathroomTagClass, Locale.ENGLISH); + assertEquals(6, result.size()); + assertEquals("bathroom", result.get(0)); + assertEquals("bathrooms", result.get(1)); + assertEquals("bath", result.get(2)); + assertEquals("baths", result.get(3)); + assertEquals("powder room", result.get(4)); + assertEquals("powder rooms", result.get(5)); + + result = service.getLabelAndSynonyms(cleaningRobotTagClass, Locale.FRENCH); + assertEquals(4, result.size()); + assertEquals("robot de nettoyage", result.get(0)); + assertEquals("robos de nettoyage", result.get(1)); + assertEquals("robot aspirateur", result.get(2)); + assertEquals("robots aspirateur", result.get(3)); + + result = service.getLabelAndSynonyms(userLocationTagClass, Locale.ENGLISH); + assertEquals(4, result.size()); + assertEquals("custom label", result.get(0)); + assertEquals("synonym1", result.get(1)); + assertEquals("synonym2", result.get(2)); + assertEquals("synonym with space", result.get(3)); + } + + @Test + public void testGetByLabel() { + Class tag = service.getByLabel("BATHROOM", Locale.ENGLISH); + assertEquals(bathroomTagClass, tag); + tag = service.getByLabel("Bath", Locale.ENGLISH); + assertNull(tag); + + tag = service.getByLabel("ROBOT de nettoyage", Locale.FRENCH); + assertEquals(cleaningRobotTagClass, tag); + tag = service.getByLabel("Robot aspirateur", Locale.FRENCH); + assertNull(tag); + + tag = service.getByLabel("CUSTOM label", Locale.ENGLISH); + assertEquals(userLocationTagClass, tag); + tag = service.getByLabel("Synonym1", Locale.ENGLISH); + assertNull(tag); + } + + @Test + public void testGetByLabelOrSynonym() { + List> tags = service.getByLabelOrSynonym("BATHROOM", Locale.ENGLISH); + assertEquals(1, tags.size()); + assertEquals(bathroomTagClass, tags.get(0)); + tags = service.getByLabelOrSynonym("POWDER Rooms", Locale.ENGLISH); + assertEquals(1, tags.size()); + assertEquals(bathroomTagClass, tags.get(0)); + tags = service.getByLabelOrSynonym("other bath", Locale.ENGLISH); + assertTrue(tags.isEmpty()); + + tags = service.getByLabelOrSynonym("ROBOT de nettoyage", Locale.FRENCH); + assertEquals(1, tags.size()); + assertEquals(cleaningRobotTagClass, tags.get(0)); + tags = service.getByLabelOrSynonym("ROBOTS aspirateur", Locale.FRENCH); + assertEquals(1, tags.size()); + assertEquals(cleaningRobotTagClass, tags.get(0)); + tags = service.getByLabelOrSynonym("Robot cuiseur", Locale.FRENCH); + assertTrue(tags.isEmpty()); + + tags = service.getByLabelOrSynonym("CUSTOM label", Locale.ENGLISH); + assertEquals(1, tags.size()); + assertEquals(userLocationTagClass, tags.get(0)); + tags = service.getByLabelOrSynonym("Synonym with space", Locale.ENGLISH); + assertEquals(1, tags.size()); + assertEquals(userLocationTagClass, tags.get(0)); + tags = service.getByLabelOrSynonym("wrong label", Locale.ENGLISH); + assertTrue(tags.isEmpty()); + } } From 8eddad5c76a690e189d3916644d13e4db63b34a2 Mon Sep 17 00:00:00 2001 From: Gwendal Roulleau Date: Fri, 16 Jun 2023 21:54:11 +0200 Subject: [PATCH 109/126] [audio] Enhance AudioSink capabilities using the AudioServlet (#3461) * [audio] More capabilities for AudioSink using the AudioServlet AudioServlet can now serve all type of AudioStream multiple times by buffering data in memory or in temporary file. Adding method to ease disposal of temporary file after playing a sound Adding an identifyier to audio stream for further development (allow audio sink to cache computation data) We can now send audio with a Runnable for a delayed task to be executed after. This delayed task includes temporary file deletion and volume restoration. This is a no breaking change / no behaviour modification for other addon AudioSink, as existing AudioSink must explicitly override the old behaviour to use this capability. Add AudioSinkSync / AudioSinkAsync abstract classes to use this capability easily. WebAudioSink now implements this capability, with the help of a modified AudioServlet Adding (approximative, better than nothing) sound duration computation method for MP3 and WAV. Use this sound duration computation to guess when the async sound is finished and when to do the post process (i.e. volume restoration) Signed-off-by: Gwendal Roulleau --- .../openhab/core/audio/AudioHTTPServer.java | 38 ++- .../org/openhab/core/audio/AudioManager.java | 11 + .../org/openhab/core/audio/AudioSink.java | 35 +++ .../openhab/core/audio/AudioSinkAsync.java | 113 +++++++++ .../org/openhab/core/audio/AudioSinkSync.java | 85 +++++++ .../org/openhab/core/audio/AudioStream.java | 11 + .../core/audio/ClonableAudioStream.java | 35 +++ .../openhab/core/audio/FileAudioStream.java | 17 +- .../core/audio/FixedLengthAudioStream.java | 17 +- .../org/openhab/core/audio/StreamServed.java | 30 +++ .../openhab/core/audio/URLAudioStream.java | 7 +- .../core/audio/internal/AudioManagerImpl.java | 87 ++++--- .../core/audio/internal/AudioServlet.java | 229 +++++++++++++----- .../javasound/JavaSoundAudioSink.java | 35 +-- .../internal/webaudio/WebAudioAudioSink.java | 40 +-- .../core/audio/utils/AudioSinkUtils.java | 43 ++++ .../core/audio/utils/AudioSinkUtilsImpl.java | 91 +++++++ .../internal/AbstractAudioServletTest.java | 8 +- .../core/audio/internal/AudioServletTest.java | 54 ++++- .../core/voice/internal/VoiceManagerImpl.java | 42 +--- .../internal/cache/AudioStreamFromCache.java | 9 +- .../voice/internal/cache/TTSLRUCacheImpl.java | 2 +- .../core/cache/lru/LRUMediaCacheEntry.java | 4 + .../org/openhab/core/common/Disposable.java | 28 +++ .../cache/lru/LRUMediaCacheEntryTest.java | 25 +- 25 files changed, 888 insertions(+), 208 deletions(-) create mode 100644 bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioSinkAsync.java create mode 100644 bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioSinkSync.java create mode 100644 bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/ClonableAudioStream.java create mode 100644 bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/StreamServed.java create mode 100644 bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/utils/AudioSinkUtils.java create mode 100644 bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/utils/AudioSinkUtilsImpl.java create mode 100644 bundles/org.openhab.core/src/main/java/org/openhab/core/common/Disposable.java diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioHTTPServer.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioHTTPServer.java index 9788b3acbdd..2cb85ee5833 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioHTTPServer.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioHTTPServer.java @@ -12,6 +12,9 @@ */ package org.openhab.core.audio; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.audio.internal.AudioServlet; @@ -34,19 +37,48 @@ public interface AudioHTTPServer { * * @param stream the stream to serve on HTTP * @return the relative URL to access the stream starting with a '/' + * @deprecated Use {@link AudioHTTPServer#serve(AudioStream, int, boolean, CompletableFuture)} */ + @Deprecated String serve(AudioStream stream); /** * Creates a relative url for a given {@link AudioStream} where it can be requested multiple times within the given * time frame. - * This method only accepts {@link FixedLengthAudioStream}s, since it needs to be able to create multiple concurrent - * streams from it, which isn't possible with a regular {@link AudioStream}. + * This method accepts all {@link AudioStream}s, but it is better to use {@link ClonableAudioStream}s. If generic + * {@link AudioStream} is used, the method tries to add the Clonable capability by storing it in a small memory + * buffer, e.g {@link ByteArrayAudioStream}, or in a cached file if the stream reached the buffer capacity, + * or fails if the stream is too long. * Streams are closed, once they expire. * * @param stream the stream to serve on HTTP * @param seconds number of seconds for which the stream is available through HTTP * @return the relative URL to access the stream starting with a '/' + * @deprecated Use {@link AudioHTTPServer#serve(AudioStream, int, boolean, CompletableFuture)} + */ + @Deprecated + String serve(AudioStream stream, int seconds); + + /** + * Creates a relative url for a given {@link AudioStream} where it can be requested one or multiple times within the + * given time frame. + * This method accepts all {@link AudioStream}s, but if multiTimeStream is set to true it is better to use + * {@link ClonableAudioStream}s. Otherwise, if a generic {@link AudioStream} is used, the method will then try + * to add the Clonable capability by storing it in a small memory buffer, e.g {@link ByteArrayAudioStream}, or in a + * cached file if the stream reached the buffer capacity, or fails to render the sound completely if the stream is + * too long. + * A {@link CompletableFuture} is used to inform the caller that the playback ends in order to clean + * resources and run delayed task, such as restoring volume. + * Streams are closed, once they expire. + * + * @param stream the stream to serve on HTTP + * @param seconds number of seconds for which the stream is available through HTTP. The stream will be deleted only + * if not started, so you can set a duration shorter than the track's duration. + * @param multiTimeStream set to true if this stream should be played multiple time, and thus needs to be made + * Cloneable if it is not already. + * @return information about the {@link StreamServed}, including the relative URL to access the stream starting with + * a '/', and a CompletableFuture to know when the playback ends. + * @throws IOException when the stream is not a {@link ClonableAudioStream} and we cannot get or store it on disk. */ - String serve(FixedLengthAudioStream stream, int seconds); + StreamServed serve(AudioStream stream, int seconds, boolean multiTimeStream) throws IOException; } diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioManager.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioManager.java index a60e5fe4172..d640c0f3996 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioManager.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioManager.java @@ -252,4 +252,15 @@ public interface AudioManager { * @return ids of matching sinks */ Set getSinkIds(String pattern); + + /** + * Handles a volume command change and returns a Runnable to restore it. + * Returning a Runnable allows us to have a no-op Runnable if changing volume back is not needed, and conveniently + * keeping it as one liner usable in a chain for the caller. + * + * @param volume The volume to set + * @param sink The sink to set the volume to + * @return A runnable to restore the volume to its previous value, or no-operation if no change is required. + */ + Runnable handleVolumeCommand(@Nullable PercentType volume, AudioSink sink); } diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioSink.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioSink.java index f1ecca1d43c..adc542a5ce4 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioSink.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioSink.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.util.Locale; import java.util.Set; +import java.util.concurrent.CompletableFuture; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -58,13 +59,47 @@ public interface AudioSink { * * In case the audioStream is null, this should be interpreted as a request to end any currently playing stream. * + * When the stream is not needed anymore, if the stream implements the {@link org.openhab.core.common.Disposable} + * interface, the sink should hereafter get rid of it by calling the dispose method. + * * @param audioStream the audio stream to play or null to keep quiet * @throws UnsupportedAudioFormatException If audioStream format is not supported * @throws UnsupportedAudioStreamException If audioStream is not supported + * @deprecated Use {@link AudioSink#processAndComplete(AudioStream)} */ + @Deprecated void process(@Nullable AudioStream audioStream) throws UnsupportedAudioFormatException, UnsupportedAudioStreamException; + /** + * Processes the passed {@link AudioStream}, and returns a CompletableFuture that should complete when the sound is + * fully played. It is the sink responsibility to complete this future. + * + * If the passed {@link AudioStream} is not supported by this instance, an {@link UnsupportedAudioStreamException} + * is thrown. + * + * If the passed {@link AudioStream} has an {@link AudioFormat} not supported by this instance, + * an {@link UnsupportedAudioFormatException} is thrown. + * + * In case the audioStream is null, this should be interpreted as a request to end any currently playing stream. + * + * When the stream is not needed anymore, if the stream implements the {@link org.openhab.core.common.Disposable} + * interface, the sink should hereafter get rid of it by calling the dispose method. + * + * @param audioStream the audio stream to play or null to keep quiet + * @return A future completed when the sound is fully played. The method can instead complete with + * UnsupportedAudioFormatException if the audioStream format is not supported, or + * UnsupportedAudioStreamException If audioStream is not supported + */ + default CompletableFuture<@Nullable Void> processAndComplete(@Nullable AudioStream audioStream) { + try { + process(audioStream); + } catch (UnsupportedAudioFormatException | UnsupportedAudioStreamException e) { + return CompletableFuture.failedFuture(e); + } + return CompletableFuture.completedFuture(null); + } + /** * Gets a set containing all supported audio formats * diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioSinkAsync.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioSinkAsync.java new file mode 100644 index 00000000000..c88d20c93c9 --- /dev/null +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioSinkAsync.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.audio; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.common.Disposable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Definition of an audio output like headphones, a speaker or for writing to + * a file / clip. + * Helper class for asynchronous sink : when the process() method returns, the {@link AudioStream} + * may or may not be played. It is the responsibility of the implementing AudioSink class to + * complete the CompletableFuture when playing is done. Any delayed tasks will then be performed, such as volume + * restoration. + * + * @author Gwendal Roulleau - Initial contribution + */ +@NonNullByDefault +public abstract class AudioSinkAsync implements AudioSink { + + private final Logger logger = LoggerFactory.getLogger(AudioSinkAsync.class); + + protected final Map> runnableByAudioStream = new HashMap<>(); + + @Override + public CompletableFuture<@Nullable Void> processAndComplete(@Nullable AudioStream audioStream) { + CompletableFuture<@Nullable Void> completableFuture = new CompletableFuture<@Nullable Void>(); + if (audioStream != null) { + runnableByAudioStream.put(audioStream, completableFuture); + } + try { + processAsynchronously(audioStream); + } catch (UnsupportedAudioFormatException | UnsupportedAudioStreamException e) { + completableFuture.completeExceptionally(e); + } + if (audioStream == null) { + // No need to delay the post process task + completableFuture.complete(null); + } + return completableFuture; + } + + @Override + public void process(@Nullable AudioStream audioStream) + throws UnsupportedAudioFormatException, UnsupportedAudioStreamException { + processAsynchronously(audioStream); + } + + /** + * Processes the passed {@link AudioStream} asynchronously. This method is expected to return before the stream is + * fully played. This is the sink responsibility to call the {@link AudioSinkAsync#playbackFinished(AudioStream)} + * when it is. + * + * If the passed {@link AudioStream} is not supported by this instance, an {@link UnsupportedAudioStreamException} + * is thrown. + * + * If the passed {@link AudioStream} has an {@link AudioFormat} not supported by this instance, + * an {@link UnsupportedAudioFormatException} is thrown. + * + * In case the audioStream is null, this should be interpreted as a request to end any currently playing stream. + * + * @param audioStream the audio stream to play or null to keep quiet + * @throws UnsupportedAudioFormatException If audioStream format is not supported + * @throws UnsupportedAudioStreamException If audioStream is not supported + */ + protected abstract void processAsynchronously(@Nullable AudioStream audioStream) + throws UnsupportedAudioFormatException, UnsupportedAudioStreamException; + + /** + * Will complete the future previously returned, allowing the core to run delayed task. + * + * @param audioStream The AudioStream is the key to find the delayed CompletableFuture in the storage. + */ + protected void playbackFinished(AudioStream audioStream) { + CompletableFuture<@Nullable Void> completableFuture = runnableByAudioStream.remove(audioStream); + if (completableFuture != null) { + completableFuture.complete(null); + } + + // if the stream is not needed anymore, then we should call back the AudioStream to let it a chance + // to auto dispose. + if (audioStream instanceof Disposable disposableAudioStream) { + try { + disposableAudioStream.dispose(); + } catch (IOException e) { + String fileName = audioStream instanceof FileAudioStream file ? file.toString() : "unknown"; + if (logger.isDebugEnabled()) { + logger.debug("Cannot dispose of stream {}", fileName, e); + } else { + logger.warn("Cannot dispose of stream {}, reason {}", fileName, e.getMessage()); + } + } + } + } +} diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioSinkSync.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioSinkSync.java new file mode 100644 index 00000000000..97c888f7468 --- /dev/null +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioSinkSync.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.audio; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.common.Disposable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Definition of an audio output like headphones, a speaker or for writing to + * a file / clip. + * Helper class for synchronous sink : when the process() method returns, + * the source is considered played, and could be disposed. + * Any delayed tasks can then be performed, such as volume restoration. + * + * @author Gwendal Roulleau - Initial contribution + */ +@NonNullByDefault +public abstract class AudioSinkSync implements AudioSink { + + private final Logger logger = LoggerFactory.getLogger(AudioSinkSync.class); + + @Override + public CompletableFuture<@Nullable Void> processAndComplete(@Nullable AudioStream audioStream) { + try { + processSynchronously(audioStream); + return CompletableFuture.completedFuture(null); + } catch (UnsupportedAudioFormatException | UnsupportedAudioStreamException e) { + return CompletableFuture.failedFuture(e); + } finally { + // as the stream is not needed anymore, we should dispose of it + if (audioStream instanceof Disposable disposableAudioStream) { + try { + disposableAudioStream.dispose(); + } catch (IOException e) { + String fileName = audioStream instanceof FileAudioStream file ? file.toString() : "unknown"; + if (logger.isDebugEnabled()) { + logger.debug("Cannot dispose of stream {}", fileName, e); + } else { + logger.warn("Cannot dispose of stream {}, reason {}", fileName, e.getMessage()); + } + } + } + } + } + + @Override + public void process(@Nullable AudioStream audioStream) + throws UnsupportedAudioFormatException, UnsupportedAudioStreamException { + processSynchronously(audioStream); + } + + /** + * Processes the passed {@link AudioStream} and returns only when the playback is ended. + * + * If the passed {@link AudioStream} is not supported by this instance, an {@link UnsupportedAudioStreamException} + * is thrown. + * + * If the passed {@link AudioStream} has an {@link AudioFormat} not supported by this instance, + * an {@link UnsupportedAudioFormatException} is thrown. + * + * In case the audioStream is null, this should be interpreted as a request to end any currently playing stream. + * + * @param audioStream the audio stream to play or null to keep quiet + * @throws UnsupportedAudioFormatException If audioStream format is not supported + * @throws UnsupportedAudioStreamException If audioStream is not supported + */ + protected abstract void processSynchronously(@Nullable AudioStream audioStream) + throws UnsupportedAudioFormatException, UnsupportedAudioStreamException; +} diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioStream.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioStream.java index c122aaf5fc6..560fcc21278 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioStream.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioStream.java @@ -15,6 +15,7 @@ import java.io.InputStream; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; /** * Wrapper for a source of audio data. @@ -37,4 +38,14 @@ public abstract class AudioStream extends InputStream { * @return The supported audio format */ public abstract AudioFormat getFormat(); + + /** + * Usefull for sinks playing the same stream multiple times, + * to avoid already done computation (like reencoding). + * + * @return A string uniquely identifying the stream. + */ + public @Nullable String getId() { + return null; + } } diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/ClonableAudioStream.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/ClonableAudioStream.java new file mode 100644 index 00000000000..111e1c1d2d9 --- /dev/null +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/ClonableAudioStream.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.audio; + +import java.io.InputStream; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * This is an {@link AudioStream}, that can be cloned + * + * @author Gwendal Roulleau - Initial contribution, separation from FixedLengthAudioStream + */ +@NonNullByDefault +public abstract class ClonableAudioStream extends AudioStream { + + /** + * Returns a new, fully independent stream instance, which can be read and closed without impacting the original + * instance. + * + * @return a new input stream that can be consumed by the caller + * @throws AudioException if stream cannot be created + */ + public abstract InputStream getClonedStream() throws AudioException; +} diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/FileAudioStream.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/FileAudioStream.java index 481d62353aa..7247742aaa6 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/FileAudioStream.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/FileAudioStream.java @@ -18,10 +18,12 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.audio.utils.AudioStreamUtils; import org.openhab.core.audio.utils.AudioWaveUtils; +import org.openhab.core.common.Disposable; /** * This is an AudioStream from an audio file @@ -31,7 +33,7 @@ * @author Christoph Weitkamp - Refactored use of filename extension */ @NonNullByDefault -public class FileAudioStream extends FixedLengthAudioStream { +public class FileAudioStream extends FixedLengthAudioStream implements Disposable { public static final String WAV_EXTENSION = "wav"; public static final String MP3_EXTENSION = "mp3"; @@ -42,16 +44,22 @@ public class FileAudioStream extends FixedLengthAudioStream { private final AudioFormat audioFormat; private InputStream inputStream; private final long length; + private final boolean isTemporaryFile; public FileAudioStream(File file) throws AudioException { this(file, getAudioFormat(file)); } public FileAudioStream(File file, AudioFormat format) throws AudioException { + this(file, format, false); + } + + public FileAudioStream(File file, AudioFormat format, boolean isTemporaryFile) throws AudioException { this.file = file; this.inputStream = getInputStream(file); this.audioFormat = format; this.length = file.length(); + this.isTemporaryFile = isTemporaryFile; } private static AudioFormat getAudioFormat(File file) throws AudioException { @@ -125,4 +133,11 @@ public synchronized void reset() throws IOException { public InputStream getClonedStream() throws AudioException { return getInputStream(file); } + + @Override + public void dispose() throws IOException { + if (isTemporaryFile) { + Files.delete(file.toPath()); + } + } } diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/FixedLengthAudioStream.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/FixedLengthAudioStream.java index 6e47d35b054..4a737d50f99 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/FixedLengthAudioStream.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/FixedLengthAudioStream.java @@ -12,18 +12,16 @@ */ package org.openhab.core.audio; -import java.io.InputStream; - import org.eclipse.jdt.annotation.NonNullByDefault; /** - * This is an {@link AudioStream}, which can provide information about its absolute length and is able to provide - * cloned streams. + * This is a {@link ClonableAudioStream}, which can also provide information about its absolute length. * * @author Kai Kreuzer - Initial contribution + * @author Gwendal Roulleau - Separate getClonedStream into its own class */ @NonNullByDefault -public abstract class FixedLengthAudioStream extends AudioStream { +public abstract class FixedLengthAudioStream extends ClonableAudioStream { /** * Provides the length of the stream in bytes. @@ -31,13 +29,4 @@ public abstract class FixedLengthAudioStream extends AudioStream { * @return absolute length in bytes */ public abstract long length(); - - /** - * Returns a new, fully independent stream instance, which can be read and closed without impacting the original - * instance. - * - * @return a new input stream that can be consumed by the caller - * @throws AudioException if stream cannot be created - */ - public abstract InputStream getClonedStream() throws AudioException; } diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/StreamServed.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/StreamServed.java new file mode 100644 index 00000000000..35bf5946957 --- /dev/null +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/StreamServed.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.audio; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * Streams served by the AudioHTTPServer. + * + * @author Gwendal Roulleau - Initial contribution + */ +@NonNullByDefault +public record StreamServed(String url, AudioStream audioStream, AtomicInteger currentlyServedStream, AtomicLong timeout, + boolean multiTimeStream, CompletableFuture<@Nullable Void> playEnd) { +} diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/URLAudioStream.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/URLAudioStream.java index fef7e76f579..e43ab639760 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/URLAudioStream.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/URLAudioStream.java @@ -40,7 +40,7 @@ * @author Christoph Weitkamp - Refactored use of filename extension */ @NonNullByDefault -public class URLAudioStream extends AudioStream { +public class URLAudioStream extends ClonableAudioStream { private static final Pattern PLS_STREAM_PATTERN = Pattern.compile("^File[0-9]=(.+)$"); @@ -154,4 +154,9 @@ public void close() throws IOException { public String toString() { return url; } + + @Override + public InputStream getClonedStream() throws AudioException { + return new URLAudioStream(url); + } } diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/AudioManagerImpl.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/AudioManagerImpl.java index 914bedf9115..eb79b9344f5 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/AudioManagerImpl.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/AudioManagerImpl.java @@ -38,8 +38,6 @@ import org.openhab.core.audio.AudioStream; import org.openhab.core.audio.FileAudioStream; import org.openhab.core.audio.URLAudioStream; -import org.openhab.core.audio.UnsupportedAudioFormatException; -import org.openhab.core.audio.UnsupportedAudioStreamException; import org.openhab.core.audio.utils.ToneSynthesizer; import org.openhab.core.config.core.ConfigOptionProvider; import org.openhab.core.config.core.ConfigurableService; @@ -122,39 +120,11 @@ public void play(@Nullable AudioStream audioStream, @Nullable String sinkId) { public void play(@Nullable AudioStream audioStream, @Nullable String sinkId, @Nullable PercentType volume) { AudioSink sink = getSink(sinkId); if (sink != null) { - PercentType oldVolume = null; - // set notification sound volume - if (volume != null) { - try { - // get current volume - oldVolume = sink.getVolume(); - } catch (IOException e) { - logger.debug("An exception occurred while getting the volume of sink '{}' : {}", sink.getId(), - e.getMessage(), e); - } - - try { - sink.setVolume(volume); - } catch (IOException e) { - logger.debug("An exception occurred while setting the volume of sink '{}' : {}", sink.getId(), - e.getMessage(), e); - } - } - try { - sink.process(audioStream); - } catch (UnsupportedAudioFormatException | UnsupportedAudioStreamException e) { - logger.warn("Error playing '{}': {}", audioStream, e.getMessage(), e); - } finally { - if (volume != null && oldVolume != null) { - // restore volume only if it was set before - try { - sink.setVolume(oldVolume); - } catch (IOException e) { - logger.debug("An exception occurred while setting the volume of sink '{}' : {}", sink.getId(), - e.getMessage(), e); - } - } - } + Runnable restoreVolume = handleVolumeCommand(volume, sink); + sink.processAndComplete(audioStream).exceptionally((exception) -> { + logger.warn("Error playing '{}': {}", audioStream, exception.getMessage(), exception); + return null; + }).thenRun(restoreVolume); } else { logger.warn("Failed playing audio stream '{}' as no audio sink was found.", audioStream); } @@ -351,6 +321,53 @@ public Set getSinkIds(String pattern) { return null; } + @Override + public Runnable handleVolumeCommand(@Nullable PercentType volume, AudioSink sink) { + boolean volumeChanged = false; + PercentType oldVolume = null; + + Runnable toRunWhenProcessFinished = () -> { + }; + + if (volume == null) { + return toRunWhenProcessFinished; + } + + // set notification sound volume + try { + // get current volume + oldVolume = sink.getVolume(); + } catch (IOException | UnsupportedOperationException e) { + logger.debug("An exception occurred while getting the volume of sink '{}' : {}", sink.getId(), + e.getMessage(), e); + } + + if (!volume.equals(oldVolume) || oldVolume == null) { + try { + sink.setVolume(volume); + volumeChanged = true; + } catch (IOException | UnsupportedOperationException e) { + logger.debug("An exception occurred while setting the volume of sink '{}' : {}", sink.getId(), + e.getMessage(), e); + } + } + + final PercentType oldVolumeFinal = oldVolume; + // restore volume only if it was set before + if (volumeChanged && oldVolumeFinal != null) { + toRunWhenProcessFinished = () -> { + try { + sink.setVolume(oldVolumeFinal); + } catch (IOException | UnsupportedOperationException e) { + logger.debug("An exception occurred while setting the volume of sink '{}' : {}", sink.getId(), + e.getMessage(), e); + } + }; + } + + return toRunWhenProcessFinished; + } + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) protected void addAudioSource(AudioSource audioSource) { this.audioSources.put(audioSource.getId(), audioSource); diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/AudioServlet.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/AudioServlet.java index 9bcee8cef5b..e7acbb867ee 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/AudioServlet.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/AudioServlet.java @@ -12,16 +12,24 @@ */ package org.openhab.core.audio.internal; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -37,9 +45,17 @@ import org.openhab.core.audio.AudioFormat; import org.openhab.core.audio.AudioHTTPServer; import org.openhab.core.audio.AudioStream; +import org.openhab.core.audio.ByteArrayAudioStream; +import org.openhab.core.audio.ClonableAudioStream; +import org.openhab.core.audio.FileAudioStream; import org.openhab.core.audio.FixedLengthAudioStream; +import org.openhab.core.audio.StreamServed; +import org.openhab.core.audio.utils.AudioSinkUtils; +import org.openhab.core.common.ThreadPoolManager; +import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletName; import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletPattern; import org.slf4j.Logger; @@ -60,23 +76,34 @@ public class AudioServlet extends HttpServlet implements AudioHTTPServer { private static final List WAV_MIME_TYPES = List.of("audio/wav", "audio/x-wav", "audio/vnd.wave"); + // A 1MB in memory buffer will help playing multiple times an AudioStream, if the sink cannot do otherwise + private static final int ONETIME_STREAM_BUFFER_MAX_SIZE = 1048576; + // 5MB max for a file buffer + private static final int ONETIME_STREAM_FILE_MAX_SIZE = 5242880; + static final String SERVLET_PATH = "/audio"; private final Logger logger = LoggerFactory.getLogger(AudioServlet.class); - private final Map oneTimeStreams = new ConcurrentHashMap<>(); - private final Map multiTimeStreams = new ConcurrentHashMap<>(); + private final Map servedStreams = new ConcurrentHashMap<>(); + + private final ScheduledExecutorService threadPool = ThreadPoolManager + .getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON); + @Nullable + ScheduledFuture periodicCleaner; - private final Map streamTimeouts = new ConcurrentHashMap<>(); + private AudioSinkUtils audioSinkUtils; + + @Activate + public AudioServlet(@Reference AudioSinkUtils audioSinkUtils) { + super(); + this.audioSinkUtils = audioSinkUtils; + } @Deactivate protected synchronized void deactivate() { - multiTimeStreams.values().forEach(this::tryClose); - multiTimeStreams.clear(); - streamTimeouts.clear(); - - oneTimeStreams.values().forEach(this::tryClose); - oneTimeStreams.clear(); + servedStreams.values().stream().map(streamServed -> streamServed.audioStream()).forEach(this::tryClose); + servedStreams.clear(); } private void tryClose(@Nullable AudioStream stream) { @@ -88,29 +115,17 @@ private void tryClose(@Nullable AudioStream stream) { } } - private @Nullable InputStream prepareInputStream(final String streamId, final HttpServletResponse resp, + private InputStream prepareInputStream(final StreamServed streamServed, final HttpServletResponse resp, List acceptedMimeTypes) throws AudioException { - final AudioStream stream; - final boolean multiAccess; - if (oneTimeStreams.containsKey(streamId)) { - stream = oneTimeStreams.remove(streamId); - multiAccess = false; - } else if (multiTimeStreams.containsKey(streamId)) { - stream = multiTimeStreams.get(streamId); - multiAccess = true; - } else { - return null; - } - - logger.debug("Stream to serve is {}", streamId); + logger.debug("Stream to serve is {}", streamServed.url()); // try to set the content-type, if possible final String mimeType; - if (AudioFormat.CODEC_MP3.equals(stream.getFormat().getCodec())) { + if (AudioFormat.CODEC_MP3.equals(streamServed.audioStream().getFormat().getCodec())) { mimeType = "audio/mpeg"; - } else if (AudioFormat.CONTAINER_WAVE.equals(stream.getFormat().getContainer())) { + } else if (AudioFormat.CONTAINER_WAVE.equals(streamServed.audioStream().getFormat().getContainer())) { mimeType = WAV_MIME_TYPES.stream().filter(acceptedMimeTypes::contains).findFirst().orElse("audio/wav"); - } else if (AudioFormat.CONTAINER_OGG.equals(stream.getFormat().getContainer())) { + } else if (AudioFormat.CONTAINER_OGG.equals(streamServed.audioStream().getFormat().getContainer())) { mimeType = "audio/ogg"; } else { mimeType = null; @@ -120,16 +135,17 @@ private void tryClose(@Nullable AudioStream stream) { } // try to set the content-length, if possible - if (stream instanceof FixedLengthAudioStream audioStream) { - final long size = audioStream.length(); + if (streamServed.audioStream() instanceof FixedLengthAudioStream fixedLengthServedStream) { + final long size = fixedLengthServedStream.length(); resp.setContentLength((int) size); } - if (multiAccess) { + if (streamServed.multiTimeStream() + && streamServed.audioStream() instanceof ClonableAudioStream clonableAudioStream) { // we need to care about concurrent access and have a separate stream for each thread - return ((FixedLengthAudioStream) stream).getClonedStream(); + return clonableAudioStream.getClonedStream(); } else { - return stream; + return streamServed.audioStream(); } } @@ -146,8 +162,6 @@ private String substringBefore(String str, String separator) { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - removeTimedOutStreams(); - String requestURI = req.getRequestURI(); if (requestURI == null) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "requestURI is null"); @@ -159,55 +173,154 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se List acceptedMimeTypes = Stream.of(Objects.requireNonNullElse(req.getHeader("Accept"), "").split(",")) .map(String::trim).collect(Collectors.toList()); - try (final InputStream stream = prepareInputStream(streamId, resp, acceptedMimeTypes)) { - if (stream == null) { - logger.debug("Received request for invalid stream id at {}", requestURI); - resp.sendError(HttpServletResponse.SC_NOT_FOUND); - } else { - stream.transferTo(resp.getOutputStream()); + StreamServed servedStream = servedStreams.get(streamId); + if (servedStream == null) { + logger.debug("Received request for invalid stream id at {}", requestURI); + resp.sendError(HttpServletResponse.SC_NOT_FOUND); + return; + } + + // we count the number of active process using the input stream + AtomicInteger currentlyServedStream = servedStream.currentlyServedStream(); + if (currentlyServedStream.incrementAndGet() == 1 || servedStream.multiTimeStream()) { + try (final InputStream stream = prepareInputStream(servedStream, resp, acceptedMimeTypes)) { + Long endOfPlayTimestamp = audioSinkUtils.transferAndAnalyzeLength(stream, resp.getOutputStream(), + servedStream.audioStream().getFormat()); + // update timeout with the sound duration : + if (endOfPlayTimestamp != null) { + servedStream.timeout().set(Math.max(servedStream.timeout().get(), endOfPlayTimestamp)); + } resp.flushBuffer(); + } catch (final AudioException ex) { + resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage()); + } finally { + currentlyServedStream.decrementAndGet(); } - } catch (final AudioException ex) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage()); + } else { + logger.debug("Received request for already consumed stream id at {}", requestURI); + resp.sendError(HttpServletResponse.SC_NOT_FOUND); + return; + } + + // we can immediately dispose and remove, if it is a one time stream + if (!servedStream.multiTimeStream()) { + servedStreams.remove(streamId); + servedStream.playEnd().complete(null); + logger.debug("Removed timed out stream {}", streamId); } } private synchronized void removeTimedOutStreams() { // Build list of expired streams. long now = System.nanoTime(); - final List toRemove = streamTimeouts.entrySet().stream().filter(e -> e.getValue() < now) + final List toRemove = servedStreams.entrySet().stream() + .filter(e -> e.getValue().timeout().get() < now && e.getValue().currentlyServedStream().get() <= 0) .map(Entry::getKey).collect(Collectors.toList()); toRemove.forEach(streamId -> { - // the stream has expired, we need to remove it! - final FixedLengthAudioStream stream = multiTimeStreams.remove(streamId); - streamTimeouts.remove(streamId); - tryClose(stream); - logger.debug("Removed timed out stream {}", streamId); + // the stream has expired and no one is using it, we need to remove it! + StreamServed streamServed = servedStreams.remove(streamId); + if (streamServed != null) { + tryClose(streamServed.audioStream()); + // we can notify the caller of the stream consumption + streamServed.playEnd().complete(null); + logger.debug("Removed timed out stream {}", streamId); + } }); + + // Because the callback should be executed as soon as possible, + // we cannot wait for the next doGet to perform a clean. So we have to schedule a periodic cleaner. + ScheduledFuture periodicCleanerLocal = periodicCleaner; + if (!servedStreams.isEmpty()) { + if (periodicCleanerLocal == null || periodicCleanerLocal.isDone()) { + // reschedule a clean + periodicCleaner = threadPool.scheduleWithFixedDelay(this::removeTimedOutStreams, 5, 5, + TimeUnit.SECONDS); + } + } else if (periodicCleanerLocal != null) { // no more stream to serve, shut the periodic cleaning thread: + periodicCleanerLocal.cancel(true); + periodicCleaner = null; + } } @Override public String serve(AudioStream stream) { - String streamId = UUID.randomUUID().toString(); - oneTimeStreams.put(streamId, stream); - return getRelativeURL(streamId); + try { + // In case the stream is never played, we cannot wait indefinitely before executing the callback. + // so we set a timeout (even if this is a one time stream). + return serve(stream, 10, false).url(); + } catch (IOException e) { + logger.warn("Cannot precache the audio stream to serve it", e); + return getRelativeURL("error"); + } + } + + @Override + public String serve(AudioStream stream, int seconds) { + try { + return serve(stream, seconds, true).url(); + } catch (IOException e) { + logger.warn("Cannot precache the audio stream to serve it", e); + return getRelativeURL("error"); + } } @Override - public String serve(FixedLengthAudioStream stream, int seconds) { + public StreamServed serve(AudioStream originalStream, int seconds, boolean multiTimeStream) throws IOException { String streamId = UUID.randomUUID().toString(); - multiTimeStreams.put(streamId, stream); - streamTimeouts.put(streamId, System.nanoTime() + TimeUnit.SECONDS.toNanos(seconds)); - return getRelativeURL(streamId); + AudioStream audioStream = originalStream; + if (!(originalStream instanceof ClonableAudioStream) && multiTimeStream) { + // we we can try to make a Cloneable stream as it is needed + audioStream = createClonableInputStream(originalStream, streamId); + } + long timeOut = System.nanoTime() + TimeUnit.SECONDS.toNanos(seconds); + CompletableFuture<@Nullable Void> playEnd = new CompletableFuture<@Nullable Void>(); + StreamServed streamToServe = new StreamServed(getRelativeURL(streamId), audioStream, new AtomicInteger(), + new AtomicLong(timeOut), multiTimeStream, playEnd); + servedStreams.put(streamId, streamToServe); + + // try to clean, or a least launch the periodic cleanse: + removeTimedOutStreams(); + + return streamToServe; } - Map getMultiTimeStreams() { - return Collections.unmodifiableMap(multiTimeStreams); + private ClonableAudioStream createClonableInputStream(AudioStream stream, String streamId) throws IOException { + byte[] dataBytes = stream.readNBytes(ONETIME_STREAM_BUFFER_MAX_SIZE + 1); + ClonableAudioStream clonableAudioStreamResult; + if (dataBytes.length <= ONETIME_STREAM_BUFFER_MAX_SIZE) { + // we will use an in memory buffer to avoid disk operation + clonableAudioStreamResult = new ByteArrayAudioStream(dataBytes, stream.getFormat()); + } else { + // in memory max size exceeded, sound is too long, we will use a file + File tempFile = File.createTempFile(streamId, ".snd"); + tempFile.deleteOnExit(); + try (OutputStream outputStream = new FileOutputStream(tempFile)) { + // copy already read data to file : + outputStream.write(dataBytes); + // copy the remaining stream data to a file. + byte[] buf = new byte[8192]; + int length; + // but with a limit + int fileSize = ONETIME_STREAM_BUFFER_MAX_SIZE + 1; + while ((length = stream.read(buf)) != -1 && fileSize < ONETIME_STREAM_FILE_MAX_SIZE) { + int lengthToWrite = Math.min(length, ONETIME_STREAM_FILE_MAX_SIZE - fileSize); + outputStream.write(buf, 0, lengthToWrite); + fileSize += lengthToWrite; + } + } + try { + clonableAudioStreamResult = new FileAudioStream(tempFile, stream.getFormat(), true); + } catch (AudioException e) { // this is in fact a FileNotFoundException and should not happen + throw new IOException("Cannot find the cache file we just created.", e); + } + } + tryClose(stream); + return clonableAudioStreamResult; } - Map getOneTimeStreams() { - return Collections.unmodifiableMap(oneTimeStreams); + Map getServedStreams() { + return Collections.unmodifiableMap(servedStreams); } private String getRelativeURL(String streamId) { diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/javasound/JavaSoundAudioSink.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/javasound/JavaSoundAudioSink.java index 606b20e0195..41208b514f6 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/javasound/JavaSoundAudioSink.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/javasound/JavaSoundAudioSink.java @@ -13,7 +13,6 @@ package org.openhab.core.audio.internal.javasound; import java.io.IOException; -import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.Scanner; @@ -32,6 +31,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.audio.AudioFormat; import org.openhab.core.audio.AudioSink; +import org.openhab.core.audio.AudioSinkAsync; import org.openhab.core.audio.AudioStream; import org.openhab.core.audio.URLAudioStream; import org.openhab.core.audio.UnsupportedAudioFormatException; @@ -55,7 +55,7 @@ */ @NonNullByDefault @Component(service = AudioSink.class, immediate = true) -public class JavaSoundAudioSink implements AudioSink { +public class JavaSoundAudioSink extends AudioSinkAsync { private static final Logger LOGGER = LoggerFactory.getLogger(JavaSoundAudioSink.class); @@ -79,13 +79,14 @@ protected void activate(BundleContext context) { } @Override - public synchronized void process(final @Nullable AudioStream audioStream) + public synchronized void processAsynchronously(final @Nullable AudioStream audioStream) throws UnsupportedAudioFormatException, UnsupportedAudioStreamException { if (audioStream != null && !AudioFormat.CODEC_MP3.equals(audioStream.getFormat().getCodec())) { AudioPlayer audioPlayer = new AudioPlayer(audioStream); audioPlayer.start(); try { audioPlayer.join(); + playbackFinished(audioStream); } catch (InterruptedException e) { LOGGER.error("Playing audio has been interrupted."); } @@ -103,8 +104,7 @@ public synchronized void process(final @Nullable AudioStream audioStream) } else { try { // we start a new continuous stream and store its handle - streamPlayer = new Player(audioStream); - playInThread(streamPlayer); + playInThread(audioStream, true); } catch (JavaLayerException e) { LOGGER.error("An exception occurred while playing url audio stream : '{}'", e.getMessage()); } @@ -113,7 +113,7 @@ public synchronized void process(final @Nullable AudioStream audioStream) } else { // we are playing some normal file (no url stream) try { - playInThread(new Player(audioStream)); + playInThread(audioStream, false); } catch (JavaLayerException e) { LOGGER.error("An exception occurred while playing audio : '{}'", e.getMessage()); } @@ -121,17 +121,20 @@ public synchronized void process(final @Nullable AudioStream audioStream) } } - private void playInThread(final @Nullable Player player) { + private void playInThread(final AudioStream audioStream, boolean store) throws JavaLayerException { // run in new thread + Player streamPlayerFinal = new Player(audioStream); + if (store) { // we store its handle in case we want to interrupt it. + streamPlayer = streamPlayerFinal; + } threadFactory.newThread(() -> { - if (player != null) { - try { - player.play(); - } catch (Exception e) { - LOGGER.error("An exception occurred while playing audio : '{}'", e.getMessage()); - } finally { - player.close(); - } + try { + streamPlayerFinal.play(); + } catch (Exception e) { + LOGGER.error("An exception occurred while playing audio : '{}'", e.getMessage()); + } finally { + streamPlayerFinal.close(); + playbackFinished(audioStream); } }).start(); } @@ -174,7 +177,7 @@ public PercentType getVolume() throws IOException { return true; }); if (volumes[0] != null) { - return new PercentType(new BigDecimal(volumes[0] * 100f)); + return new PercentType(Math.round(volumes[0] * 100f)); } else { LOGGER.warn("Cannot determine master volume level - assuming 100%"); return PercentType.HUNDRED; diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/webaudio/WebAudioAudioSink.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/webaudio/WebAudioAudioSink.java index 7c9299b5847..36e492ee772 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/webaudio/WebAudioAudioSink.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/webaudio/WebAudioAudioSink.java @@ -21,8 +21,9 @@ import org.openhab.core.audio.AudioFormat; import org.openhab.core.audio.AudioHTTPServer; import org.openhab.core.audio.AudioSink; +import org.openhab.core.audio.AudioSinkAsync; import org.openhab.core.audio.AudioStream; -import org.openhab.core.audio.FixedLengthAudioStream; +import org.openhab.core.audio.StreamServed; import org.openhab.core.audio.URLAudioStream; import org.openhab.core.audio.UnsupportedAudioFormatException; import org.openhab.core.audio.UnsupportedAudioStreamException; @@ -44,13 +45,12 @@ */ @NonNullByDefault @Component(service = AudioSink.class, immediate = true) -public class WebAudioAudioSink implements AudioSink { +public class WebAudioAudioSink extends AudioSinkAsync { private final Logger logger = LoggerFactory.getLogger(WebAudioAudioSink.class); private static final Set SUPPORTED_AUDIO_FORMATS = Set.of(AudioFormat.MP3, AudioFormat.WAV); - private static final Set> SUPPORTED_AUDIO_STREAMS = Set - .of(FixedLengthAudioStream.class, URLAudioStream.class); + private static final Set> SUPPORTED_AUDIO_STREAMS = Set.of(AudioStream.class); private AudioHTTPServer audioHTTPServer; private EventPublisher eventPublisher; @@ -62,7 +62,7 @@ public WebAudioAudioSink(@Reference AudioHTTPServer audioHTTPServer, @Reference } @Override - public void process(@Nullable AudioStream audioStream) + public void processAsynchronously(@Nullable AudioStream audioStream) throws UnsupportedAudioFormatException, UnsupportedAudioStreamException { if (audioStream == null) { // in case the audioStream is null, this should be interpreted as a request to end any currently playing @@ -70,22 +70,26 @@ public void process(@Nullable AudioStream audioStream) logger.debug("Web Audio sink does not support stopping the currently playing stream."); return; } - try (AudioStream stream = audioStream) { - logger.debug("Received audio stream of format {}", audioStream.getFormat()); - if (audioStream instanceof URLAudioStream urlAudioStream) { + logger.debug("Received audio stream of format {}", audioStream.getFormat()); + if (audioStream instanceof URLAudioStream urlAudioStream) { + try (AudioStream stream = urlAudioStream) { + // in this case only, we need to close the stream by ourself in a try with block, + // because nothing will consume it // it is an external URL, so we can directly pass this on. sendEvent(urlAudioStream.getURL()); - } else if (audioStream instanceof FixedLengthAudioStream lengthAudioStream) { - // we need to serve it for a while and make it available to multiple clients, hence only - // FixedLengthAudioStreams are supported. - sendEvent(audioHTTPServer.serve(lengthAudioStream, 10)); - } else { - throw new UnsupportedAudioStreamException( - "Web audio sink can only handle FixedLengthAudioStreams and URLAudioStreams.", - audioStream.getClass()); + } catch (IOException e) { + logger.debug("Error while closing the audio stream: {}", e.getMessage(), e); + } + } else { + // we need to serve it for a while and make it available to multiple clients + try { + StreamServed servedStream = audioHTTPServer.serve(audioStream, 10, true); + // we will let the HTTP servlet run the delayed task when finished with the stream + servedStream.playEnd().thenRun(() -> this.playbackFinished(audioStream)); + sendEvent(servedStream.url()); + } catch (IOException e) { + logger.warn("Cannot precache the audio stream to serve it", e); } - } catch (IOException e) { - logger.debug("Error while closing the audio stream: {}", e.getMessage(), e); } } diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/utils/AudioSinkUtils.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/utils/AudioSinkUtils.java new file mode 100644 index 00000000000..b5a96e10811 --- /dev/null +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/utils/AudioSinkUtils.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.audio.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.audio.AudioFormat; + +/** + * Some utility methods for sink + * + * @author Gwendal Roulleau - Initial contribution + * + */ +@NonNullByDefault +public interface AudioSinkUtils { + + /** + * Transfers data from an input stream to an output stream and computes on the fly its duration + * + * @param in the input stream giving audio data ta play + * @param out the output stream receiving data to play + * @return the timestamp (from System.nanoTime) when the sound should be fully played. Returns null if computing + * time fails. + * @throws IOException if reading from the stream or writing to the stream failed + */ + @Nullable + Long transferAndAnalyzeLength(InputStream in, OutputStream out, AudioFormat audioFormat) throws IOException; +} diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/utils/AudioSinkUtilsImpl.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/utils/AudioSinkUtilsImpl.java new file mode 100644 index 00000000000..f49da0ce8e0 --- /dev/null +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/utils/AudioSinkUtilsImpl.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.audio.utils; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import javazoom.jl.decoder.Bitstream; +import javazoom.jl.decoder.BitstreamException; +import javazoom.jl.decoder.Header; + +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.audio.AudioFormat; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Some utility methods for sink + * + * @author Gwendal Roulleau - Initial contribution + * + */ +@NonNullByDefault +@Component +public class AudioSinkUtilsImpl implements AudioSinkUtils { + + private final Logger logger = LoggerFactory.getLogger(AudioSinkUtilsImpl.class); + + @Override + public @Nullable Long transferAndAnalyzeLength(InputStream in, OutputStream out, AudioFormat audioFormat) + throws IOException { + // take some data from the stream beginning + byte[] dataBytes = in.readNBytes(8192); + + // beginning sound timestamp : + long startTime = System.nanoTime(); + // copy already read data to the output stream : + out.write(dataBytes); + // transfer everything else + Long dataTransferedLength = dataBytes.length + in.transferTo(out); + + if (dataTransferedLength > 0) { + if (AudioFormat.CODEC_PCM_SIGNED.equals(audioFormat.getCodec())) { + try (AudioInputStream audioInputStream = AudioSystem + .getAudioInputStream(new ByteArrayInputStream(dataBytes))) { + int frameSize = audioInputStream.getFormat().getFrameSize(); + float frameRate = audioInputStream.getFormat().getFrameRate(); + long computedDuration = Float.valueOf((dataTransferedLength / (frameSize * frameRate)) * 1000000000) + .longValue(); + return startTime + computedDuration; + } catch (IOException | UnsupportedAudioFileException e) { + logger.debug("Cannot compute the duration of input stream", e); + return null; + } + } else if (AudioFormat.CODEC_MP3.equals(audioFormat.getCodec())) { + // not precise, no VBR, but better than nothing + Bitstream bitstream = new Bitstream(new ByteArrayInputStream(dataBytes)); + try { + Header h = bitstream.readFrame(); + if (h != null) { + long computedDuration = Float.valueOf(h.total_ms(dataTransferedLength.intValue()) * 1000000) + .longValue(); + return startTime + computedDuration; + } + } catch (BitstreamException ex) { + logger.debug("Cannot compute the duration of input stream", ex); + return null; + } + } + } + + return null; + } +} diff --git a/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/AbstractAudioServletTest.java b/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/AbstractAudioServletTest.java index 14224cb2ac6..c70545adc4e 100644 --- a/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/AbstractAudioServletTest.java +++ b/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/AbstractAudioServletTest.java @@ -33,7 +33,8 @@ import org.openhab.core.audio.AudioFormat; import org.openhab.core.audio.AudioStream; import org.openhab.core.audio.ByteArrayAudioStream; -import org.openhab.core.audio.FixedLengthAudioStream; +import org.openhab.core.audio.utils.AudioSinkUtils; +import org.openhab.core.audio.utils.AudioSinkUtilsImpl; import org.openhab.core.test.TestPortUtil; import org.openhab.core.test.TestServer; import org.openhab.core.test.java.JavaTest; @@ -62,10 +63,11 @@ public abstract class AbstractAudioServletTest extends JavaTest { public @Mock @NonNullByDefault({}) HttpService httpServiceMock; public @Mock @NonNullByDefault({}) HttpContext httpContextMock; + public AudioSinkUtils audioSinkUtils = new AudioSinkUtilsImpl(); @BeforeEach public void setupServerAndClient() { - audioServlet = new AudioServlet(); + audioServlet = new AudioServlet(audioSinkUtils); ServletHolder servletHolder = new ServletHolder(audioServlet); @@ -126,7 +128,7 @@ protected String serveStream(AudioStream stream, @Nullable Integer timeInterval) String path; if (timeInterval != null) { - path = audioServlet.serve((FixedLengthAudioStream) stream, timeInterval); + path = audioServlet.serve(stream, timeInterval); } else { path = audioServlet.serve(stream); } diff --git a/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/AudioServletTest.java b/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/AudioServletTest.java index 13e4957b5a6..6f7200604a6 100644 --- a/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/AudioServletTest.java +++ b/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/AudioServletTest.java @@ -14,10 +14,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.*; import java.io.File; import java.util.concurrent.TimeUnit; @@ -29,8 +27,10 @@ import org.junit.jupiter.api.Test; import org.openhab.core.audio.AudioFormat; import org.openhab.core.audio.AudioStream; +import org.openhab.core.audio.ByteArrayAudioStream; import org.openhab.core.audio.FileAudioStream; import org.openhab.core.audio.FixedLengthAudioStream; +import org.openhab.core.audio.StreamServed; import org.openhab.core.audio.internal.utils.BundledSoundFileHandler; /** @@ -128,7 +128,7 @@ public void onlyOneRequestToOneTimeStreamsCanBeMade() throws Exception { } @Test - public void requestToMultitimeStreamCannotBeDoneAfterTheTimeoutOfTheStreamHasExipred() throws Exception { + public void requestToMultitimeStreamCannotBeDoneAfterTheTimeoutOfTheStreamHasExpired() throws Exception { final int streamTimeout = 3; AudioStream audioStream = getByteArrayAudioStream(testByteArray, AudioFormat.CONTAINER_NONE, @@ -151,8 +151,8 @@ public void requestToMultitimeStreamCannotBeDoneAfterTheTimeoutOfTheStreamHasExi assertThat("The response media type was not as expected", response.getMediaType(), is(MEDIA_TYPE_AUDIO_MPEG)); - assertThat("The audio stream was not added to the multitime streams", - audioServlet.getMultiTimeStreams().containsValue(audioStream), is(true)); + assertThat("The audio stream was not added to the multitime streams", audioServlet.getServedStreams() + .values().stream().map(StreamServed::audioStream).toList().contains(audioStream), is(true)); } waitForAssert(() -> { @@ -161,27 +161,54 @@ public void requestToMultitimeStreamCannotBeDoneAfterTheTimeoutOfTheStreamHasExi } catch (Exception e) { throw new IllegalStateException(e); } - assertThat("The audio stream was not removed from multitime streams", - audioServlet.getMultiTimeStreams().containsValue(audioStream), is(false)); + assertThat("The audio stream was not removed from multitime streams", audioServlet.getServedStreams() + .values().stream().map(StreamServed::audioStream).toList().contains(audioStream), is(false)); }); response = getHttpRequest(url).send(); assertThat("The response status was not as expected", response.getStatus(), is(HttpStatus.NOT_FOUND_404)); } + @Test + public void oneTimeStreamIsRecreatedAsAClonable() throws Exception { + AudioStream audioStream = mock(AudioStream.class); + AudioFormat audioFormat = mock(AudioFormat.class); + when(audioStream.getFormat()).thenReturn(audioFormat); + when(audioFormat.getCodec()).thenReturn(AudioFormat.CODEC_MP3); + when(audioStream.readNBytes(anyInt())).thenReturn(testByteArray); + + String url = serveStream(audioStream, 10); + String uuid = url.substring(url.lastIndexOf("/") + 1); + StreamServed servedStream = audioServlet.getServedStreams().get(uuid); + + // does not contain directly the stream because it is now a new stream wrapper + assertThat(servedStream.audioStream(), not(audioStream)); + // it is now a ByteArrayAudioStream wrapper : + assertThat(servedStream.audioStream(), instanceOf(ByteArrayAudioStream.class)); + + ContentResponse response = getHttpRequest(url).send(); + assertThat("The response content was not as expected", response.getContent(), is(testByteArray)); + + verify(audioStream).close(); + } + @Test public void oneTimeStreamIsClosedAndRemovedAfterServed() throws Exception { AudioStream audioStream = mock(AudioStream.class); AudioFormat audioFormat = mock(AudioFormat.class); when(audioStream.getFormat()).thenReturn(audioFormat); when(audioFormat.getCodec()).thenReturn(AudioFormat.CODEC_MP3); + when(audioStream.readNBytes(anyInt())).thenReturn(new byte[] { 1, 2, 3 }); String url = serveStream(audioStream); + assertThat(audioServlet.getServedStreams().values().stream().map(StreamServed::audioStream).toList(), + contains(audioStream)); getHttpRequest(url).send(); verify(audioStream).close(); - assertThat(audioServlet.getOneTimeStreams().values(), not(contains(audioStream))); + assertThat(audioServlet.getServedStreams().values().stream().map(StreamServed::audioStream).toList(), + not(contains(audioStream))); } @Test @@ -195,9 +222,13 @@ public void multiTimeStreamIsClosedAfterExpired() throws Exception { cloneCounter.getAndIncrement(); return clonedStream; }); + when(audioStream.readNBytes(anyInt())).thenReturn(new byte[] { 1, 2, 3 }); + when(clonedStream.readNBytes(anyInt())).thenReturn(new byte[] { 1, 2, 3 }); when(audioFormat.getCodec()).thenReturn(AudioFormat.CODEC_MP3); String url = serveStream(audioStream, 2); + assertThat(audioServlet.getServedStreams().values().stream().map(StreamServed::audioStream).toList(), + contains(audioStream)); waitForAssert(() -> { try { @@ -210,7 +241,8 @@ public void multiTimeStreamIsClosedAfterExpired() throws Exception { }); verify(audioStream).close(); - assertThat(audioServlet.getMultiTimeStreams().values(), not(contains(audioStream))); + assertThat(audioServlet.getServedStreams().values().stream().map(StreamServed::audioStream).toList(), + not(contains(audioStream))); verify(clonedStream, times(cloneCounter.get())).close(); } diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceManagerImpl.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceManagerImpl.java index f08df2250ab..de770481a69 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceManagerImpl.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceManagerImpl.java @@ -12,7 +12,6 @@ */ package org.openhab.core.voice.internal; -import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; @@ -41,8 +40,6 @@ import org.openhab.core.audio.AudioSink; import org.openhab.core.audio.AudioSource; import org.openhab.core.audio.AudioStream; -import org.openhab.core.audio.UnsupportedAudioFormatException; -import org.openhab.core.audio.UnsupportedAudioStreamException; import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.config.core.ConfigOptionProvider; import org.openhab.core.config.core.ConfigurableService; @@ -272,39 +269,12 @@ public void say(String text, @Nullable String voiceId, @Nullable String sinkId, throw new TTSException( "Failed playing audio stream '" + audioStream + "' as audio sink doesn't support it"); } - - PercentType oldVolume = null; - // set notification sound volume - if (volume != null) { - try { - // get current volume - oldVolume = sink.getVolume(); - } catch (IOException e) { - logger.debug("An exception occurred while getting the volume of sink '{}' : {}", sink.getId(), - e.getMessage(), e); - } - - try { - sink.setVolume(volume); - } catch (IOException e) { - logger.debug("An exception occurred while setting the volume of sink '{}' : {}", sink.getId(), - e.getMessage(), e); - } - } - try { - sink.process(audioStream); - } finally { - if (volume != null && oldVolume != null) { - // restore volume only if it was set before - try { - sink.setVolume(oldVolume); - } catch (IOException e) { - logger.debug("An exception occurred while setting the volume of sink '{}' : {}", sink.getId(), - e.getMessage(), e); - } - } - } - } catch (TTSException | UnsupportedAudioFormatException | UnsupportedAudioStreamException e) { + Runnable restoreVolume = audioManager.handleVolumeCommand(volume, sink); + sink.processAndComplete(audioStream).exceptionally(exception -> { + logger.warn("Error playing '{}': {}", audioStream, exception.getMessage(), exception); + return null; + }).thenRun(restoreVolume); + } catch (TTSException e) { if (logger.isDebugEnabled()) { logger.debug("Error saying '{}': {}", text, e.getMessage(), e); } else { diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/cache/AudioStreamFromCache.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/cache/AudioStreamFromCache.java index 1cc79ea458a..360769944e3 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/cache/AudioStreamFromCache.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/cache/AudioStreamFromCache.java @@ -32,10 +32,12 @@ public class AudioStreamFromCache extends FixedLengthAudioStream { private InputStreamCacheWrapper inputStream; private AudioFormat audioFormat; + private String key; - public AudioStreamFromCache(InputStreamCacheWrapper inputStream, AudioFormatInfo audioFormat) { + public AudioStreamFromCache(InputStreamCacheWrapper inputStream, AudioFormatInfo audioFormat, String key) { this.inputStream = inputStream; this.audioFormat = audioFormat.toAudioFormat(); + this.key = key; } @Override @@ -101,4 +103,9 @@ public synchronized void reset() throws IOException { public boolean markSupported() { return inputStream.markSupported(); } + + @Override + public @Nullable String getId() { + return key; + } } diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/cache/TTSLRUCacheImpl.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/cache/TTSLRUCacheImpl.java index 03ba00db2ad..ace6df0dce6 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/cache/TTSLRUCacheImpl.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/cache/TTSLRUCacheImpl.java @@ -135,7 +135,7 @@ public AudioStream get(CachedTTSService tts, String text, Voice voice, AudioForm // we are sure that the cache is used, and so we can use an AudioStream // implementation that use convenient methods for some client, like getClonedStream() // or mark /reset - return new AudioStreamFromCache(inputStreamCacheWrapper, metadata); + return new AudioStreamFromCache(inputStreamCacheWrapper, metadata, key); } else { // the cache is not used, we can use the original response AudioStream return (AudioStream) fileAndMetadata.getInputStream(); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/lru/LRUMediaCacheEntry.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/lru/LRUMediaCacheEntry.java index 74c83aaabde..8bf60773cc8 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/lru/LRUMediaCacheEntry.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/lru/LRUMediaCacheEntry.java @@ -25,6 +25,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.common.Disposable; import org.openhab.core.storage.Storage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -233,6 +234,9 @@ protected void closeStreamClient() throws IOException { if (inputStreamLocal != null) { inputStreamLocal.close(); } + if (inputStreamLocal instanceof Disposable disposableStream) { + disposableStream.dispose(); + } } } } finally { diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/common/Disposable.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/common/Disposable.java new file mode 100644 index 00000000000..b2b4fd9db0d --- /dev/null +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/common/Disposable.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.common; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * For resource needing a callback when they are not needed anymore. + * + * @author Gwendal Roulleau - Initial contribution + */ +@NonNullByDefault +@FunctionalInterface +public interface Disposable { + void dispose() throws IOException; +} diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/lru/LRUMediaCacheEntryTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/lru/LRUMediaCacheEntryTest.java index 078f80ea043..619ee0c0923 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/lru/LRUMediaCacheEntryTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/cache/lru/LRUMediaCacheEntryTest.java @@ -26,6 +26,8 @@ import java.util.function.Supplier; import java.util.stream.Collectors; +import org.apache.commons.lang3.mutable.Mutable; +import org.apache.commons.lang3.mutable.MutableObject; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.junit.jupiter.api.BeforeEach; @@ -191,10 +193,21 @@ public void loadTwoThreadsAtTheSameTimeFromTheSameSupplierTest() throws IOExcept InputStream actualAudioStream2 = lruMediaCacheEntry.getInputStream(); // read bytes from the two stream concurrently + Mutable<@Nullable IOException> exceptionCatched = new MutableObject<>(); List parallelAudioStreamList = Arrays.asList(actualAudioStream1, actualAudioStream2); - List bytesResultList = parallelAudioStreamList.parallelStream().map(this::readSafe) - .collect(Collectors.toList()); + List bytesResultList = parallelAudioStreamList.parallelStream().map(stream -> { + try { + return stream.readAllBytes(); + } catch (IOException e) { + exceptionCatched.setValue(e); + return new byte[0]; + } + }).collect(Collectors.toList()); + IOException possibleException = exceptionCatched.getValue(); + if (possibleException != null) { + throw possibleException; + } assertArrayEquals(randomData, bytesResultList.get(0)); assertArrayEquals(randomData, bytesResultList.get(1)); @@ -208,14 +221,6 @@ public void loadTwoThreadsAtTheSameTimeFromTheSameSupplierTest() throws IOExcept verifyNoMoreInteractions(ttsServiceMock); } - private byte[] readSafe(InputStream InputStream) { - try { - return InputStream.readAllBytes(); - } catch (IOException e) { - return new byte[0]; - } - } - private byte[] getRandomData(int length) { Random random = new Random(); byte[] randomBytes = new byte[length]; From c2e81a13fd96288079048683f9aefe96a8807bfb Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 17 Jun 2023 11:00:39 +0200 Subject: [PATCH 110/126] Upgrade XChart to 3.8.4 (#3291) Signed-off-by: Jan N. Klug --- bundles/org.openhab.core.ui/NOTICE | 4 ++-- bundles/org.openhab.core.ui/bnd.bnd | 3 +++ bundles/org.openhab.core.ui/pom.xml | 3 +-- .../chart/defaultchartprovider/DefaultChartProvider.java | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/bundles/org.openhab.core.ui/NOTICE b/bundles/org.openhab.core.ui/NOTICE index f4cf4cb3fa1..c9355858dd6 100644 --- a/bundles/org.openhab.core.ui/NOTICE +++ b/bundles/org.openhab.core.ui/NOTICE @@ -15,10 +15,10 @@ https://github.com/openhab/openhab-core == Third-party Content -xchart-2.6.1 +xchart-3.8.4 * License: Apache License, 2.0 * Project: http://knowm.org/open-source/xchart -* Source: https://github.com/timmolter/XChart/tree/xchart-2.6.1 +* Source: https://github.com/timmolter/XChart/tree/xchart-3.8.4 == Third-party license(s) diff --git a/bundles/org.openhab.core.ui/bnd.bnd b/bundles/org.openhab.core.ui/bnd.bnd index c13a3330e6a..d6f226152e2 100644 --- a/bundles/org.openhab.core.ui/bnd.bnd +++ b/bundles/org.openhab.core.ui/bnd.bnd @@ -4,4 +4,7 @@ Private-Package: \ org.knowm.xchart.* Import-Package: \ de.erichseifert.vectorgraphics2d.*;resolution:=optional,\ + de.rototor.pdfbox.*;resolution:=optional,\ + com.madgag.*;resolution:=optional,\ + org.apache.pdfbox.*;resolution:=optional,\ * \ No newline at end of file diff --git a/bundles/org.openhab.core.ui/pom.xml b/bundles/org.openhab.core.ui/pom.xml index 575615ef393..2eef0e13fbb 100644 --- a/bundles/org.openhab.core.ui/pom.xml +++ b/bundles/org.openhab.core.ui/pom.xml @@ -18,8 +18,7 @@ org.knowm.xchart xchart - - 3.1.0 + 3.8.4 org.openhab.core.bundles diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/DefaultChartProvider.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/DefaultChartProvider.java index 1c8304a336e..5f89cb04859 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/DefaultChartProvider.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/DefaultChartProvider.java @@ -32,7 +32,7 @@ import org.knowm.xchart.XYChart; import org.knowm.xchart.XYChartBuilder; import org.knowm.xchart.XYSeries; -import org.knowm.xchart.style.Styler; +import org.knowm.xchart.style.AxesChartStyler; import org.knowm.xchart.style.Styler.LegendPosition; import org.knowm.xchart.style.XYStyler; import org.knowm.xchart.style.markers.None; @@ -195,7 +195,7 @@ public BufferedImage createChart(@Nullable String serviceId, @Nullable String th styler.setYAxisDecimalPattern(yAxisDecimalPattern); } styler.setYAxisTickMarkSpacingHint(yAxisSpacing); - styler.setYAxisLabelAlignment(Styler.TextAlignment.Right); + styler.setYAxisLabelAlignment(AxesChartStyler.TextAlignment.Right); // chart styler.setChartBackgroundColor(chartTheme.getChartBackgroundColor()); styler.setChartFontColor(chartTheme.getChartFontColor()); From d64bd3b90b98ecbf69ed0cd99beb04ed3c0f450c Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 18 Jun 2023 09:29:49 +0200 Subject: [PATCH 111/126] Improve existing and add new persistence filters (#3642) * Improve existing and add new persistence filters * include filter Signed-off-by: Jan N. Klug --- .../core/model/persistence/Persistence.xtext | 23 +++- .../internal/PersistenceModelManager.java | 24 +++- .../persistence/dto/PersistenceFilterDTO.java | 24 +++- .../PersistenceServiceConfigurationDTO.java | 2 + .../filter/PersistenceEqualsFilter.java | 59 +++++++++ .../filter/PersistenceIncludeFilter.java | 106 +++++++++++++++++ .../filter/PersistenceThresholdFilter.java | 17 ++- .../filter/PersistenceTimeFilter.java | 4 +- ...sistenceServiceConfigurationDTOMapper.java | 53 ++++++++- .../filter/PersistenceEqualsFilterTest.java | 84 +++++++++++++ .../filter/PersistenceIncludeFilterTest.java | 112 ++++++++++++++++++ .../PersistenceThresholdFilterTest.java | 33 +++--- .../internal/PersistenceManagerTest.java | 4 +- 13 files changed, 503 insertions(+), 42 deletions(-) create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceEqualsFilter.java create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceIncludeFilter.java create mode 100644 bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceEqualsFilterTest.java create mode 100644 bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceIncludeFilterTest.java diff --git a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/Persistence.xtext b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/Persistence.xtext index 4e34511c2e1..7699c6e7405 100644 --- a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/Persistence.xtext +++ b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/Persistence.xtext @@ -27,17 +27,34 @@ Filter: ; FilterDetails: - ThresholdFilter | TimeFilter + ThresholdFilter | TimeFilter | EqualsFilter | NotEqualsFilter | IncludeFilter | NotIncludeFilter ; ThresholdFilter: - '>' value=DECIMAL unit=STRING + '>' (relative?='%')? value=DECIMAL unit=STRING? ; TimeFilter: - value=INT unit=('s' | 'm' | 'h' | 'd') + 'T' value=INT unit=('s' | 'm' | 'h' | 'd') ; +EqualsFilter: + '=' values+=STRING (',' values+=STRING)* +; + +NotEqualsFilter: + '!' values+=STRING (',' values+=STRING)* +; + +IncludeFilter: + '[]' lower=DECIMAL upper=DECIMAL unit=STRING? +; + +NotIncludeFilter: + '][' lower=DECIMAL upper=DECIMAL unit=STRING? +; + + PersistenceConfiguration: items+=(AllConfig | ItemConfig | GroupConfig) (',' items+=(AllConfig | ItemConfig | GroupConfig))* ('->' alias=STRING)? ((':' ('strategy' '=' strategies+=[Strategy|ID] (',' strategies+=[Strategy|ID])*)? diff --git a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/internal/PersistenceModelManager.java b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/internal/PersistenceModelManager.java index 3531eb4976c..9b5d2b24de6 100644 --- a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/internal/PersistenceModelManager.java +++ b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/internal/PersistenceModelManager.java @@ -26,9 +26,13 @@ import org.openhab.core.model.core.ModelRepositoryChangeListener; import org.openhab.core.model.persistence.persistence.AllConfig; import org.openhab.core.model.persistence.persistence.CronStrategy; +import org.openhab.core.model.persistence.persistence.EqualsFilter; import org.openhab.core.model.persistence.persistence.Filter; import org.openhab.core.model.persistence.persistence.GroupConfig; +import org.openhab.core.model.persistence.persistence.IncludeFilter; import org.openhab.core.model.persistence.persistence.ItemConfig; +import org.openhab.core.model.persistence.persistence.NotEqualsFilter; +import org.openhab.core.model.persistence.persistence.NotIncludeFilter; import org.openhab.core.model.persistence.persistence.PersistenceConfiguration; import org.openhab.core.model.persistence.persistence.PersistenceModel; import org.openhab.core.model.persistence.persistence.Strategy; @@ -40,7 +44,9 @@ import org.openhab.core.persistence.config.PersistenceConfig; import org.openhab.core.persistence.config.PersistenceGroupConfig; import org.openhab.core.persistence.config.PersistenceItemConfig; +import org.openhab.core.persistence.filter.PersistenceEqualsFilter; import org.openhab.core.persistence.filter.PersistenceFilter; +import org.openhab.core.persistence.filter.PersistenceIncludeFilter; import org.openhab.core.persistence.filter.PersistenceThresholdFilter; import org.openhab.core.persistence.filter.PersistenceTimeFilter; import org.openhab.core.persistence.registry.PersistenceServiceConfiguration; @@ -175,13 +181,21 @@ private List mapFilters(List filters) { } private PersistenceFilter mapFilter(Filter filter) { - if (filter.getDefinition() instanceof TimeFilter) { - TimeFilter timeFilter = (TimeFilter) filter.getDefinition(); + if (filter.getDefinition() instanceof TimeFilter timeFilter) { return new PersistenceTimeFilter(filter.getName(), timeFilter.getValue(), timeFilter.getUnit()); - } else if (filter.getDefinition() instanceof ThresholdFilter) { - ThresholdFilter thresholdFilter = (ThresholdFilter) filter.getDefinition(); + } else if (filter.getDefinition() instanceof ThresholdFilter thresholdFilter) { return new PersistenceThresholdFilter(filter.getName(), thresholdFilter.getValue(), - thresholdFilter.getUnit()); + thresholdFilter.getUnit(), thresholdFilter.isRelative()); + } else if (filter.getDefinition() instanceof EqualsFilter equalsFilter) { + return new PersistenceEqualsFilter(filter.getName(), equalsFilter.getValues(), false); + } else if (filter.getDefinition() instanceof NotEqualsFilter notEqualsFilter) { + return new PersistenceEqualsFilter(filter.getName(), notEqualsFilter.getValues(), true); + } else if (filter.getDefinition() instanceof IncludeFilter includeFilter) { + return new PersistenceIncludeFilter(filter.getName(), includeFilter.getLower(), includeFilter.getUpper(), + includeFilter.getUnit(), false); + } else if (filter.getDefinition() instanceof NotIncludeFilter notIncludeFilter) { + return new PersistenceIncludeFilter(filter.getName(), notIncludeFilter.getLower(), + notIncludeFilter.getUpper(), notIncludeFilter.getUnit(), true); } throw new IllegalArgumentException("Unknown filter type " + filter.getClass()); } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceFilterDTO.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceFilterDTO.java index d59bc9a480d..097e92204d7 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceFilterDTO.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceFilterDTO.java @@ -13,6 +13,7 @@ package org.openhab.core.persistence.dto; import java.math.BigDecimal; +import java.util.List; /** * The {@link org.openhab.core.persistence.dto.PersistenceFilterDTO} is used for transferring persistence filter @@ -21,7 +22,24 @@ * @author Jan N. Klug - Initial contribution */ public class PersistenceFilterDTO { - public String name = ""; - public BigDecimal value = BigDecimal.ZERO; - public String unit = ""; + public String name; + + // threshold and time + public BigDecimal value; + + // threshold + public Boolean relative; + + // threshold, include/exclude + public String unit; + + // include/exclude + public BigDecimal lower; + public BigDecimal upper; + + // equals/not equals + public List values; + + // equals/not equals, include/exclude + public Boolean inverted; } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceServiceConfigurationDTO.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceServiceConfigurationDTO.java index bc796a36871..e4faa9ed20f 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceServiceConfigurationDTO.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceServiceConfigurationDTO.java @@ -30,6 +30,8 @@ public class PersistenceServiceConfigurationDTO { public Collection cronStrategies = List.of(); public Collection thresholdFilters = List.of(); public Collection timeFilters = List.of(); + public Collection equalsFilters = List.of(); + public Collection includeFilters = List.of(); public boolean editable = false; } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceEqualsFilter.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceEqualsFilter.java new file mode 100644 index 00000000000..fdceb2863a8 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceEqualsFilter.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.filter; + +import java.util.Collection; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.items.Item; + +/** + * The {@link PersistenceEqualsFilter} is a filter that allows only specific values to pass + *

+ * The filter returns {@code false} if the string representation of the item's state is not in the given list + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class PersistenceEqualsFilter extends PersistenceFilter { + private final Collection values; + private final boolean inverted; + + public PersistenceEqualsFilter(String name, Collection values, boolean inverted) { + super(name); + this.values = values; + this.inverted = inverted; + } + + public Collection getValues() { + return values; + } + + public boolean getInverted() { + return inverted; + } + + @Override + public boolean apply(Item item) { + return values.contains(item.getState().toFullString()) != inverted; + } + + @Override + public void persisted(Item item) { + } + + @Override + public String toString() { + return String.format("%s [name=%s, value=%s, inverted=]", getClass().getSimpleName(), getName(), values); + } +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceIncludeFilter.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceIncludeFilter.java new file mode 100644 index 00000000000..105562f1df0 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceIncludeFilter.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.filter; + +import java.math.BigDecimal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.items.Item; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PersistenceIncludeFilter} is a filter that allows only specific values to pass + *

+ * The filter returns {@code false} if the string representation of the item's state is not in the given list + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class PersistenceIncludeFilter extends PersistenceFilter { + private final Logger logger = LoggerFactory.getLogger(PersistenceIncludeFilter.class); + + private final BigDecimal lower; + private final BigDecimal upper; + private final String unit; + private final boolean inverted; + + public PersistenceIncludeFilter(String name, BigDecimal lower, BigDecimal upper, String unit, boolean inverted) { + super(name); + this.lower = lower; + this.upper = upper; + this.unit = unit; + this.inverted = inverted; + } + + public BigDecimal getLower() { + return lower; + } + + public BigDecimal getUpper() { + return upper; + } + + public String getUnit() { + return unit; + } + + public boolean getInverted() { + return inverted; + } + + @Override + public boolean apply(Item item) { + State state = item.getState(); + BigDecimal compareValue = null; + if (state instanceof DecimalType decimalType) { + compareValue = decimalType.toBigDecimal(); + } else if (state instanceof QuantityType quantityType) { + if (!unit.isBlank()) { + QuantityType convertedQuantity = quantityType.toUnit(unit); + if (convertedQuantity != null) { + compareValue = convertedQuantity.toBigDecimal(); + } + } + } + if (compareValue == null) { + logger.warn("Cannot compare {} to range {}{} - {}{} ", state, lower, unit, upper, unit); + return true; + } + + if (inverted) { + logger.error("Compare {} {} to {} -> {}, {} -> {}", inverted, compareValue, lower, + compareValue.compareTo(lower) <= 0, upper, compareValue.compareTo(upper) >= 0); + + return compareValue.compareTo(lower) <= 0 || compareValue.compareTo(upper) >= 0; + } else { + + logger.error("Compare {} {} to {} -> {}, {} -> {}", inverted, compareValue, lower, + compareValue.compareTo(lower) >= 0, upper, compareValue.compareTo(upper) <= 0); + return compareValue.compareTo(lower) >= 0 && compareValue.compareTo(upper) <= 0; + } + } + + @Override + public void persisted(Item item) { + } + + @Override + public String toString() { + return String.format("%s [name=%s, lower=%s, upper=%s, unit=%s, inverted=%b]", getClass().getSimpleName(), + getName(), lower, upper, unit, inverted); + } +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceThresholdFilter.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceThresholdFilter.java index efd3d6d968c..269c40db9d8 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceThresholdFilter.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceThresholdFilter.java @@ -29,7 +29,7 @@ /** * The {@link PersistenceThresholdFilter} is a filter to prevent persistence based on a threshold. - * + *

* The filter returns {@code false} if the new value deviates by less than {@link #value}. If unit is "%" is * {@code true}, the filter returns {@code false} if the relative deviation is less than {@link #value}. * @@ -43,13 +43,15 @@ public class PersistenceThresholdFilter extends PersistenceFilter { private final BigDecimal value; private final String unit; + private final boolean relative; private final transient Map valueCache = new HashMap<>(); - public PersistenceThresholdFilter(String name, BigDecimal value, String unit) { + public PersistenceThresholdFilter(String name, BigDecimal value, String unit, boolean relative) { super(name); this.value = value; this.unit = unit; + this.relative = relative; } public BigDecimal getValue() { @@ -60,6 +62,10 @@ public String getUnit() { return unit; } + public boolean isRelative() { + return relative; + } + @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public boolean apply(Item item) { @@ -78,7 +84,7 @@ public boolean apply(Item item) { if (state instanceof DecimalType) { BigDecimal oldState = ((DecimalType) cachedState).toBigDecimal(); BigDecimal delta = oldState.subtract(((DecimalType) state).toBigDecimal()); - if ("%".equals(unit) && !BigDecimal.ZERO.equals(oldState)) { + if (relative && !BigDecimal.ZERO.equals(oldState)) { delta = delta.multiply(HUNDRED).divide(oldState, 2, RoundingMode.HALF_UP); } return delta.abs().compareTo(value) > 0; @@ -86,7 +92,7 @@ public boolean apply(Item item) { try { QuantityType oldState = (QuantityType) cachedState; QuantityType delta = oldState.subtract((QuantityType) state); - if ("%".equals(unit)) { + if (relative) { if (BigDecimal.ZERO.equals(oldState.toBigDecimal())) { // value is different and old value is 0 -> always above relative threshold return true; @@ -117,6 +123,7 @@ public void persisted(Item item) { @Override public String toString() { - return String.format("%s [name=%s, value=%s, unit=%s]", getClass().getSimpleName(), getName(), value, unit); + return String.format("%s [name=%s, value=%s, unit=%s, relative=%b]", getClass().getSimpleName(), getName(), + value, unit, relative); } } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceTimeFilter.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceTimeFilter.java index c3edcfdc6f3..524ef2bc342 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceTimeFilter.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceTimeFilter.java @@ -24,8 +24,8 @@ /** * The {@link PersistenceTimeFilter} is a filter to prevent persistence base on intervals. - * - * The filter returns {@link false} if the time between now and the time of the last persisted value is less than + *

+ * The filter returns {@code false} if the time between now and the time of the last persisted value is less than * {@link #duration} {@link #unit} * * @author Jan N. Klug - Initial contribution diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationDTOMapper.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationDTOMapper.java index 2b9b513ed18..a20dbcc6d39 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationDTOMapper.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationDTOMapper.java @@ -32,7 +32,9 @@ import org.openhab.core.persistence.dto.PersistenceFilterDTO; import org.openhab.core.persistence.dto.PersistenceItemConfigurationDTO; import org.openhab.core.persistence.dto.PersistenceServiceConfigurationDTO; +import org.openhab.core.persistence.filter.PersistenceEqualsFilter; import org.openhab.core.persistence.filter.PersistenceFilter; +import org.openhab.core.persistence.filter.PersistenceIncludeFilter; import org.openhab.core.persistence.filter.PersistenceThresholdFilter; import org.openhab.core.persistence.filter.PersistenceTimeFilter; import org.openhab.core.persistence.strategy.PersistenceCronStrategy; @@ -65,6 +67,10 @@ public static PersistenceServiceConfigurationDTO map( PersistenceServiceConfigurationDTOMapper::mapPersistenceThresholdFilter); dto.timeFilters = filterList(persistenceServiceConfiguration.getFilters(), PersistenceTimeFilter.class, PersistenceServiceConfigurationDTOMapper::mapPersistenceTimeFilter); + dto.equalsFilters = filterList(persistenceServiceConfiguration.getFilters(), PersistenceEqualsFilter.class, + PersistenceServiceConfigurationDTOMapper::mapPersistenceEqualsFilter); + dto.includeFilters = filterList(persistenceServiceConfiguration.getFilters(), PersistenceIncludeFilter.class, + PersistenceServiceConfigurationDTOMapper::mapPersistenceIncludeFilter); return dto; } @@ -73,11 +79,14 @@ public static PersistenceServiceConfiguration map(PersistenceServiceConfiguratio Map strategyMap = dto.cronStrategies.stream() .collect(Collectors.toMap(e -> e.name, e -> new PersistenceCronStrategy(e.name, e.cronExpression))); - Map filterMap = Stream - .concat(dto.thresholdFilters.stream().map(f -> new PersistenceThresholdFilter(f.name, f.value, f.unit)), - dto.timeFilters.stream() - .map(f -> new PersistenceTimeFilter(f.name, f.value.intValue(), f.unit))) - .collect(Collectors.toMap(PersistenceFilter::getName, e -> e)); + Map filterMap = Stream.of( + dto.thresholdFilters.stream() + .map(f -> new PersistenceThresholdFilter(f.name, f.value, f.unit, f.relative)), + dto.timeFilters.stream().map(f -> new PersistenceTimeFilter(f.name, f.value.intValue(), f.unit)), + dto.equalsFilters.stream().map(f -> new PersistenceEqualsFilter(f.name, f.values, f.inverted)), + dto.includeFilters.stream() + .map(f -> new PersistenceIncludeFilter(f.name, f.lower, f.upper, f.unit, f.inverted))) + .flatMap(Function.identity()).collect(Collectors.toMap(PersistenceFilter::getName, e -> e)); List defaults = dto.defaults.stream() .map(str -> stringToPersistenceStrategy(str, strategyMap, dto.serviceId)).toList(); @@ -87,7 +96,9 @@ public static PersistenceServiceConfiguration map(PersistenceServiceConfiguratio .map(PersistenceServiceConfigurationDTOMapper::stringToPersistenceConfig).toList(); List strategies = config.strategies.stream() .map(str -> stringToPersistenceStrategy(str, strategyMap, dto.serviceId)).toList(); - return new PersistenceItemConfiguration(items, config.alias, strategies, List.of()); + List filters = config.filters.stream() + .map(str -> stringToPersistenceFilter(str, filterMap, dto.serviceId)).toList(); + return new PersistenceItemConfiguration(items, config.alias, strategies, filters); }).toList(); return new PersistenceServiceConfiguration(dto.serviceId, configs, defaults, strategyMap.values(), @@ -121,6 +132,16 @@ private static PersistenceStrategy stringToPersistenceStrategy(String string, throw new IllegalArgumentException("Strategy '" + string + "' unknown for service '" + serviceId + "'"); } + private static PersistenceFilter stringToPersistenceFilter(String string, Map filterMap, + String serviceId) { + PersistenceFilter filter = filterMap.get(string); + if (filter != null) { + return filter; + } + + throw new IllegalArgumentException("Filter '" + string + "' unknown for service '" + serviceId + "'"); + } + private static String persistenceConfigToString(PersistenceConfig config) { if (config instanceof PersistenceAllConfig) { return "*"; @@ -137,6 +158,7 @@ private static PersistenceItemConfigurationDTO mapPersistenceItemConfig(Persiste itemDto.items = config.items().stream().map(PersistenceServiceConfigurationDTOMapper::persistenceConfigToString) .toList(); itemDto.strategies = config.strategies().stream().map(PersistenceStrategy::getName).toList(); + itemDto.filters = config.filters().stream().map(PersistenceFilter::getName).toList(); itemDto.alias = config.alias(); return itemDto; } @@ -153,6 +175,7 @@ private static PersistenceFilterDTO mapPersistenceThresholdFilter(PersistenceThr filterDTO.name = thresholdFilter.getName(); filterDTO.value = thresholdFilter.getValue(); filterDTO.unit = thresholdFilter.getUnit(); + filterDTO.relative = thresholdFilter.isRelative(); return filterDTO; } @@ -163,4 +186,22 @@ private static PersistenceFilterDTO mapPersistenceTimeFilter(PersistenceTimeFilt filterDTO.unit = persistenceTimeFilter.getUnit(); return filterDTO; } + + private static PersistenceFilterDTO mapPersistenceEqualsFilter(PersistenceEqualsFilter persistenceEqualsFilter) { + PersistenceFilterDTO filterDTO = new PersistenceFilterDTO(); + filterDTO.name = persistenceEqualsFilter.getName(); + filterDTO.values = persistenceEqualsFilter.getValues().stream().toList(); + filterDTO.inverted = persistenceEqualsFilter.getInverted(); + return filterDTO; + } + + private static PersistenceFilterDTO mapPersistenceIncludeFilter(PersistenceIncludeFilter persistenceIncludeFilter) { + PersistenceFilterDTO filterDTO = new PersistenceFilterDTO(); + filterDTO.name = persistenceIncludeFilter.getName(); + filterDTO.lower = persistenceIncludeFilter.getLower(); + filterDTO.upper = persistenceIncludeFilter.getUpper(); + filterDTO.unit = persistenceIncludeFilter.getUnit(); + filterDTO.inverted = persistenceIncludeFilter.getInverted(); + return filterDTO; + } } diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceEqualsFilterTest.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceEqualsFilterTest.java new file mode 100644 index 00000000000..2376cc59b47 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceEqualsFilterTest.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.filter; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.mockito.Mockito.when; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.openhab.core.items.GenericItem; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.types.State; + +/** + * The {@link PersistenceEqualsFilterTest} contains tests for {@link PersistenceEqualsFilter} + * + * @author Jan N. Klug - Initial contribution + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@NonNullByDefault +public class PersistenceEqualsFilterTest { + private static final String ITEM_NAME = "itemName"; + + private @NonNullByDefault({}) @Mock GenericItem item; + + @ParameterizedTest + @MethodSource("argumentProvider") + public void equalsFilterTest(State state, Collection values, boolean expected) { + when(item.getState()).thenReturn(state); + + PersistenceEqualsFilter filter = new PersistenceEqualsFilter("filter", values, false); + assertThat(filter.apply(item), is(expected)); + } + + @ParameterizedTest + @MethodSource("argumentProvider") + public void notEqualsFilterTest(State state, Collection values, boolean expected) { + when(item.getState()).thenReturn(state); + + PersistenceEqualsFilter filter = new PersistenceEqualsFilter("filter", values, true); + assertThat(filter.apply(item), is(not(expected))); + } + + private static Stream argumentProvider() { + return Stream.of(// + // item state, values, result + Arguments.of(new StringType("value1"), List.of("value1", "value2"), true), + Arguments.of(new StringType("value3"), List.of("value1", "value2"), false), + Arguments.of(new DecimalType(5), List.of("3", "5", "9"), true), + Arguments.of(new DecimalType(7), List.of("3", "5", "9"), false), + Arguments.of(new QuantityType<>(10, SIUnits.CELSIUS), List.of("5 °C", "10 °C", "15 °C"), true), + Arguments.of(new QuantityType<>(20, SIUnits.CELSIUS), List.of("5 °C", "10 °C", "15 °C"), false), + Arguments.of(OnOffType.ON, List.of("ON", "UNDEF", "NULL"), true), + Arguments.of(OnOffType.OFF, List.of("ON", "UNDEF", "NULL"), false)); + } +} diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceIncludeFilterTest.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceIncludeFilterTest.java new file mode 100644 index 00000000000..a43b85cab41 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceIncludeFilterTest.java @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.filter; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.when; + +import java.math.BigDecimal; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.openhab.core.items.GenericItem; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.types.State; + +/** + * The {@link PersistenceIncludeFilterTest} contains tests for {@link PersistenceIncludeFilter} + * + * @author Jan N. Klug - Initial contribution + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@NonNullByDefault +public class PersistenceIncludeFilterTest { + private static final String ITEM_NAME = "itemName"; + + private @NonNullByDefault({}) @Mock GenericItem item; + + @ParameterizedTest + @MethodSource("argumentProvider") + public void includeFilterTest(State state, BigDecimal lower, BigDecimal upper, String unit, boolean expected) { + when(item.getState()).thenReturn(state); + + PersistenceIncludeFilter filter = new PersistenceIncludeFilter("filter", lower, upper, unit, false); + assertThat(filter.apply(item), is(expected)); + } + + @ParameterizedTest + @MethodSource("notArgumentProvider") + public void notIncludeFilterTest(State state, BigDecimal lower, BigDecimal upper, String unit, boolean expected) { + when(item.getState()).thenReturn(state); + + PersistenceIncludeFilter filter = new PersistenceIncludeFilter("filter", lower, upper, unit, true); + assertThat(filter.apply(item), is(expected)); + } + + private static Stream argumentProvider() { + return Stream.of(// + // item state, lower, upper, unit, result + // QuantityType + Arguments.of(new QuantityType<>("17 °C"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "°C", true), + Arguments.of(new QuantityType<>("17 °C"), BigDecimal.valueOf(17), BigDecimal.valueOf(19), "°C", true), + Arguments.of(new QuantityType<>("17 °C"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "°C", true), + Arguments.of(new QuantityType<>("10 °C"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "°C", false), + Arguments.of(new QuantityType<>("20 °C"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "°C", false), + Arguments.of(new QuantityType<>("0 °C"), BigDecimal.valueOf(270), BigDecimal.valueOf(275), "K", true), + // invalid or missing units + Arguments.of(new QuantityType<>("5 kg"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "°C", true), + Arguments.of(new QuantityType<>("17 kg"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "°C", true), + Arguments.of(new QuantityType<>("5 kg"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "", true), + Arguments.of(new QuantityType<>("17 kg"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "", true), + // DecimalType + Arguments.of(new DecimalType("17"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "", true), + Arguments.of(new DecimalType("17"), BigDecimal.valueOf(17), BigDecimal.valueOf(19), "", true), + Arguments.of(new DecimalType("17"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "", true), + Arguments.of(new DecimalType("10"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "", false), + Arguments.of(new DecimalType("20"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "", false)); + } + + private static Stream notArgumentProvider() { + return Stream.of(// + // item state, lower, upper, unit, result + // QuantityType + Arguments.of(new QuantityType<>("17 °C"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "°C", false), + Arguments.of(new QuantityType<>("17 °C"), BigDecimal.valueOf(17), BigDecimal.valueOf(19), "°C", true), + Arguments.of(new QuantityType<>("17 °C"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "°C", true), + Arguments.of(new QuantityType<>("10 °C"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "°C", true), + Arguments.of(new QuantityType<>("20 °C"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "°C", true), + Arguments.of(new QuantityType<>("0 °C"), BigDecimal.valueOf(270), BigDecimal.valueOf(275), "K", false), + // invalid or missing units + Arguments.of(new QuantityType<>("5 kg"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "°C", true), + Arguments.of(new QuantityType<>("17 kg"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "°C", true), + Arguments.of(new QuantityType<>("5 kg"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "", true), + Arguments.of(new QuantityType<>("17 kg"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "", true), + // DecimalType + Arguments.of(new DecimalType("17"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "", false), + Arguments.of(new DecimalType("17"), BigDecimal.valueOf(17), BigDecimal.valueOf(19), "", true), + Arguments.of(new DecimalType("17"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "", true), + Arguments.of(new DecimalType("10"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "", true), + Arguments.of(new DecimalType("20"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "", true)); + } +} diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceThresholdFilterTest.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceThresholdFilterTest.java index 15cef6acc65..e37e22c5030 100644 --- a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceThresholdFilterTest.java +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceThresholdFilterTest.java @@ -70,42 +70,43 @@ public void setup() { @Test public void differentItemSameValue() { - filterTest(ITEM_NAME_2, DecimalType.ZERO, DecimalType.ZERO, "", true); + filterTest(ITEM_NAME_2, DecimalType.ZERO, DecimalType.ZERO, "", false, true); } @ParameterizedTest @MethodSource("argumentProvider") - public void filterTest(State state1, State state2, String unit, boolean expected) { - filterTest(ITEM_NAME_1, state1, state2, unit, expected); + public void filterTest(State state1, State state2, String unit, boolean relative, boolean expected) { + filterTest(ITEM_NAME_1, state1, state2, unit, relative, expected); } private static Stream argumentProvider() { return Stream.of(// // same item, same value -> false - Arguments.of(DecimalType.ZERO, DecimalType.ZERO, "", false), + Arguments.of(DecimalType.ZERO, DecimalType.ZERO, "", false, false), // plain decimal, below threshold, absolute - Arguments.of(DecimalType.ZERO, DecimalType.valueOf("5"), "", false), + Arguments.of(DecimalType.ZERO, DecimalType.valueOf("5"), "", false, false), // plain decimal, above threshold, absolute - Arguments.of(DecimalType.ZERO, DecimalType.valueOf("15"), "", true), + Arguments.of(DecimalType.ZERO, DecimalType.valueOf("15"), "", false, true), // plain decimal, below threshold, relative - Arguments.of(DecimalType.valueOf("10.0"), DecimalType.valueOf("9.5"), "%", false), + Arguments.of(DecimalType.valueOf("10.0"), DecimalType.valueOf("9.5"), "", true, false), // plain decimal, above threshold, relative - Arguments.of(DecimalType.valueOf("10.0"), DecimalType.valueOf("11.5"), "%", true), + Arguments.of(DecimalType.valueOf("10.0"), DecimalType.valueOf("11.5"), "", true, true), // quantity type, below threshold, relative - Arguments.of(new QuantityType<>("15 A"), new QuantityType<>("14000 mA"), "%", false), + Arguments.of(new QuantityType<>("15 A"), new QuantityType<>("14000 mA"), "", true, false), // quantity type, above threshold, relative - Arguments.of(new QuantityType<>("2000 mbar"), new QuantityType<>("2.6 bar"), "%", true), + Arguments.of(new QuantityType<>("2000 mbar"), new QuantityType<>("2.6 bar"), "", true, true), // quantity type, below threshold, absolute, no unit - Arguments.of(new QuantityType<>("100 K"), new QuantityType<>("105 K"), "", false), + Arguments.of(new QuantityType<>("100 K"), new QuantityType<>("105 K"), "", false, false), // quantity type, above threshold, absolute, no unit - Arguments.of(new QuantityType<>("20 V"), new QuantityType<>("9000 mV"), "", true), + Arguments.of(new QuantityType<>("20 V"), new QuantityType<>("9000 mV"), "", false, true), // quantity type, below threshold, absolute, with unit - Arguments.of(new QuantityType<>("10 m"), new QuantityType<>("10.002 m"), "mm", false), + Arguments.of(new QuantityType<>("10 m"), new QuantityType<>("10.002 m"), "mm", false, false), // quantity type, above threshold, absolute, with unit - Arguments.of(new QuantityType<>("-10 °C"), new QuantityType<>("5 °C"), "K", true)); + Arguments.of(new QuantityType<>("-10 °C"), new QuantityType<>("5 °C"), "K", false, true)); } - private void filterTest(String item2name, State state1, State state2, String unit, boolean expected) { + private void filterTest(String item2name, State state1, State state2, String unit, boolean relative, + boolean expected) { String itemType = "Number"; if (state1 instanceof QuantityType q) { itemType += ":" + UnitUtils.getDimensionName(q.getUnit()); @@ -117,7 +118,7 @@ private void filterTest(String item2name, State state1, State state2, String uni item1.setState(state1); item2.setState(state2); - PersistenceFilter filter = new PersistenceThresholdFilter("test", BigDecimal.TEN, unit); + PersistenceFilter filter = new PersistenceThresholdFilter("test", BigDecimal.TEN, unit, relative); assertThat(filter.apply(item1), is(true)); filter.persisted(item1); diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/internal/PersistenceManagerTest.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/internal/PersistenceManagerTest.java index 9172dd3e83e..b33766fbd4a 100644 --- a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/internal/PersistenceManagerTest.java +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/internal/PersistenceManagerTest.java @@ -308,7 +308,7 @@ public void cronStrategyIsScheduledAndCancelledAndPersistsValue() throws Excepti new PersistenceCronStrategy("withoutFilter", "0 0 * * * ?"), null); addConfiguration(TEST_QUERYABLE_PERSISTENCE_SERVICE_ID, new PersistenceItemConfig(TEST_ITEM3_NAME), new PersistenceCronStrategy("withFilter", "0 * * * * ?"), - new PersistenceThresholdFilter("test", BigDecimal.TEN, "")); + new PersistenceThresholdFilter("test", BigDecimal.TEN, "", false)); manager.onReadyMarkerAdded(new ReadyMarker("", "")); @@ -352,7 +352,7 @@ public void cronStrategyIsProperlyUpdated() { @Test public void filterAppliesOnStateUpdate() { addConfiguration(TEST_PERSISTENCE_SERVICE_ID, new PersistenceAllConfig(), PersistenceStrategy.Globals.UPDATE, - new PersistenceThresholdFilter("test", BigDecimal.TEN, "")); + new PersistenceThresholdFilter("test", BigDecimal.TEN, "", false)); manager.stateUpdated(TEST_ITEM3, DecimalType.ZERO); manager.stateUpdated(TEST_ITEM3, DecimalType.ZERO); From 36f95bb5b5bd7b14172a0807939db5dc75f3618e Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 18 Jun 2023 21:08:47 +0200 Subject: [PATCH 112/126] Fix missing query prefix in JarFileAddonService (#3664) See title. This brings the query id in-line with the other add-on services. Also adds correct content type. Signed-off-by: Jan N. Klug --- .../openhab/core/addon/internal/JarFileAddonService.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/JarFileAddonService.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/JarFileAddonService.java index 2b7819900b6..8b8fe68bcbe 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/JarFileAddonService.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/JarFileAddonService.java @@ -53,6 +53,7 @@ public class JarFileAddonService extends BundleTracker implements AddonService { public static final String SERVICE_ID = "jar"; public static final String SERVICE_NAME = "JAR-File add-on service"; + private static final String ADDONS_CONTENT_TYPE = "application/vnd.openhab.bundle"; private static final Map ADDON_TYPE_MAP = Map.of( // "automation", new AddonType("automation", "Automation"), // @@ -155,7 +156,7 @@ private Addon toAddon(Bundle bundle, AddonInfo addonInfo) { .withVersion(bundle.getVersion().toString()).withLabel(addonInfo.getName()) .withConfigDescriptionURI(addonInfo.getConfigDescriptionURI()) .withDescription(Objects.requireNonNullElse(addonInfo.getDescription(), bundle.getSymbolicName())) - .build(); + .withContentType(ADDONS_CONTENT_TYPE).build(); } @Override @@ -168,7 +169,8 @@ public List getAddons(@Nullable Locale locale) { @Override public @Nullable Addon getAddon(String id, @Nullable Locale locale) { - return addons.get(id); + String queryId = id.startsWith(ADDON_ID_PREFIX) ? id : ADDON_ID_PREFIX + id; + return addons.get(queryId); } @Override From 64fd046266d3c5896d90c899a519b58147c3c8db Mon Sep 17 00:00:00 2001 From: J-N-K Date: Fri, 23 Jun 2023 10:46:43 +0200 Subject: [PATCH 113/126] Bump spifly to 1.3.6 and asm to 9.4 to support records (#3658) Signed-off-by: Jan N. Klug --- bom/runtime/pom.xml | 2 +- bundles/org.openhab.core.semantics/pom.xml | 2 +- .../karaf/openhab-tp/src/main/feature/feature.xml | 14 +++++++------- itests/org.openhab.core.io.net.tests/itest.bndrun | 10 +++++----- .../itest.bndrun | 13 +++++++------ 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 8862acbdead..0f118c6079b 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -320,7 +320,7 @@ org.apache.aries.spifly org.apache.aries.spifly.dynamic.bundle - 1.3.4 + 1.3.6 diff --git a/bundles/org.openhab.core.semantics/pom.xml b/bundles/org.openhab.core.semantics/pom.xml index 54962c3b240..1ebf3cec206 100644 --- a/bundles/org.openhab.core.semantics/pom.xml +++ b/bundles/org.openhab.core.semantics/pom.xml @@ -23,7 +23,7 @@ org.ow2.asm asm - 9.2 + 9.4 provided diff --git a/features/karaf/openhab-tp/src/main/feature/feature.xml b/features/karaf/openhab-tp/src/main/feature/feature.xml index ea76ff655e0..3e38044c9ca 100644 --- a/features/karaf/openhab-tp/src/main/feature/feature.xml +++ b/features/karaf/openhab-tp/src/main/feature/feature.xml @@ -103,12 +103,12 @@ - openhab.tp;feature=asm;version=9.2.0 - mvn:org.ow2.asm/asm/9.2 - mvn:org.ow2.asm/asm-analysis/9.2 - mvn:org.ow2.asm/asm-commons/9.2 - mvn:org.ow2.asm/asm-util/9.2 - mvn:org.ow2.asm/asm-tree/9.2 + openhab.tp;feature=asm;version=9.4.0 + mvn:org.ow2.asm/asm/9.4 + mvn:org.ow2.asm/asm-analysis/9.4 + mvn:org.ow2.asm/asm-commons/9.4 + mvn:org.ow2.asm/asm-util/9.4 + mvn:org.ow2.asm/asm-tree/9.4 @@ -120,7 +120,7 @@ mvn:org.osgi/org.osgi.util.function/1.1.0 mvn:org.osgi/org.osgi.util.promise/1.1.1 mvn:org.osgi/org.osgi.service.jaxrs/1.0.0 - mvn:org.apache.aries.spifly/org.apache.aries.spifly.dynamic.bundle/1.3.4 + mvn:org.apache.aries.spifly/org.apache.aries.spifly.dynamic.bundle/1.3.6 mvn:org.apache.aries.component-dsl/org.apache.aries.component-dsl.component-dsl/1.2.2 mvn:org.apache.aries.jax.rs/org.apache.aries.jax.rs.whiteboard/2.0.0 diff --git a/itests/org.openhab.core.io.net.tests/itest.bndrun b/itests/org.openhab.core.io.net.tests/itest.bndrun index 0bd9c43c5e3..9d4983c500e 100644 --- a/itests/org.openhab.core.io.net.tests/itest.bndrun +++ b/itests/org.openhab.core.io.net.tests/itest.bndrun @@ -67,10 +67,10 @@ Fragment-Host: org.openhab.core.io.net uom-lib-common;version='[2.1.0,2.1.1)',\ org.eclipse.jetty.http2.server;version='[9.4.50,9.4.51)',\ org.eclipse.jetty.websocket.servlet;version='[9.4.50,9.4.51)',\ - org.apache.aries.spifly.dynamic.bundle;version='[1.3.4,1.3.5)',\ org.eclipse.jetty.websocket.server;version='[9.4.50,9.4.51)',\ org.objectweb.asm;version='[9.4.0,9.4.1)',\ - org.objectweb.asm.commons;version='[9.2.0,9.2.1)',\ - org.objectweb.asm.tree;version='[9.2.0,9.2.1)',\ - org.objectweb.asm.tree.analysis;version='[9.2.0,9.2.1)',\ - org.objectweb.asm.util;version='[9.2.0,9.2.1)' + org.apache.aries.spifly.dynamic.bundle;version='[1.3.6,1.3.7)',\ + org.objectweb.asm.commons;version='[9.4.0,9.4.1)',\ + org.objectweb.asm.tree;version='[9.4.0,9.4.1)',\ + org.objectweb.asm.tree.analysis;version='[9.4.0,9.4.1)',\ + org.objectweb.asm.util;version='[9.4.0,9.4.1)' diff --git a/itests/org.openhab.core.io.rest.core.tests/itest.bndrun b/itests/org.openhab.core.io.rest.core.tests/itest.bndrun index f2d2f06a979..21cf1349327 100644 --- a/itests/org.openhab.core.io.rest.core.tests/itest.bndrun +++ b/itests/org.openhab.core.io.rest.core.tests/itest.bndrun @@ -70,7 +70,6 @@ Fragment-Host: org.openhab.core.io.rest.core io.methvin.directory-watcher;version='[0.17.1,0.17.2)',\ com.sun.jna;version='[5.12.1,5.12.2)',\ com.fasterxml.woodstox.woodstox-core;version='[6.4.0,6.4.1)',\ - org.apache.aries.spifly.dynamic.bundle;version='[1.3.4,1.3.5)',\ org.apache.felix.configadmin;version='[1.9.26,1.9.27)',\ org.apache.felix.http.servlet-api;version='[1.2.0,1.2.1)',\ org.apache.felix.scr;version='[2.2.4,2.2.5)',\ @@ -83,10 +82,6 @@ Fragment-Host: org.openhab.core.io.rest.core org.eclipse.jetty.util;version='[9.4.50,9.4.51)',\ org.eclipse.jetty.util.ajax;version='[9.4.50,9.4.51)',\ org.eclipse.jetty.xml;version='[9.4.50,9.4.51)',\ - org.objectweb.asm.commons;version='[9.2.0,9.2.1)',\ - org.objectweb.asm.tree;version='[9.2.0,9.2.1)',\ - org.objectweb.asm.tree.analysis;version='[9.2.0,9.2.1)',\ - org.objectweb.asm.util;version='[9.2.0,9.2.1)',\ org.openhab.core.addon;version='[4.0.0,4.0.1)',\ org.ops4j.pax.logging.pax-logging-api;version='[2.2.0,2.2.1)',\ org.ops4j.pax.web.pax-web-api;version='[8.0.15,8.0.16)',\ @@ -106,4 +101,10 @@ Fragment-Host: org.openhab.core.io.rest.core net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ org.mockito.junit-jupiter;version='[4.11.0,4.11.1)',\ org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ - org.objenesis;version='[3.3.0,3.3.1)' + org.objenesis;version='[3.3.0,3.3.1)',\ + org.apache.aries.spifly.dynamic.bundle;version='[1.3.6,1.3.7)',\ + org.objectweb.asm.commons;version='[9.4.0,9.4.1)',\ + org.objectweb.asm.tree;version='[9.4.0,9.4.1)',\ + org.objectweb.asm.tree.analysis;version='[9.4.0,9.4.1)',\ + org.objectweb.asm.util;version='[9.4.0,9.4.1)',\ + org.osgi.service.cm;version='[1.6.0,1.6.1)' From 78e66745abdc82ed8275cd9dc2be49f7fa36ead8 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 25 Jun 2023 16:22:55 +0200 Subject: [PATCH 114/126] Add support for things with generic channels (#3355) * Add support for generic channels Signed-off-by: Jan N. Klug --- bundles/org.openhab.core.thing/pom.xml | 5 + .../thing/binding/generic/ChannelHandler.java | 43 ++++ .../generic/ChannelHandlerContent.java | 55 ++++++ .../thing/binding/generic/ChannelMode.java | 27 +++ .../generic/ChannelTransformation.java | 97 +++++++++ .../generic/ChannelValueConverterConfig.java | 138 +++++++++++++ .../converter/ColorChannelHandler.java | 142 +++++++++++++ .../converter/DimmerChannelHandler.java | 104 ++++++++++ .../FixedValueMappingChannelHandler.java | 65 ++++++ .../converter/GenericChannelHandler.java | 61 ++++++ .../converter/ImageChannelHandler.java | 55 ++++++ .../converter/NumberChannelHandler.java | 79 ++++++++ .../converter/PlayerChannelHandler.java | 86 ++++++++ .../RollershutterChannelHandler.java | 102 ++++++++++ .../AbstractTransformingChannelHandler.java | 115 +++++++++++ .../generic/ChannelTransformationTest.java | 148 ++++++++++++++ .../generic/converter/ConverterTest.java | 186 ++++++++++++++++++ ...AbstractTransformingItemConverterTest.java | 172 ++++++++++++++++ .../core/transform/TransformationHelper.java | 93 ++++++--- .../itest.bndrun | 3 +- .../itest.bndrun | 3 +- .../itest.bndrun | 3 +- .../itest.bndrun | 3 +- .../itest.bndrun | 4 +- .../itest.bndrun | 4 +- .../itest.bndrun | 4 +- .../itest.bndrun | 4 +- .../itest.bndrun | 4 +- .../org.openhab.core.thing.tests/itest.bndrun | 3 +- .../org.openhab.core.voice.tests/itest.bndrun | 3 +- 30 files changed, 1775 insertions(+), 36 deletions(-) create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelHandler.java create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelHandlerContent.java create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelMode.java create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelTransformation.java create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelValueConverterConfig.java create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/ColorChannelHandler.java create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/DimmerChannelHandler.java create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/FixedValueMappingChannelHandler.java create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/GenericChannelHandler.java create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/ImageChannelHandler.java create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/NumberChannelHandler.java create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/PlayerChannelHandler.java create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/RollershutterChannelHandler.java create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/binding/generic/converter/AbstractTransformingChannelHandler.java create mode 100644 bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/binding/generic/ChannelTransformationTest.java create mode 100644 bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/binding/generic/converter/ConverterTest.java create mode 100644 bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/binding/generic/converter/AbstractTransformingItemConverterTest.java diff --git a/bundles/org.openhab.core.thing/pom.xml b/bundles/org.openhab.core.thing/pom.xml index 58cd26a9443..f969dcd2100 100644 --- a/bundles/org.openhab.core.thing/pom.xml +++ b/bundles/org.openhab.core.thing/pom.xml @@ -25,6 +25,11 @@ org.openhab.core.io.console ${project.version} + + org.openhab.core.bundles + org.openhab.core.transform + ${project.version} + org.openhab.core.bundles org.openhab.core.test diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelHandler.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelHandler.java new file mode 100644 index 00000000000..8839167d4f9 --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelHandler.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding.generic; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.types.Command; + +/** + * The {@link ChannelHandler} defines the interface for converting received {@link ChannelHandlerContent} + * to {@link org.openhab.core.types.State}s for posting updates to {@link org.openhab.core.thing.Channel}s and + * {@link Command}s to values for sending + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public interface ChannelHandler { + + /** + * called to process a given content for this channel + * + * @param content raw content to process (null results in + * {@link org.openhab.core.types.UnDefType#UNDEF}) + */ + void process(@Nullable ChannelHandlerContent content); + + /** + * called to send a command to this channel + * + * @param command + */ + void send(Command command); +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelHandlerContent.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelHandlerContent.java new file mode 100644 index 00000000000..ed5b4ad3e15 --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelHandlerContent.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding.generic; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link ChannelHandlerContent} defines the pre-processed response + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class ChannelHandlerContent { + private final byte[] rawContent; + private final Charset encoding; + private final @Nullable String mediaType; + + public ChannelHandlerContent(byte[] rawContent, String encoding, @Nullable String mediaType) { + this.rawContent = rawContent; + this.mediaType = mediaType; + + Charset finalEncoding = StandardCharsets.UTF_8; + try { + finalEncoding = Charset.forName(encoding); + } catch (IllegalArgumentException e) { + } + this.encoding = finalEncoding; + } + + public byte[] getRawContent() { + return rawContent; + } + + public String getAsString() { + return new String(rawContent, encoding); + } + + public @Nullable String getMediaType() { + return mediaType; + } +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelMode.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelMode.java new file mode 100644 index 00000000000..63353866747 --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelMode.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding.generic; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ChannelMode} enum defines control modes for channels + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public enum ChannelMode { + READONLY, + READWRITE, + WRITEONLY +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelTransformation.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelTransformation.java new file mode 100644 index 00000000000..e4187cdae13 --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelTransformation.java @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding.generic; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.transform.TransformationException; +import org.openhab.core.transform.TransformationHelper; +import org.openhab.core.transform.TransformationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link ChannelTransformation} can be used to transform an input value using one or more transformations. + * Individual transformations can be chained with and must follow the pattern + * serviceName:function where serviceName refers to a {@link TransformationService} and + * function has to be a valid transformation function for this service + * + * @author Jan N. Klug - Initial contribution + */ +public class ChannelTransformation { + private final Logger logger = LoggerFactory.getLogger(ChannelTransformation.class); + private List transformationSteps; + + public ChannelTransformation(@Nullable String transformationString) { + if (transformationString != null) { + try { + transformationSteps = Arrays.stream(transformationString.split("∩")).filter(s -> !s.isBlank()) + .map(TransformationStep::new).toList(); + return; + } catch (IllegalArgumentException e) { + logger.warn("Transformation ignored, failed to parse {}: {}", transformationString, e.getMessage()); + } + } + transformationSteps = List.of(); + } + + public Optional apply(String value) { + Optional valueOptional = Optional.of(value); + + // process all transformations + for (TransformationStep transformationStep : transformationSteps) { + valueOptional = valueOptional.flatMap(transformationStep::apply); + } + + logger.trace("Transformed '{}' to '{}' using '{}'", value, valueOptional, transformationSteps); + return valueOptional; + } + + private static class TransformationStep { + private final Logger logger = LoggerFactory.getLogger(TransformationStep.class); + private final String serviceName; + private final String function; + + public TransformationStep(String pattern) throws IllegalArgumentException { + int index = pattern.indexOf(":"); + if (index == -1) { + throw new IllegalArgumentException( + "The transformation pattern must consist of the type and the pattern separated by a colon"); + } + this.serviceName = pattern.substring(0, index).toUpperCase().trim(); + this.function = pattern.substring(index + 1).trim(); + } + + public Optional apply(String value) { + TransformationService service = TransformationHelper.getTransformationService(serviceName); + if (service != null) { + try { + return Optional.ofNullable(service.transform(function, value)); + } catch (TransformationException e) { + logger.debug("Applying {} failed: {}", this, e.getMessage()); + } + } else { + logger.warn("Failed to use {}, service not found", this); + } + return Optional.empty(); + } + + @Override + public String toString() { + return "TransformationStep{serviceName='" + serviceName + "', function='" + function + "'}"; + } + } +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelValueConverterConfig.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelValueConverterConfig.java new file mode 100644 index 00000000000..72dd8a8a053 --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/ChannelValueConverterConfig.java @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding.generic; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.types.IncreaseDecreaseType; +import org.openhab.core.library.types.NextPreviousType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.OpenClosedType; +import org.openhab.core.library.types.PlayPauseType; +import org.openhab.core.library.types.RewindFastforwardType; +import org.openhab.core.library.types.StopMoveType; +import org.openhab.core.library.types.UpDownType; +import org.openhab.core.thing.binding.generic.converter.ColorChannelHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; + +/** + * The {@link ChannelValueConverterConfig} is a base class for the channel configuration of things + * using the {@link ChannelHandler}s + * + * @author Jan N. Klug - Initial contribution + */ + +@NonNullByDefault +public class ChannelValueConverterConfig { + private final Map stringStateMap = new HashMap<>(); + private final Map commandStringMap = new HashMap<>(); + + public ChannelMode mode = ChannelMode.READWRITE; + + // number + public @Nullable String unit; + + // switch, dimmer, color + public @Nullable String onValue; + public @Nullable String offValue; + + // dimmer, color + public BigDecimal step = BigDecimal.ONE; + public @Nullable String increaseValue; + public @Nullable String decreaseValue; + + // color + public ColorChannelHandler.ColorMode colorMode = ColorChannelHandler.ColorMode.RGB; + + // contact + public @Nullable String openValue; + public @Nullable String closedValue; + + // rollershutter + public @Nullable String upValue; + public @Nullable String downValue; + public @Nullable String stopValue; + public @Nullable String moveValue; + + // player + public @Nullable String playValue; + public @Nullable String pauseValue; + public @Nullable String nextValue; + public @Nullable String previousValue; + public @Nullable String rewindValue; + public @Nullable String fastforwardValue; + + private boolean initialized = false; + + /** + * maps a command to a user-defined string + * + * @param command the command to map + * @return a string or null if no mapping found + */ + public @Nullable String commandToFixedValue(Command command) { + if (!initialized) { + createMaps(); + } + + return commandStringMap.get(command); + } + + /** + * maps a user-defined string to a state + * + * @param string the string to map + * @return the state or null if no mapping found + */ + public @Nullable State fixedValueToState(String string) { + if (!initialized) { + createMaps(); + } + + return stringStateMap.get(string); + } + + private void createMaps() { + addToMaps(this.onValue, OnOffType.ON); + addToMaps(this.offValue, OnOffType.OFF); + addToMaps(this.openValue, OpenClosedType.OPEN); + addToMaps(this.closedValue, OpenClosedType.CLOSED); + addToMaps(this.upValue, UpDownType.UP); + addToMaps(this.downValue, UpDownType.DOWN); + + commandStringMap.put(IncreaseDecreaseType.INCREASE, increaseValue); + commandStringMap.put(IncreaseDecreaseType.DECREASE, decreaseValue); + commandStringMap.put(StopMoveType.STOP, stopValue); + commandStringMap.put(StopMoveType.MOVE, moveValue); + commandStringMap.put(PlayPauseType.PLAY, playValue); + commandStringMap.put(PlayPauseType.PAUSE, pauseValue); + commandStringMap.put(NextPreviousType.NEXT, nextValue); + commandStringMap.put(NextPreviousType.PREVIOUS, previousValue); + commandStringMap.put(RewindFastforwardType.REWIND, rewindValue); + commandStringMap.put(RewindFastforwardType.FASTFORWARD, fastforwardValue); + + initialized = true; + } + + private void addToMaps(@Nullable String value, State state) { + if (value != null) { + commandStringMap.put((Command) state, value); + stringStateMap.put(value, state); + } + } +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/ColorChannelHandler.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/ColorChannelHandler.java new file mode 100644 index 00000000000..c2dff0ef26f --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/ColorChannelHandler.java @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding.generic.converter; + +import java.math.BigDecimal; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.types.HSBType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.thing.binding.generic.ChannelTransformation; +import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig; +import org.openhab.core.thing.internal.binding.generic.converter.AbstractTransformingChannelHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link ColorChannelHandler} implements {@link org.openhab.core.library.items.ColorItem} conversions + * + * @author Jan N. Klug - Initial contribution + */ + +@NonNullByDefault +public class ColorChannelHandler extends AbstractTransformingChannelHandler { + private static final BigDecimal BYTE_FACTOR = BigDecimal.valueOf(2.55); + private static final BigDecimal HUNDRED = BigDecimal.valueOf(100); + private static final Pattern TRIPLE_MATCHER = Pattern.compile("(?\\d+),(?\\d+),(?\\d+)"); + + private State state = UnDefType.UNDEF; + + public ColorChannelHandler(Consumer updateState, Consumer postCommand, + @Nullable Consumer sendValue, ChannelTransformation stateTransformations, + ChannelTransformation commandTransformations, ChannelValueConverterConfig channelConfig) { + super(updateState, postCommand, sendValue, stateTransformations, commandTransformations, channelConfig); + } + + @Override + protected @Nullable Command toCommand(String value) { + return null; + } + + @Override + public String toString(Command command) { + String string = channelConfig.commandToFixedValue(command); + if (string != null) { + return string; + } + + if (command instanceof HSBType newState) { + state = newState; + return hsbToString(newState); + } else if (command instanceof PercentType percentCommand && state instanceof HSBType colorState) { + HSBType newState = new HSBType(colorState.getBrightness(), colorState.getSaturation(), percentCommand); + state = newState; + return hsbToString(newState); + } + + throw new IllegalArgumentException("Command type '" + command.toString() + "' not supported"); + } + + @Override + public Optional toState(String string) { + State newState = UnDefType.UNDEF; + if (string.equals(channelConfig.onValue)) { + if (state instanceof HSBType) { + newState = new HSBType(((HSBType) state).getHue(), ((HSBType) state).getSaturation(), + PercentType.HUNDRED); + } else { + newState = HSBType.WHITE; + } + } else if (string.equals(channelConfig.offValue)) { + if (state instanceof HSBType) { + newState = new HSBType(((HSBType) state).getHue(), ((HSBType) state).getSaturation(), PercentType.ZERO); + } else { + newState = HSBType.BLACK; + } + } else if (string.equals(channelConfig.increaseValue) && state instanceof HSBType) { + BigDecimal newBrightness = ((HSBType) state).getBrightness().toBigDecimal().add(channelConfig.step); + if (HUNDRED.compareTo(newBrightness) < 0) { + newBrightness = HUNDRED; + } + newState = new HSBType(((HSBType) state).getHue(), ((HSBType) state).getSaturation(), + new PercentType(newBrightness)); + } else if (string.equals(channelConfig.decreaseValue) && state instanceof HSBType) { + BigDecimal newBrightness = ((HSBType) state).getBrightness().toBigDecimal().subtract(channelConfig.step); + if (BigDecimal.ZERO.compareTo(newBrightness) > 0) { + newBrightness = BigDecimal.ZERO; + } + newState = new HSBType(((HSBType) state).getHue(), ((HSBType) state).getSaturation(), + new PercentType(newBrightness)); + } else { + Matcher matcher = TRIPLE_MATCHER.matcher(string); + if (matcher.matches()) { + switch (channelConfig.colorMode) { + case RGB -> { + int r = Integer.parseInt(matcher.group("r")); + int g = Integer.parseInt(matcher.group("g")); + int b = Integer.parseInt(matcher.group("b")); + newState = HSBType.fromRGB(r, g, b); + } + case HSB -> newState = new HSBType(string); + } + } + } + + state = newState; + return Optional.of(newState); + } + + private String hsbToString(HSBType state) { + switch (channelConfig.colorMode) { + case RGB: + PercentType[] rgb = state.toRGB(); + return String.format("%1$d,%2$d,%3$d", rgb[0].toBigDecimal().multiply(BYTE_FACTOR).intValue(), + rgb[1].toBigDecimal().multiply(BYTE_FACTOR).intValue(), + rgb[2].toBigDecimal().multiply(BYTE_FACTOR).intValue()); + case HSB: + return state.toString(); + } + throw new IllegalStateException("Invalid colorMode setting"); + } + + public enum ColorMode { + RGB, + HSB + } +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/DimmerChannelHandler.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/DimmerChannelHandler.java new file mode 100644 index 00000000000..f9980de7831 --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/DimmerChannelHandler.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding.generic.converter; + +import java.math.BigDecimal; +import java.util.Optional; +import java.util.function.Consumer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.thing.binding.generic.ChannelTransformation; +import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig; +import org.openhab.core.thing.internal.binding.generic.converter.AbstractTransformingChannelHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link DimmerChannelHandler} implements {@link org.openhab.core.library.items.DimmerItem} conversions + * + * @author Jan N. Klug - Initial contribution + */ + +@NonNullByDefault +public class DimmerChannelHandler extends AbstractTransformingChannelHandler { + private static final BigDecimal HUNDRED = BigDecimal.valueOf(100); + + private State state = UnDefType.UNDEF; + + public DimmerChannelHandler(Consumer updateState, Consumer postCommand, + @Nullable Consumer sendValue, ChannelTransformation stateTransformations, + ChannelTransformation commandTransformations, ChannelValueConverterConfig channelConfig) { + super(updateState, postCommand, sendValue, stateTransformations, commandTransformations, channelConfig); + } + + @Override + protected @Nullable Command toCommand(String value) { + return null; + } + + @Override + public String toString(Command command) { + String string = channelConfig.commandToFixedValue(command); + if (string != null) { + return string; + } + + if (command instanceof PercentType) { + return ((PercentType) command).toString(); + } + + throw new IllegalArgumentException("Command type '" + command.toString() + "' not supported"); + } + + @Override + public Optional toState(String string) { + State newState = UnDefType.UNDEF; + + if (string.equals(channelConfig.onValue)) { + newState = PercentType.HUNDRED; + } else if (string.equals(channelConfig.offValue)) { + newState = PercentType.ZERO; + } else if (string.equals(channelConfig.increaseValue) && state instanceof PercentType) { + BigDecimal newBrightness = ((PercentType) state).toBigDecimal().add(channelConfig.step); + if (HUNDRED.compareTo(newBrightness) < 0) { + newBrightness = HUNDRED; + } + newState = new PercentType(newBrightness); + } else if (string.equals(channelConfig.decreaseValue) && state instanceof PercentType) { + BigDecimal newBrightness = ((PercentType) state).toBigDecimal().subtract(channelConfig.step); + if (BigDecimal.ZERO.compareTo(newBrightness) > 0) { + newBrightness = BigDecimal.ZERO; + } + newState = new PercentType(newBrightness); + } else { + try { + BigDecimal value = new BigDecimal(string); + if (value.compareTo(PercentType.HUNDRED.toBigDecimal()) > 0) { + value = PercentType.HUNDRED.toBigDecimal(); + } + if (value.compareTo(PercentType.ZERO.toBigDecimal()) < 0) { + value = PercentType.ZERO.toBigDecimal(); + } + newState = new PercentType(value); + } catch (NumberFormatException e) { + // ignore + } + } + + state = newState; + return Optional.of(newState); + } +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/FixedValueMappingChannelHandler.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/FixedValueMappingChannelHandler.java new file mode 100644 index 00000000000..47ea726d3b6 --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/FixedValueMappingChannelHandler.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding.generic.converter; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.thing.binding.generic.ChannelTransformation; +import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig; +import org.openhab.core.thing.internal.binding.generic.converter.AbstractTransformingChannelHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link FixedValueMappingChannelHandler} implements mapping conversions for different item-types + * + * @author Jan N. Klug - Initial contribution + */ + +@NonNullByDefault +public class FixedValueMappingChannelHandler extends AbstractTransformingChannelHandler { + + public FixedValueMappingChannelHandler(Consumer updateState, Consumer postCommand, + @Nullable Consumer sendValue, ChannelTransformation stateTransformations, + ChannelTransformation commandTransformations, ChannelValueConverterConfig channelConfig) { + super(updateState, postCommand, sendValue, stateTransformations, commandTransformations, channelConfig); + } + + @Override + protected @Nullable Command toCommand(String value) { + return null; + } + + @Override + public String toString(Command command) { + String value = channelConfig.commandToFixedValue(command); + if (value != null) { + return value; + } + + throw new IllegalArgumentException( + "Command type '" + command.toString() + "' not supported or mapping not defined."); + } + + @Override + public Optional toState(String string) { + State state = channelConfig.fixedValueToState(string); + + return Optional.of(Objects.requireNonNullElse(state, UnDefType.UNDEF)); + } +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/GenericChannelHandler.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/GenericChannelHandler.java new file mode 100644 index 00000000000..aa25055270c --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/GenericChannelHandler.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding.generic.converter; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.thing.binding.generic.ChannelTransformation; +import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig; +import org.openhab.core.thing.internal.binding.generic.converter.AbstractTransformingChannelHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link GenericChannelHandler} implements simple conversions for different item types + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class GenericChannelHandler extends AbstractTransformingChannelHandler { + private final Function toState; + + public GenericChannelHandler(Function toState, Consumer updateState, + Consumer postCommand, @Nullable Consumer sendValue, + ChannelTransformation stateTransformations, ChannelTransformation commandTransformations, + ChannelValueConverterConfig channelConfig) { + super(updateState, postCommand, sendValue, stateTransformations, commandTransformations, channelConfig); + this.toState = toState; + } + + protected Optional toState(String value) { + try { + return Optional.of(toState.apply(value)); + } catch (IllegalArgumentException e) { + return Optional.of(UnDefType.UNDEF); + } + } + + @Override + protected @Nullable Command toCommand(String value) { + return null; + } + + protected String toString(Command command) { + return command.toString(); + } +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/ImageChannelHandler.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/ImageChannelHandler.java new file mode 100644 index 00000000000..ff23ea3289b --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/ImageChannelHandler.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding.generic.converter; + +import java.util.function.Consumer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.types.RawType; +import org.openhab.core.thing.binding.generic.ChannelHandler; +import org.openhab.core.thing.binding.generic.ChannelHandlerContent; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link ImageChannelHandler} implements {@link org.openhab.core.library.items.ImageItem} conversions + * + * @author Jan N. Klug - Initial contribution + */ + +@NonNullByDefault +public class ImageChannelHandler implements ChannelHandler { + private final Consumer updateState; + + public ImageChannelHandler(Consumer updateState) { + this.updateState = updateState; + } + + @Override + public void process(@Nullable ChannelHandlerContent content) { + if (content == null) { + updateState.accept(UnDefType.UNDEF); + return; + } + String mediaType = content.getMediaType(); + updateState.accept( + new RawType(content.getRawContent(), mediaType != null ? mediaType : RawType.DEFAULT_MIME_TYPE)); + } + + @Override + public void send(Command command) { + throw new IllegalStateException("Read-only channel"); + } +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/NumberChannelHandler.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/NumberChannelHandler.java new file mode 100644 index 00000000000..91a48514d4c --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/NumberChannelHandler.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding.generic.converter; + +import java.util.Optional; +import java.util.function.Consumer; + +import javax.measure.format.MeasurementParseException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.thing.binding.generic.ChannelTransformation; +import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig; +import org.openhab.core.thing.internal.binding.generic.converter.AbstractTransformingChannelHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link NumberChannelHandler} implements {@link org.openhab.core.library.items.NumberItem} conversions + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class NumberChannelHandler extends AbstractTransformingChannelHandler { + + public NumberChannelHandler(Consumer updateState, Consumer postCommand, + @Nullable Consumer sendValue, ChannelTransformation stateTransformations, + ChannelTransformation commandTransformations, ChannelValueConverterConfig channelConfig) { + super(updateState, postCommand, sendValue, stateTransformations, commandTransformations, channelConfig); + } + + @Override + protected @Nullable Command toCommand(String value) { + return null; + } + + @Override + protected Optional toState(String value) { + String trimmedValue = value.trim(); + State newState = UnDefType.UNDEF; + if (!trimmedValue.isEmpty()) { + try { + if (channelConfig.unit != null) { + // we have a given unit - use that + newState = new QuantityType<>(trimmedValue + " " + channelConfig.unit); + } else { + try { + // try if we have a simple number + newState = new DecimalType(trimmedValue); + } catch (IllegalArgumentException e1) { + // not a plain number, maybe with unit? + newState = new QuantityType<>(trimmedValue); + } + } + } catch (IllegalArgumentException | MeasurementParseException e) { + // finally failed + } + } + return Optional.of(newState); + } + + @Override + protected String toString(Command command) { + return command.toString(); + } +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/PlayerChannelHandler.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/PlayerChannelHandler.java new file mode 100644 index 00000000000..0afe6f41c18 --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/PlayerChannelHandler.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding.generic.converter; + +import java.util.Optional; +import java.util.function.Consumer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.types.NextPreviousType; +import org.openhab.core.library.types.PlayPauseType; +import org.openhab.core.library.types.RewindFastforwardType; +import org.openhab.core.thing.binding.generic.ChannelTransformation; +import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig; +import org.openhab.core.thing.internal.binding.generic.converter.AbstractTransformingChannelHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; + +/** + * The {@link PlayerChannelHandler} implements {@link org.openhab.core.library.items.RollershutterItem} + * conversions + * + * @author Jan N. Klug - Initial contribution + */ + +@NonNullByDefault +public class PlayerChannelHandler extends AbstractTransformingChannelHandler { + private @Nullable String lastCommand; // store last command to prevent duplicate commands + + public PlayerChannelHandler(Consumer updateState, Consumer postCommand, + @Nullable Consumer sendValue, ChannelTransformation stateTransformations, + ChannelTransformation commandTransformations, ChannelValueConverterConfig channelConfig) { + super(updateState, postCommand, sendValue, stateTransformations, commandTransformations, channelConfig); + } + + @Override + public String toString(Command command) { + String string = channelConfig.commandToFixedValue(command); + if (string != null) { + return string; + } + + throw new IllegalArgumentException("Command type '" + command.toString() + "' not supported"); + } + + @Override + protected @Nullable Command toCommand(String string) { + if (string.equals(lastCommand)) { + // only send commands once + return null; + } + lastCommand = string; + + if (string.equals(channelConfig.playValue)) { + return PlayPauseType.PLAY; + } else if (string.equals(channelConfig.pauseValue)) { + return PlayPauseType.PAUSE; + } else if (string.equals(channelConfig.nextValue)) { + return NextPreviousType.NEXT; + } else if (string.equals(channelConfig.previousValue)) { + return NextPreviousType.PREVIOUS; + } else if (string.equals(channelConfig.rewindValue)) { + return RewindFastforwardType.REWIND; + } else if (string.equals(channelConfig.fastforwardValue)) { + return RewindFastforwardType.FASTFORWARD; + } + + return null; + } + + @Override + public Optional toState(String string) { + // no value - we ignore state updates + return Optional.empty(); + } +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/RollershutterChannelHandler.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/RollershutterChannelHandler.java new file mode 100644 index 00000000000..cf0ef1df0a6 --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/binding/generic/converter/RollershutterChannelHandler.java @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding.generic.converter; + +import java.math.BigDecimal; +import java.util.Optional; +import java.util.function.Consumer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.StopMoveType; +import org.openhab.core.library.types.UpDownType; +import org.openhab.core.thing.binding.generic.ChannelTransformation; +import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig; +import org.openhab.core.thing.internal.binding.generic.converter.AbstractTransformingChannelHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link RollershutterChannelHandler} implements {@link org.openhab.core.library.items.RollershutterItem} + * conversions + * + * @author Jan N. Klug - Initial contribution + */ + +@NonNullByDefault +public class RollershutterChannelHandler extends AbstractTransformingChannelHandler { + + public RollershutterChannelHandler(Consumer updateState, Consumer postCommand, + @Nullable Consumer sendValue, ChannelTransformation stateTransformations, + ChannelTransformation commandTransformations, ChannelValueConverterConfig channelConfig) { + super(updateState, postCommand, sendValue, stateTransformations, commandTransformations, channelConfig); + } + + @Override + public String toString(Command command) { + String string = channelConfig.commandToFixedValue(command); + if (string != null) { + return string; + } + + if (command instanceof PercentType) { + final String downValue = channelConfig.downValue; + final String upValue = channelConfig.upValue; + if (command.equals(PercentType.HUNDRED) && downValue != null) { + return downValue; + } else if (command.equals(PercentType.ZERO) && upValue != null) { + return upValue; + } else { + return ((PercentType) command).toString(); + } + } + + throw new IllegalArgumentException("Command type '" + command.toString() + "' not supported"); + } + + @Override + protected @Nullable Command toCommand(String string) { + if (string.equals(channelConfig.upValue)) { + return UpDownType.UP; + } else if (string.equals(channelConfig.downValue)) { + return UpDownType.DOWN; + } else if (string.equals(channelConfig.moveValue)) { + return StopMoveType.MOVE; + } else if (string.equals(channelConfig.stopValue)) { + return StopMoveType.STOP; + } + + return null; + } + + @Override + public Optional toState(String string) { + State newState = UnDefType.UNDEF; + try { + BigDecimal value = new BigDecimal(string); + if (value.compareTo(PercentType.HUNDRED.toBigDecimal()) > 0) { + value = PercentType.HUNDRED.toBigDecimal(); + } + if (value.compareTo(PercentType.ZERO.toBigDecimal()) < 0) { + value = PercentType.ZERO.toBigDecimal(); + } + newState = new PercentType(value); + } catch (NumberFormatException e) { + // ignore + } + + return Optional.of(newState); + } +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/binding/generic/converter/AbstractTransformingChannelHandler.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/binding/generic/converter/AbstractTransformingChannelHandler.java new file mode 100644 index 00000000000..b4e332f0c9a --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/binding/generic/converter/AbstractTransformingChannelHandler.java @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.internal.binding.generic.converter; + +import java.util.Optional; +import java.util.function.Consumer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.thing.binding.generic.ChannelHandler; +import org.openhab.core.thing.binding.generic.ChannelHandlerContent; +import org.openhab.core.thing.binding.generic.ChannelMode; +import org.openhab.core.thing.binding.generic.ChannelTransformation; +import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link AbstractTransformingChannelHandler} is a base class for an item converter with transformations + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public abstract class AbstractTransformingChannelHandler implements ChannelHandler { + private final Consumer updateState; + private final Consumer postCommand; + private final @Nullable Consumer sendValue; + private final ChannelTransformation stateTransformations; + private final ChannelTransformation commandTransformations; + + protected final ChannelValueConverterConfig channelConfig; + + public AbstractTransformingChannelHandler(Consumer updateState, Consumer postCommand, + @Nullable Consumer sendValue, ChannelTransformation stateTransformations, + ChannelTransformation commandTransformations, ChannelValueConverterConfig channelConfig) { + this.updateState = updateState; + this.postCommand = postCommand; + this.sendValue = sendValue; + this.stateTransformations = stateTransformations; + this.commandTransformations = commandTransformations; + this.channelConfig = channelConfig; + } + + @Override + public void process(@Nullable ChannelHandlerContent content) { + if (content == null) { + updateState.accept(UnDefType.UNDEF); + return; + } + if (channelConfig.mode != ChannelMode.WRITEONLY) { + stateTransformations.apply(content.getAsString()).ifPresent(transformedValue -> { + Command command = toCommand(transformedValue); + if (command != null) { + postCommand.accept(command); + } else { + toState(transformedValue).ifPresent(updateState); + } + }); + } else { + throw new IllegalStateException("Write-only channel"); + } + } + + @Override + public void send(Command command) { + Consumer sendHttpValue = this.sendValue; + if (sendHttpValue != null && channelConfig.mode != ChannelMode.READONLY) { + commandTransformations.apply(toString(command)).ifPresent(sendHttpValue); + } else { + throw new IllegalStateException("Read-only channel"); + } + } + + /** + * check if this converter received a value that needs to be sent as command + * + * @param value the value + * @return the command or null + */ + protected abstract @Nullable Command toCommand(String value); + + /** + * convert the received value to a state + * + * @param value the value + * @return the state that represents the value of UNDEF if conversion failed + */ + protected abstract Optional toState(String value); + + /** + * convert a command to a string + * + * @param command the command + * @return the string representation of the command + */ + protected abstract String toString(Command command); + + @FunctionalInterface + public interface Factory { + ChannelHandler create(Consumer updateState, Consumer postCommand, + @Nullable Consumer sendHttpValue, ChannelTransformation stateTransformations, + ChannelTransformation commandTransformations, ChannelValueConverterConfig channelConfig); + } +} diff --git a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/binding/generic/ChannelTransformationTest.java b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/binding/generic/ChannelTransformationTest.java new file mode 100644 index 00000000000..d7847b63228 --- /dev/null +++ b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/binding/generic/ChannelTransformationTest.java @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding.generic; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.openhab.core.transform.TransformationException; +import org.openhab.core.transform.TransformationHelper; +import org.openhab.core.transform.TransformationService; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; + +/** + * The {@link ChannelTransformationTest} contains tests for the {@link ChannelTransformation} + * + * @author Jan N. Klug - Initial contribution + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@NonNullByDefault +public class ChannelTransformationTest { + private static final String T1_NAME = "TRANSFORM1"; + private static final String T1_PATTERN = "T1Pattern"; + private static final String T1_INPUT = "T1Input"; + private static final String T1_RESULT = "T1Result"; + + private static final String T2_NAME = "TRANSFORM2"; + private static final String T2_PATTERN = "T2Pattern"; + private static final String T2_INPUT = T1_RESULT; + private static final String T2_RESULT = "T2Result"; + + private @Mock @NonNullByDefault({}) TransformationService transformationService1Mock; + private @Mock @NonNullByDefault({}) TransformationService transformationService2Mock; + + private @Mock @NonNullByDefault({}) BundleContext bundleContextMock; + private @Mock @NonNullByDefault({}) ServiceReference serviceRef1Mock; + private @Mock @NonNullByDefault({}) ServiceReference serviceRef2Mock; + + private @NonNullByDefault({}) TransformationHelper transformationHelper; + + @BeforeEach + public void init() throws TransformationException { + Mockito.when(transformationService1Mock.transform(eq(T1_PATTERN), eq(T1_INPUT))) + .thenAnswer(answer -> T1_RESULT); + Mockito.when(transformationService2Mock.transform(eq(T2_PATTERN), eq(T1_INPUT))) + .thenAnswer(answer -> T2_RESULT); + Mockito.when(transformationService2Mock.transform(eq(T2_PATTERN), eq(T2_INPUT))) + .thenAnswer(answer -> T2_RESULT); + + Mockito.when(serviceRef1Mock.getProperty(any())).thenReturn("TRANSFORM1"); + Mockito.when(serviceRef2Mock.getProperty(any())).thenReturn("TRANSFORM2"); + + Mockito.when(bundleContextMock.getService(serviceRef1Mock)).thenReturn(transformationService1Mock); + Mockito.when(bundleContextMock.getService(serviceRef2Mock)).thenReturn(transformationService2Mock); + + transformationHelper = new TransformationHelper(bundleContextMock); + transformationHelper.setTransformationService(serviceRef1Mock); + transformationHelper.setTransformationService(serviceRef2Mock); + } + + @AfterEach + public void tearDown() { + transformationHelper.deactivate(); + } + + @Test + public void testMissingTransformation() { + String pattern = "TRANSFORM:pattern"; + + ChannelTransformation transformation = new ChannelTransformation(pattern); + String result = transformation.apply(T1_INPUT).orElse(null); + + assertNull(result); + } + + @Test + public void testSingleTransformation() { + String pattern = T1_NAME + ":" + T1_PATTERN; + + ChannelTransformation transformation = new ChannelTransformation(pattern); + String result = transformation.apply(T1_INPUT).orElse(null); + + assertEquals(T1_RESULT, result); + } + + @Test + public void testInvalidFirstTransformation() { + String pattern = T1_NAME + "X:" + T1_PATTERN + "∩" + T2_NAME + ":" + T2_PATTERN; + + ChannelTransformation transformation = new ChannelTransformation(pattern); + String result = transformation.apply(T1_INPUT).orElse(null); + + assertNull(result); + } + + @Test + public void testInvalidSecondTransformation() { + String pattern = T1_NAME + ":" + T1_PATTERN + "∩" + T2_NAME + "X:" + T2_PATTERN; + + ChannelTransformation transformation = new ChannelTransformation(pattern); + String result = transformation.apply(T1_INPUT).orElse(null); + + assertNull(result); + } + + @Test + public void testDoubleTransformationWithoutSpaces() { + String pattern = T1_NAME + ":" + T1_PATTERN + "∩" + T2_NAME + ":" + T2_PATTERN; + + ChannelTransformation transformation = new ChannelTransformation(pattern); + String result = transformation.apply(T1_INPUT).orElse(null); + + assertEquals(T2_RESULT, result); + } + + @Test + public void testDoubleTransformationWithSpaces() { + String pattern = " " + T1_NAME + " : " + T1_PATTERN + " ∩ " + T2_NAME + " : " + T2_PATTERN + " "; + + ChannelTransformation transformation = new ChannelTransformation(pattern); + String result = transformation.apply(T1_INPUT).orElse(null); + + assertEquals(T2_RESULT, result); + } +} diff --git a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/binding/generic/converter/ConverterTest.java b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/binding/generic/converter/ConverterTest.java new file mode 100644 index 00000000000..1d5e2e6b24e --- /dev/null +++ b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/binding/generic/converter/ConverterTest.java @@ -0,0 +1,186 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.binding.generic.converter; + +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.HSBType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.PlayPauseType; +import org.openhab.core.library.types.PointType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.binding.generic.ChannelHandlerContent; +import org.openhab.core.thing.binding.generic.ChannelTransformation; +import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link ConverterTest} is a test class for state converters + * + * @author Jan N. Klug - Initial contribution + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@NonNullByDefault +public class ConverterTest { + + private @Mock @NonNullByDefault({}) Consumer sendValueMock; + + private @Mock @NonNullByDefault({}) Consumer updateStateMock; + + private @Mock @NonNullByDefault({}) Consumer postCommandMock; + + @Test + public void numberItemConverter() { + NumberChannelHandler converter = new NumberChannelHandler(updateStateMock, postCommandMock, sendValueMock, + new ChannelTransformation(null), new ChannelTransformation(null), new ChannelValueConverterConfig()); + + // without unit + Assertions.assertEquals(Optional.of(new DecimalType(1234)), converter.toState("1234")); + + // unit in transformation result + Assertions.assertEquals(Optional.of(new QuantityType<>(100, SIUnits.CELSIUS)), converter.toState("100°C")); + + // no valid value + Assertions.assertEquals(Optional.of(UnDefType.UNDEF), converter.toState("W")); + Assertions.assertEquals(Optional.of(UnDefType.UNDEF), converter.toState("")); + } + + @Test + public void numberItemConverterWithUnit() { + ChannelValueConverterConfig channelConfig = new ChannelValueConverterConfig(); + channelConfig.unit = "W"; + NumberChannelHandler converter = new NumberChannelHandler(updateStateMock, postCommandMock, sendValueMock, + new ChannelTransformation(null), new ChannelTransformation(null), channelConfig); + + // without unit + Assertions.assertEquals(Optional.of(new QuantityType<>(500, Units.WATT)), converter.toState("500")); + + // no valid value + Assertions.assertEquals(Optional.of(UnDefType.UNDEF), converter.toState("foo")); + Assertions.assertEquals(Optional.of(UnDefType.UNDEF), converter.toState("")); + } + + @Test + public void stringTypeConverter() { + GenericChannelHandler converter = createConverter(StringType::new); + Assertions.assertEquals(Optional.of(new StringType("Test")), converter.toState("Test")); + } + + @Test + public void decimalTypeConverter() { + GenericChannelHandler converter = createConverter(DecimalType::new); + Assertions.assertEquals(Optional.of(new DecimalType(15.6)), converter.toState("15.6")); + } + + @Test + public void pointTypeConverter() { + GenericChannelHandler converter = createConverter(PointType::new); + Assertions.assertEquals( + Optional.of(new PointType(new DecimalType(51.1), new DecimalType(7.2), new DecimalType(100))), + converter.toState("51.1, 7.2, 100")); + } + + @Test + public void playerItemTypeConverter() { + ChannelValueConverterConfig cfg = new ChannelValueConverterConfig(); + cfg.playValue = "PLAY"; + ChannelHandlerContent content = new ChannelHandlerContent("PLAY".getBytes(StandardCharsets.UTF_8), "UTF-8", + null); + PlayerChannelHandler converter = new PlayerChannelHandler(updateStateMock, postCommandMock, sendValueMock, + new ChannelTransformation(null), new ChannelTransformation(null), cfg); + converter.process(content); + converter.process(content); + + Mockito.verify(postCommandMock).accept(PlayPauseType.PLAY); + Mockito.verify(updateStateMock, Mockito.never()).accept(ArgumentMatchers.any()); + } + + @Test + public void colorItemTypeRGBConverter() { + ChannelValueConverterConfig cfg = new ChannelValueConverterConfig(); + cfg.colorMode = ColorChannelHandler.ColorMode.RGB; + ChannelHandlerContent content = new ChannelHandlerContent("123,34,47".getBytes(StandardCharsets.UTF_8), "UTF-8", + null); + ColorChannelHandler converter = new ColorChannelHandler(updateStateMock, postCommandMock, sendValueMock, + new ChannelTransformation(null), new ChannelTransformation(null), cfg); + + converter.process(content); + Mockito.verify(updateStateMock).accept(HSBType.fromRGB(123, 34, 47)); + } + + @Test + public void colorItemTypeHSBConverter() { + ChannelValueConverterConfig cfg = new ChannelValueConverterConfig(); + cfg.colorMode = ColorChannelHandler.ColorMode.HSB; + ChannelHandlerContent content = new ChannelHandlerContent("123,34,47".getBytes(StandardCharsets.UTF_8), "UTF-8", + null); + ColorChannelHandler converter = new ColorChannelHandler(updateStateMock, postCommandMock, sendValueMock, + new ChannelTransformation(null), new ChannelTransformation(null), cfg); + + converter.process(content); + Mockito.verify(updateStateMock).accept(new HSBType("123,34,47")); + } + + @Test + public void rollerSHutterConverter() { + ChannelValueConverterConfig cfg = new ChannelValueConverterConfig(); + RollershutterChannelHandler converter = new RollershutterChannelHandler(updateStateMock, postCommandMock, + sendValueMock, new ChannelTransformation(null), new ChannelTransformation(null), cfg); + + // test 0 and 100 + ChannelHandlerContent content = new ChannelHandlerContent("0".getBytes(StandardCharsets.UTF_8), "UTF-8", null); + converter.process(content); + Mockito.verify(updateStateMock).accept(PercentType.ZERO); + content = new ChannelHandlerContent("100".getBytes(StandardCharsets.UTF_8), "UTF-8", null); + converter.process(content); + Mockito.verify(updateStateMock).accept(PercentType.HUNDRED); + + // test under/over-range (expect two times total for zero/100 + content = new ChannelHandlerContent("-1".getBytes(StandardCharsets.UTF_8), "UTF-8", null); + converter.process(content); + Mockito.verify(updateStateMock, Mockito.times(2)).accept(PercentType.ZERO); + content = new ChannelHandlerContent("105".getBytes(StandardCharsets.UTF_8), "UTF-8", null); + converter.process(content); + Mockito.verify(updateStateMock, Mockito.times(2)).accept(PercentType.HUNDRED); + + // test value + content = new ChannelHandlerContent("67".getBytes(StandardCharsets.UTF_8), "UTF-8", null); + converter.process(content); + Mockito.verify(updateStateMock).accept(new PercentType(67)); + } + + public GenericChannelHandler createConverter(Function fcn) { + return new GenericChannelHandler(fcn, updateStateMock, postCommandMock, sendValueMock, + new ChannelTransformation(null), new ChannelTransformation(null), new ChannelValueConverterConfig()); + } +} diff --git a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/binding/generic/converter/AbstractTransformingItemConverterTest.java b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/binding/generic/converter/AbstractTransformingItemConverterTest.java new file mode 100644 index 00000000000..08207319d4a --- /dev/null +++ b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/binding/generic/converter/AbstractTransformingItemConverterTest.java @@ -0,0 +1,172 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.thing.internal.binding.generic.converter; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.function.Consumer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.binding.generic.ChannelHandlerContent; +import org.openhab.core.thing.binding.generic.ChannelTransformation; +import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link AbstractTransformingItemConverterTest} is a test class for the + * {@link AbstractTransformingChannelHandler} + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class AbstractTransformingItemConverterTest { + + @Mock + private @NonNullByDefault({}) Consumer sendHttpValue; + + @Mock + private @NonNullByDefault({}) Consumer updateState; + + @Mock + private @NonNullByDefault({}) Consumer postCommand; + + private @NonNullByDefault({}) AutoCloseable closeable; + + @Spy + private ChannelTransformation stateChannelTransformation = new ChannelTransformation(null); + + @Spy + private ChannelTransformation commandChannelTransformation = new ChannelTransformation(null); + + @BeforeEach + public void init() { + closeable = MockitoAnnotations.openMocks(this); + } + + @AfterEach + public void close() throws Exception { + closeable.close(); + } + + @Test + public void undefOnNullContentTest() { + TestChannelHandler realConverter = new TestChannelHandler(updateState, postCommand, sendHttpValue, + stateChannelTransformation, commandChannelTransformation, false); + TestChannelHandler converter = spy(realConverter); + + converter.process(null); + // make sure UNDEF is send as state update + verify(updateState, only()).accept(UnDefType.UNDEF); + verify(postCommand, never()).accept(any()); + verify(sendHttpValue, never()).accept(any()); + + // make sure no other processing applies + verify(converter, never()).toState(any()); + verify(converter, never()).toCommand(any()); + verify(converter, never()).toString(any()); + } + + @Test + public void commandIsPostedAsCommand() { + TestChannelHandler converter = new TestChannelHandler(updateState, postCommand, sendHttpValue, + stateChannelTransformation, commandChannelTransformation, true); + + converter.process(new ChannelHandlerContent("TEST".getBytes(StandardCharsets.UTF_8), "", null)); + + // check state transformation is applied + verify(stateChannelTransformation).apply(any()); + verify(commandChannelTransformation, never()).apply(any()); + + // check only postCommand is applied + verify(updateState, never()).accept(any()); + verify(postCommand, only()).accept(new StringType("TEST")); + verify(sendHttpValue, never()).accept(any()); + } + + @Test + public void updateIsPostedAsUpdate() { + TestChannelHandler converter = new TestChannelHandler(updateState, postCommand, sendHttpValue, + stateChannelTransformation, commandChannelTransformation, false); + + converter.process(new ChannelHandlerContent("TEST".getBytes(StandardCharsets.UTF_8), "", null)); + + // check state transformation is applied + verify(stateChannelTransformation).apply(any()); + verify(commandChannelTransformation, never()).apply(any()); + + // check only updateState is called + verify(updateState, only()).accept(new StringType("TEST")); + verify(postCommand, never()).accept(any()); + verify(sendHttpValue, never()).accept(any()); + } + + @Test + public void sendCommandSendsCommand() { + TestChannelHandler converter = new TestChannelHandler(updateState, postCommand, sendHttpValue, + stateChannelTransformation, commandChannelTransformation, false); + + converter.send(new StringType("TEST")); + + // check command transformation is applied + verify(stateChannelTransformation, never()).apply(any()); + verify(commandChannelTransformation).apply(any()); + + // check only sendHttpValue is applied + verify(updateState, never()).accept(any()); + verify(postCommand, never()).accept(any()); + verify(sendHttpValue, only()).accept("TEST"); + } + + private static class TestChannelHandler extends AbstractTransformingChannelHandler { + private boolean hasCommand; + + public TestChannelHandler(Consumer updateState, Consumer postCommand, + @Nullable Consumer sendValue, ChannelTransformation stateChannelTransformation, + ChannelTransformation commandChannelTransformation, boolean hasCommand) { + super(updateState, postCommand, sendValue, stateChannelTransformation, commandChannelTransformation, + new ChannelValueConverterConfig()); + this.hasCommand = hasCommand; + } + + @Override + protected @Nullable Command toCommand(String value) { + return hasCommand ? new StringType(value) : null; + } + + @Override + protected Optional toState(String value) { + return Optional.of(new StringType(value)); + } + + @Override + protected String toString(Command command) { + return command.toString(); + } + } +} diff --git a/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationHelper.java b/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationHelper.java index 93500be5e19..94e953d7253 100644 --- a/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationHelper.java +++ b/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationHelper.java @@ -12,25 +12,34 @@ */ package org.openhab.core.transform; -import java.util.Collection; import java.util.IllegalFormatException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.osgi.framework.BundleContext; -import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Kai Kreuzer - Initial contribution + * @author Jan N. Klug - Refactored to OSGi service */ +@Component(immediate = true) @NonNullByDefault public class TransformationHelper { + private static final Map SERVICES = new ConcurrentHashMap<>(); private static final Logger LOGGER = LoggerFactory.getLogger(TransformationHelper.class); @@ -40,6 +49,35 @@ public class TransformationHelper { protected static final Pattern EXTRACT_TRANSFORMFUNCTION_PATTERN = Pattern .compile("(.*?)\\((.*)\\)" + FUNCTION_VALUE_DELIMITER + "(.*)"); + private final BundleContext bundleContext; + + @Activate + public TransformationHelper(BundleContext bundleContext) { + this.bundleContext = bundleContext; + } + + @Deactivate + public void deactivate() { + SERVICES.clear(); + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void setTransformationService(ServiceReference ref) { + String key = (String) ref.getProperty(TransformationService.SERVICE_PROPERTY_NAME); + TransformationService service = bundleContext.getService(ref); + if (service != null) { + SERVICES.put(key, service); + LOGGER.debug("Added transformation service {}", key); + } + } + + public void unsetTransformationService(ServiceReference ref) { + String key = (String) ref.getProperty(TransformationService.SERVICE_PROPERTY_NAME); + if (SERVICES.remove(key) != null) { + LOGGER.debug("Removed transformation service {}", key); + } + } + /** * determines whether a pattern refers to a transformation service * @@ -50,52 +88,57 @@ public static boolean isTransform(String pattern) { return EXTRACT_TRANSFORMFUNCTION_PATTERN.matcher(pattern).matches(); } + public static @Nullable TransformationService getTransformationService(String serviceName) { + return SERVICES.get(serviceName); + } + /** - * Queries the OSGi service registry for a service that provides a transformation service of - * a given transformation type (e.g. REGEX, XSLT, etc.) + * Return the transformation service that provides a given transformation type (e.g. REGEX, XSLT, etc.) * * @param context the bundle context which can be null * @param transformationType the desired transformation type * @return a service instance or null, if none could be found + * + * @deprecated use {@link #getTransformationService(String)} instead */ + @Deprecated public static @Nullable TransformationService getTransformationService(@Nullable BundleContext context, String transformationType) { - if (context != null) { - String filter = "(" + TransformationService.SERVICE_PROPERTY_NAME + "=" + transformationType + ")"; - try { - Collection> refs = context - .getServiceReferences(TransformationService.class, filter); - if (refs != null && !refs.isEmpty()) { - return context.getService(refs.iterator().next()); - } else { - LOGGER.debug("Cannot get service reference for transformation service of type {}", - transformationType); - } - } catch (InvalidSyntaxException e) { - LOGGER.debug("Cannot get service reference for transformation service of type {}", transformationType, - e); - } - } - return null; + return getTransformationService(transformationType); } /** * Transforms a state string using transformation functions within a given pattern. * * @param context a valid bundle context, required for accessing the services - * @param stateDescPattern the pattern that contains the transformation instructions + * @param transformationString the pattern that contains the transformation instructions * @param state the state to be formatted before being passed into the transformation function * @return the result of the transformation. If no transformation was done, null is returned * @throws TransformationException if transformation service is not available or the transformation failed + * + * @deprecated Use {@link #transform(String, String)} instead */ - public static @Nullable String transform(BundleContext context, String stateDescPattern, String state) + @Deprecated + public static @Nullable String transform(BundleContext context, String transformationString, String state) throws TransformationException { - Matcher matcher = EXTRACT_TRANSFORMFUNCTION_PATTERN.matcher(stateDescPattern); + return transform(transformationString, state); + } + + /** + * Transforms a state string using transformation functions within a given pattern. + * + * @param transformationString the pattern that contains the transformation instructions + * @param state the state to be formatted before being passed into the transformation function + * @return the result of the transformation. If no transformation was done, null is returned + * @throws TransformationException if transformation service is not available or the transformation failed + */ + public static @Nullable String transform(String transformationString, String state) throws TransformationException { + Matcher matcher = EXTRACT_TRANSFORMFUNCTION_PATTERN.matcher(transformationString); if (matcher.find()) { String type = matcher.group(1); String pattern = matcher.group(2); String value = matcher.group(3); - TransformationService transformation = TransformationHelper.getTransformationService(context, type); + TransformationService transformation = SERVICES.get(type); if (transformation != null) { return transform(transformation, pattern, value, state); } else { diff --git a/itests/org.openhab.core.automation.integration.tests/itest.bndrun b/itests/org.openhab.core.automation.integration.tests/itest.bndrun index 21aafaab4ef..9323415d07c 100644 --- a/itests/org.openhab.core.automation.integration.tests/itest.bndrun +++ b/itests/org.openhab.core.automation.integration.tests/itest.bndrun @@ -66,4 +66,5 @@ Fragment-Host: org.openhab.core.automation net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ org.objenesis;version='[3.3.0,3.3.1)',\ - org.osgi.service.cm;version='[1.6.0,1.6.1)' + org.osgi.service.cm;version='[1.6.0,1.6.1)',\ + org.openhab.core.transform;version='[4.0.0,4.0.1)' diff --git a/itests/org.openhab.core.automation.module.core.tests/itest.bndrun b/itests/org.openhab.core.automation.module.core.tests/itest.bndrun index de798e868f5..f87b5a71009 100644 --- a/itests/org.openhab.core.automation.module.core.tests/itest.bndrun +++ b/itests/org.openhab.core.automation.module.core.tests/itest.bndrun @@ -66,4 +66,5 @@ Fragment-Host: org.openhab.core.automation net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ org.objenesis;version='[3.3.0,3.3.1)',\ - org.osgi.service.cm;version='[1.6.0,1.6.1)' + org.osgi.service.cm;version='[1.6.0,1.6.1)',\ + org.openhab.core.transform;version='[4.0.0,4.0.1)' diff --git a/itests/org.openhab.core.automation.module.timer.tests/itest.bndrun b/itests/org.openhab.core.automation.module.timer.tests/itest.bndrun index 364e3f2dcac..5e6f415c499 100644 --- a/itests/org.openhab.core.automation.module.timer.tests/itest.bndrun +++ b/itests/org.openhab.core.automation.module.timer.tests/itest.bndrun @@ -66,4 +66,5 @@ Fragment-Host: org.openhab.core.automation net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ org.objenesis;version='[3.3.0,3.3.1)',\ - org.osgi.service.cm;version='[1.6.0,1.6.1)' + org.osgi.service.cm;version='[1.6.0,1.6.1)',\ + org.openhab.core.transform;version='[4.0.0,4.0.1)' diff --git a/itests/org.openhab.core.automation.tests/itest.bndrun b/itests/org.openhab.core.automation.tests/itest.bndrun index 55ed88082d3..98fb146d9a8 100644 --- a/itests/org.openhab.core.automation.tests/itest.bndrun +++ b/itests/org.openhab.core.automation.tests/itest.bndrun @@ -66,4 +66,5 @@ Fragment-Host: org.openhab.core.automation net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ org.objenesis;version='[3.3.0,3.3.1)',\ - org.osgi.service.cm;version='[1.6.0,1.6.1)' + org.osgi.service.cm;version='[1.6.0,1.6.1)',\ + org.openhab.core.transform;version='[4.0.0,4.0.1)' diff --git a/itests/org.openhab.core.config.discovery.mdns.tests/itest.bndrun b/itests/org.openhab.core.config.discovery.mdns.tests/itest.bndrun index 1c4f83e7614..b283f7cac4f 100644 --- a/itests/org.openhab.core.config.discovery.mdns.tests/itest.bndrun +++ b/itests/org.openhab.core.config.discovery.mdns.tests/itest.bndrun @@ -65,4 +65,6 @@ Fragment-Host: org.openhab.core.config.discovery.mdns net.bytebuddy.byte-buddy;version='[1.12.19,1.12.20)',\ net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ - org.objenesis;version='[3.3.0,3.3.1)' + org.objenesis;version='[3.3.0,3.3.1)',\ + org.openhab.core.transform;version='[4.0.0,4.0.1)',\ + org.osgi.service.cm;version='[1.6.0,1.6.1)' diff --git a/itests/org.openhab.core.config.discovery.tests/itest.bndrun b/itests/org.openhab.core.config.discovery.tests/itest.bndrun index 2f70c26108f..ad2ab040a73 100644 --- a/itests/org.openhab.core.config.discovery.tests/itest.bndrun +++ b/itests/org.openhab.core.config.discovery.tests/itest.bndrun @@ -64,4 +64,6 @@ Fragment-Host: org.openhab.core.config.discovery net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ org.mockito.junit-jupiter;version='[4.11.0,4.11.1)',\ org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ - org.objenesis;version='[3.3.0,3.3.1)' + org.objenesis;version='[3.3.0,3.3.1)',\ + org.openhab.core.transform;version='[4.0.0,4.0.1)',\ + org.osgi.service.cm;version='[1.6.0,1.6.1)' diff --git a/itests/org.openhab.core.config.discovery.usbserial.linuxsysfs.tests/itest.bndrun b/itests/org.openhab.core.config.discovery.usbserial.linuxsysfs.tests/itest.bndrun index 402acf7ea78..5f27c622288 100644 --- a/itests/org.openhab.core.config.discovery.usbserial.linuxsysfs.tests/itest.bndrun +++ b/itests/org.openhab.core.config.discovery.usbserial.linuxsysfs.tests/itest.bndrun @@ -65,4 +65,6 @@ Fragment-Host: org.openhab.core.config.discovery.usbserial.linuxsysfs net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ org.mockito.junit-jupiter;version='[4.11.0,4.11.1)',\ org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ - org.objenesis;version='[3.3.0,3.3.1)' + org.objenesis;version='[3.3.0,3.3.1)',\ + org.openhab.core.transform;version='[4.0.0,4.0.1)',\ + org.osgi.service.cm;version='[1.6.0,1.6.1)' diff --git a/itests/org.openhab.core.config.discovery.usbserial.tests/itest.bndrun b/itests/org.openhab.core.config.discovery.usbserial.tests/itest.bndrun index 956ee342b1a..20edb85c9d7 100644 --- a/itests/org.openhab.core.config.discovery.usbserial.tests/itest.bndrun +++ b/itests/org.openhab.core.config.discovery.usbserial.tests/itest.bndrun @@ -73,4 +73,6 @@ Provide-Capability: \ net.bytebuddy.byte-buddy;version='[1.12.19,1.12.20)',\ net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ - org.objenesis;version='[3.3.0,3.3.1)' + org.objenesis;version='[3.3.0,3.3.1)',\ + org.openhab.core.transform;version='[4.0.0,4.0.1)',\ + org.osgi.service.cm;version='[1.6.0,1.6.1)' diff --git a/itests/org.openhab.core.storage.json.tests/itest.bndrun b/itests/org.openhab.core.storage.json.tests/itest.bndrun index 1f52ea404a7..d029def728f 100644 --- a/itests/org.openhab.core.storage.json.tests/itest.bndrun +++ b/itests/org.openhab.core.storage.json.tests/itest.bndrun @@ -58,4 +58,6 @@ Fragment-Host: org.openhab.core.storage.json junit-jupiter-engine;version='[5.9.2,5.9.3)',\ junit-platform-commons;version='[1.9.2,1.9.3)',\ junit-platform-engine;version='[1.9.2,1.9.3)',\ - junit-platform-launcher;version='[1.9.2,1.9.3)' + junit-platform-launcher;version='[1.9.2,1.9.3)',\ + org.openhab.core.transform;version='[4.0.0,4.0.1)',\ + org.osgi.service.cm;version='[1.6.0,1.6.1)' diff --git a/itests/org.openhab.core.thing.tests/itest.bndrun b/itests/org.openhab.core.thing.tests/itest.bndrun index 93bb6e00233..dc229ee0af3 100644 --- a/itests/org.openhab.core.thing.tests/itest.bndrun +++ b/itests/org.openhab.core.thing.tests/itest.bndrun @@ -66,4 +66,5 @@ Fragment-Host: org.openhab.core.thing net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ org.mockito.junit-jupiter;version='[4.11.0,4.11.1)',\ org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ - org.objenesis;version='[3.3.0,3.3.1)' + org.objenesis;version='[3.3.0,3.3.1)',\ + org.openhab.core.transform;version='[4.0.0,4.0.1)' diff --git a/itests/org.openhab.core.voice.tests/itest.bndrun b/itests/org.openhab.core.voice.tests/itest.bndrun index b4e85f066ff..5d0738bd7d7 100644 --- a/itests/org.openhab.core.voice.tests/itest.bndrun +++ b/itests/org.openhab.core.voice.tests/itest.bndrun @@ -72,4 +72,5 @@ Fragment-Host: org.openhab.core.voice junit-jupiter-params;version='[5.9.2,5.9.3)',\ junit-platform-commons;version='[1.9.2,1.9.3)',\ junit-platform-engine;version='[1.9.2,1.9.3)',\ - junit-platform-launcher;version='[1.9.2,1.9.3)' + junit-platform-launcher;version='[1.9.2,1.9.3)',\ + org.openhab.core.transform;version='[4.0.0,4.0.1)' From db976101116804d65df232f283827f4b78ade096 Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Sun, 25 Jun 2023 17:24:46 +0200 Subject: [PATCH 115/126] Replace StringBuffer usages with StringBuilder (#3668) See: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/StringBuilder.html > This class provides an API compatible with StringBuffer, but with no guarantee of synchronization. > This class is designed for use as a drop-in replacement for StringBuffer in places where the string buffer was being used by a single thread (as is generally the case). > Where possible, it is recommended that this class be used in preference to StringBuffer as it will be faster under most implementations. Signed-off-by: Wouter Born --- .../automation/internal/RuleEngineImpl.java | 23 ++++++++----------- .../automation/internal/RuleRegistryImpl.java | 6 ++--- .../core/io/console/ConsoleInterpreter.java | 2 +- .../binding/internal/MagicServiceConfig.java | 2 +- ...FirmwareUpdateConsoleCommandExtension.java | 2 +- 5 files changed, 15 insertions(+), 20 deletions(-) diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java index 1585d0be863..9d3adc6e14c 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java @@ -266,7 +266,7 @@ public RuleEngineImpl(final @Reference ModuleTypeRegistry moduleTypeRegistry, this.ruleRegistry = ruleRegistry; this.readyService = readyService; - listener = new RegistryChangeListener() { + listener = new RegistryChangeListener<>() { @Override public void added(Rule rule) { RuleEngineImpl.this.addRule(rule); @@ -337,8 +337,7 @@ public void added(ModuleType moduleType) { synchronized (this) { Set rulesPerModule = mapModuleTypeToRules.get(moduleTypeName); if (rulesPerModule != null) { - rules = new HashSet<>(); - rules.addAll(rulesPerModule); + rules = new HashSet<>(rulesPerModule); } } if (rules != null) { @@ -366,8 +365,7 @@ public void updated(ModuleType oldElement, ModuleType moduleType) { synchronized (this) { Set rulesPerModule = mapModuleTypeToRules.get(moduleTypeName); if (rulesPerModule != null) { - rules = new HashSet<>(); - rules.addAll(rulesPerModule); + rules = new HashSet<>(rulesPerModule); } } if (rules != null) { @@ -402,8 +400,7 @@ protected void addModuleHandlerFactory(ModuleHandlerFactory moduleHandlerFactory moduleHandlerFactories.put(moduleTypeName, moduleHandlerFactory); Set rulesPerModule = mapModuleTypeToRules.get(moduleTypeName); if (rulesPerModule != null) { - rules = new HashSet<>(); - rules.addAll(rulesPerModule); + rules = new HashSet<>(rulesPerModule); } } if (rules != null) { @@ -505,10 +502,8 @@ private void setRule(WrappedRule rule) { final boolean activated = activateRule(rule); if (activated) { Future f = scheduleTasks.remove(rUID); - if (f != null) { - if (!f.isDone()) { - f.cancel(true); - } + if ((f != null) && !f.isDone()) { + f.cancel(true); } } } @@ -942,7 +937,7 @@ private void removeMissingModuleTypes(Collection moduleTypes) { for (Entry> e : mapMissingHandlers.entrySet()) { String rUID = e.getKey(); List missingTypes = e.getValue(); - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); sb.append("Missing handlers: "); for (String typeUID : missingTypes) { sb.append(typeUID).append(", "); @@ -1097,7 +1092,7 @@ private Map getContext(String ruleUID, @Nullable Set throw new IllegalStateException("context cannot be null at that point - please report a bug."); } if (connections != null) { - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (Connection c : connections) { String outputModuleId = c.getOutputModuleId(); if (outputModuleId != null) { @@ -1438,7 +1433,7 @@ public void onReadyMarkerRemoved(ReadyMarker readyMarker) { private void executeRulesWithStartLevel() { getScheduledExecutor().submit(() -> { ruleRegistry.getAll().stream() // - .filter(r -> mustTrigger(r)) // + .filter(this::mustTrigger) // .forEach(r -> runNow(r.getUID(), true, Map.of(SystemTriggerHandler.OUT_STARTLEVEL, StartLevelService.STARTLEVEL_RULES))); started = true; diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleRegistryImpl.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleRegistryImpl.java index c7c5971823a..f50a9f5d081 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleRegistryImpl.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleRegistryImpl.java @@ -500,7 +500,7 @@ private void validateConfiguration(List configDescri if (isOptionalConfig(configDescriptions)) { return; } else { - StringBuffer statusDescription = new StringBuffer(); + StringBuilder statusDescription = new StringBuilder(); String msg = " '%s';"; for (ConfigDescriptionParameter configParameter : configDescriptions) { if (configParameter.isRequired()) { @@ -517,7 +517,7 @@ private void validateConfiguration(List configDescri processValue(configurations.remove(configParameterName), configParameter); } if (!configurations.isEmpty()) { - StringBuffer statusDescription = new StringBuffer(); + StringBuilder statusDescription = new StringBuilder(); String msg = " '%s';"; for (String name : configurations.keySet()) { statusDescription.append(String.format(msg, name)); @@ -610,7 +610,7 @@ private boolean checkType(Type type, Object configValue) { */ private void resolveModuleConfigReferences(List modules, Map ruleConfiguration) { if (modules != null) { - StringBuffer statusDescription = new StringBuffer(); + StringBuilder statusDescription = new StringBuilder(); for (Module module : modules) { try { ReferenceResolver.updateConfiguration(module.getConfiguration(), ruleConfiguration, logger); diff --git a/bundles/org.openhab.core.io.console/src/main/java/org/openhab/core/io/console/ConsoleInterpreter.java b/bundles/org.openhab.core.io.console/src/main/java/org/openhab/core/io/console/ConsoleInterpreter.java index d4f94db622a..01c8d076eb4 100644 --- a/bundles/org.openhab.core.io.console/src/main/java/org/openhab/core/io/console/ConsoleInterpreter.java +++ b/bundles/org.openhab.core.io.console/src/main/java/org/openhab/core/io/console/ConsoleInterpreter.java @@ -33,7 +33,7 @@ public class ConsoleInterpreter { public static String getHelp(final String base, final String separator, Collection extensions) { final List usages = ConsoleInterpreter.getUsages(extensions); - final StringBuffer buffer = new StringBuffer(); + final StringBuilder buffer = new StringBuilder(); buffer.append("---openHAB commands---\n\t"); for (int i = 0; i < usages.size(); i++) { diff --git a/bundles/org.openhab.core.test.magic/src/main/java/org/openhab/core/magic/binding/internal/MagicServiceConfig.java b/bundles/org.openhab.core.test.magic/src/main/java/org/openhab/core/magic/binding/internal/MagicServiceConfig.java index ce8895e0774..1e46d4bf392 100644 --- a/bundles/org.openhab.core.test.magic/src/main/java/org/openhab/core/magic/binding/internal/MagicServiceConfig.java +++ b/bundles/org.openhab.core.test.magic/src/main/java/org/openhab/core/magic/binding/internal/MagicServiceConfig.java @@ -46,7 +46,7 @@ public class MagicServiceConfig { @Override public String toString() { - StringBuffer b = new StringBuffer(); + StringBuilder b = new StringBuilder(); for (Field field : this.getClass().getDeclaredFields()) { Object value; try { diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/console/FirmwareUpdateConsoleCommandExtension.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/console/FirmwareUpdateConsoleCommandExtension.java index 3e91ea4b99d..b102afba530 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/console/FirmwareUpdateConsoleCommandExtension.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/console/FirmwareUpdateConsoleCommandExtension.java @@ -132,7 +132,7 @@ private void listFirmwareStatus(Console console, String[] args) { FirmwareStatusInfo firmwareStatusInfo = firmwareUpdateService.getFirmwareStatusInfo(thingUID); if (firmwareStatusInfo != null) { - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); sb.append(String.format("Firmware status for thing with UID %s is %s.", thingUID, firmwareStatusInfo.getFirmwareStatus())); From 505e51210e11f5a2ab1016a5c2098c7a28f22959 Mon Sep 17 00:00:00 2001 From: Scott Hraban Date: Sun, 2 Jul 2023 00:31:02 -0700 Subject: [PATCH 116/126] Always use ThingUid as the unique identifier for thing status metrics (#3674) * Always use ThingUid as the unique identifier for thing status metrics At bind time, ThingId was being used, but then ThingUid was being used when changes occured, causing multiple meters to be created for the same Thing, with the status always being 0 for the meter created at bind time. This change uses the full ThingUid, for both binding and event processing, since ThingUid is the globally unique value. Fixes #3672 Signed-off-by: Scott Hraban --- .../internal/metrics/ThingStateMetric.java | 21 +++-- .../metrics/ThingStateMetricTest.java | 91 +++++++++++++++++++ 2 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 bundles/org.openhab.core.io.monitor/src/test/java/org/openhab/core/io/monitor/internal/metrics/ThingStateMetricTest.java diff --git a/bundles/org.openhab.core.io.monitor/src/main/java/org/openhab/core/io/monitor/internal/metrics/ThingStateMetric.java b/bundles/org.openhab.core.io.monitor/src/main/java/org/openhab/core/io/monitor/internal/metrics/ThingStateMetric.java index b74cd99416a..4501bbacfed 100644 --- a/bundles/org.openhab.core.io.monitor/src/main/java/org/openhab/core/io/monitor/internal/metrics/ThingStateMetric.java +++ b/bundles/org.openhab.core.io.monitor/src/main/java/org/openhab/core/io/monitor/internal/metrics/ThingStateMetric.java @@ -40,16 +40,18 @@ import io.micrometer.core.instrument.Tags; /** - * The {@link ThingStateMetric} class implements a metric for the openHAB things states. + * The {@link ThingStateMetric} class implements a metric for the openHAB things + * states. * * @author Robert Bach - Initial contribution + * @author Scott Hraban - Create Meter using thingUid instead of thingId during + * bind phase */ @NonNullByDefault public class ThingStateMetric implements OpenhabCoreMeterBinder, EventSubscriber { private final Logger logger = LoggerFactory.getLogger(ThingStateMetric.class); public static final String METRIC_NAME = "openhab.thing.state"; private static final String THING_TAG_NAME = "thing"; - private static final String THINGSTATUS_TOPIC_PREFIX = "openhab/things/"; private final ThingRegistry thingRegistry; private final Meter.Id commonMeterId; private final Map registeredMeters = new HashMap<>(); @@ -70,7 +72,7 @@ public void bindTo(@NonNullByDefault({}) MeterRegistry meterRegistry) { logger.debug("ThingStateMetric is being bound..."); this.meterRegistry = meterRegistry; thingRegistry.getAll().forEach( - thing -> createOrUpdateMetricForBundleState(thing.getUID().getId(), thing.getStatus().ordinal())); + thing -> createOrUpdateMetricForBundleState(thing.getUID().getAsString(), thing.getStatus().ordinal())); eventSubscriberRegistration = this.bundleContext.registerService(EventSubscriber.class.getName(), this, null); } @@ -111,10 +113,13 @@ public Set getSubscribedEventTypes() { @Override public void receive(Event event) { - logger.trace("Received ThingStatusInfo(Changed)Event..."); - String thingId = event.getTopic().substring(THINGSTATUS_TOPIC_PREFIX.length(), - event.getTopic().lastIndexOf('/')); - ThingStatus status = gson.fromJson(event.getPayload(), ThingStatusInfo.class).getStatus(); - createOrUpdateMetricForBundleState(thingId, status.ordinal()); + if (event instanceof ThingStatusInfoEvent thingEvent) { + logger.trace("Received ThingStatusInfo(Changed)Event..."); + String thingUid = thingEvent.getThingUID().getAsString(); + ThingStatus status = gson.fromJson(event.getPayload(), ThingStatusInfo.class).getStatus(); + createOrUpdateMetricForBundleState(thingUid, status.ordinal()); + } else { + logger.trace("Received unsubscribed for event type {}", event.getClass().getSimpleName()); + } } } diff --git a/bundles/org.openhab.core.io.monitor/src/test/java/org/openhab/core/io/monitor/internal/metrics/ThingStateMetricTest.java b/bundles/org.openhab.core.io.monitor/src/test/java/org/openhab/core/io/monitor/internal/metrics/ThingStateMetricTest.java new file mode 100644 index 00000000000..61118050546 --- /dev/null +++ b/bundles/org.openhab.core.io.monitor/src/test/java/org/openhab/core/io/monitor/internal/metrics/ThingStateMetricTest.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.monitor.internal.metrics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingRegistry; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.events.ThingEventFactory; +import org.openhab.core.thing.internal.ThingImpl; +import org.osgi.framework.BundleContext; + +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; + +/** + * Tests for ThingStateMetric class + * + * @author Scott Hraban - Initial contribution + */ +@ExtendWith(MockitoExtension.class) +@NonNullByDefault +public class ThingStateMetricTest { + + @Test + public void testThingUidAlwaysUsedToCreateMeter() { + final String strThingTypeUid = "sonos:Amp"; + + final String strThingUid = strThingTypeUid + ":RINCON_347E5C0D150501400"; + ThingUID thingUid = new ThingUID(strThingUid); + Thing thing = new ThingImpl(new ThingTypeUID(strThingTypeUid), thingUid); + + final String strThingUid2 = strThingTypeUid + ":foo"; + ThingUID thingUid2 = new ThingUID(strThingUid2); + + ThingRegistry thingRegistry = mock(ThingRegistry.class); + + SimpleMeterRegistry meterRegistry = new SimpleMeterRegistry(); + + ThingStateMetric thingStateMetric = new ThingStateMetric(mock(BundleContext.class), thingRegistry, + new HashSet()); + + // Only one meter registered at bind time + doReturn(Collections.singleton(thing)).when(thingRegistry).getAll(); + thingStateMetric.bindTo(meterRegistry); + + List meters = meterRegistry.getMeters(); + assertEquals(1, meters.size()); + assertEquals(strThingUid, meters.get(0).getId().getTag("thing")); + + // Still only one meter registered after receiving an event + ThingStatusInfo thingStatusInfo = new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null); + thingStateMetric.receive(ThingEventFactory.createStatusInfoEvent(thingUid, thingStatusInfo)); + + meters = meterRegistry.getMeters(); + assertEquals(1, meters.size()); + assertEquals(strThingUid, meters.get(0).getId().getTag("thing")); + + // Now another one is added + thingStateMetric.receive(ThingEventFactory.createStatusInfoEvent(thingUid2, thingStatusInfo)); + + meters = meterRegistry.getMeters(); + assertEquals(2, meters.size()); + } +} From 5ceaa64768ed1f22e40d7b72c7d95aced28a5fa1 Mon Sep 17 00:00:00 2001 From: Mark Herwege Date: Sun, 2 Jul 2023 11:03:00 +0200 Subject: [PATCH 117/126] [rest] Add widget state pattern and default unit to ItemUIRegistry (#3644) * expose widget format pattern to sitemap REST * Add unit fields to sitemap REST Signed-off-by: Mark Herwege --- .../sitemap/internal/SitemapResource.java | 3 + .../io/rest/sitemap/internal/WidgetDTO.java | 4 + .../ui/internal/items/ItemUIRegistryImpl.java | 109 +++++++++++++----- .../openhab/core/ui/items/ItemUIRegistry.java | 10 ++ 4 files changed, 98 insertions(+), 28 deletions(-) diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java index 478f244a141..84d3823ff44 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java @@ -125,6 +125,7 @@ * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification * @author Wouter Born - Migrated to OpenAPI annotations * @author Laurent Garnier - Added support for icon color + * @author Mark Herwege - Added pattern and unit fields */ @Component(service = RESTResource.class) @JaxrsResource @@ -520,6 +521,8 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null bean.valuecolor = convertItemValueColor(itemUIRegistry.getValueColor(widget), itemState); bean.iconcolor = convertItemValueColor(itemUIRegistry.getIconColor(widget), itemState); bean.label = itemUIRegistry.getLabel(widget); + bean.pattern = itemUIRegistry.getFormatPattern(widget); + bean.unit = itemUIRegistry.getUnitForWidget(widget); bean.type = widget.eClass().getName(); bean.visibility = itemUIRegistry.getVisiblity(widget); if (widget instanceof LinkableWidget linkableWidget) { diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/WidgetDTO.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/WidgetDTO.java index db47ddd27e8..c9dc713b082 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/WidgetDTO.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/WidgetDTO.java @@ -24,6 +24,7 @@ * @author Kai Kreuzer - Initial contribution * @author Chris Jackson - Initial contribution * @author Laurent Garnier - New field iconcolor + * @author Mark herwege - New fields pattern, unit */ public class WidgetDTO { @@ -38,6 +39,9 @@ public class WidgetDTO { public String valuecolor; public String iconcolor; + public String pattern; + public String unit; + // widget-specific attributes public final List mappings = new ArrayList<>(); public Boolean switchSupport; diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java index 4be4185cddc..d5a69469427 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java @@ -108,6 +108,7 @@ * @author Stefan Triller - Method to convert a state into something a sitemap entity can understand * @author Erdoan Hadzhiyusein - Adapted the class to work with the new DateTimeType * @author Laurent Garnier - new method getIconColor + * @author Mark Herwege - new method getFormatPattern(widget), clean pattern */ @NonNullByDefault @Component(immediate = true, configurationPid = "org.openhab.sitemap", // @@ -121,9 +122,9 @@ public class ItemUIRegistryImpl implements ItemUIRegistry { protected static final Pattern EXTRACT_TRANSFORM_FUNCTION_PATTERN = Pattern.compile("(.*?)\\((.*)\\):(.*)"); /* RegEx to identify format patterns. See java.util.Formatter#formatSpecifier (without the '%' at the very end). */ - protected static final String IDENTIFY_FORMAT_PATTERN_PATTERN = "%((unit%)|((\\d+\\$)?([-#+ 0,(<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z])))"; + protected static final String IDENTIFY_FORMAT_PATTERN_PATTERN = "%(?:(unit%)|(?:(?:\\d+\\$)?(?:[-#+ 0,(<]*)?(?:\\d+)?(?:\\.\\d+)?(?:[tT])?(?:[a-zA-Z])))"; + private static final Pattern FORMAT_PATTERN = Pattern.compile("(?:^|[^%])" + IDENTIFY_FORMAT_PATTERN_PATTERN); - private static final Pattern LABEL_PATTERN = Pattern.compile(".*?\\[.*? (.*?)]"); private static final int MAX_BUTTONS = 4; private static final String DEFAULT_SORTING = "NONE"; @@ -331,10 +332,14 @@ private Switch createPlayerButtons() { String labelMappedOption = null; State state = null; StateDescription stateDescription = null; - String formatPattern = getFormatPattern(label); + String formatPattern = getFormatPattern(w); + + if (formatPattern != null && label.indexOf("[") < 0) { + label = label + " [" + formatPattern + "]"; + } // now insert the value, if the state is a string or decimal value and there is some formatting pattern defined - // in the label (i.e. it contains at least a %) + // in the label or state description (i.e. it contains at least a %) try { final Item item = getItem(itemName); @@ -348,13 +353,8 @@ private Switch createPlayerButtons() { // returned StateDescription. What is expected is the display of a value using the pattern // provided by the channel state description provider. stateDescription = item.getStateDescription(); - if (formatPattern == null && stateDescription != null && stateDescription.getPattern() != null) { - label = label + " [" + stateDescription.getPattern() + "]"; - } - String updatedPattern = getFormatPattern(label); - if (updatedPattern != null) { - formatPattern = updatedPattern; + if (formatPattern != null) { state = item.getState(); if (formatPattern.contains("%d")) { @@ -371,7 +371,7 @@ private Switch createPlayerButtons() { } } } catch (ItemNotFoundException e) { - logger.error("Cannot retrieve item '{}' for widget {}", itemName, w.eClass().getInstanceTypeName()); + logger.warn("Cannot retrieve item '{}' for widget {}", itemName, w.eClass().getInstanceTypeName()); } boolean considerTransform = false; @@ -455,7 +455,10 @@ private Switch createPlayerButtons() { } label = label.trim(); - label = label.substring(0, label.indexOf("[") + 1) + formatPattern + "]"; + int index = label.indexOf("["); + if (index >= 0) { + label = label.substring(0, index + 1) + formatPattern + "]"; + } } } @@ -463,7 +466,7 @@ private Switch createPlayerButtons() { } private QuantityType convertStateToWidgetUnit(QuantityType quantityState, Widget w) { - Unit widgetUnit = UnitUtils.parseUnit(getFormatPattern(w.getLabel())); + Unit widgetUnit = UnitUtils.parseUnit(getFormatPattern(w)); if (widgetUnit != null && !widgetUnit.equals(quantityState.getUnit())) { return Objects.requireNonNullElse(quantityState.toInvertibleUnit(widgetUnit), quantityState); } @@ -471,6 +474,56 @@ private QuantityType convertStateToWidgetUnit(QuantityType quantityState, return quantityState; } + @Override + public @Nullable String getFormatPattern(Widget w) { + String label = getLabelFromWidget(w); + String pattern = getFormatPattern(label); + String itemName = w.getItem(); + try { + Item item = null; + if (itemName != null && !itemName.isBlank()) { + item = getItem(itemName); + } + if (item != null && pattern == null) { + StateDescription stateDescription = item.getStateDescription(); + if (stateDescription != null) { + pattern = stateDescription.getPattern(); + } + } + + if (pattern == null) { + return null; + } + + // remove last part of pattern, after unit, if it exists, as this is not valid and creates problems with + // updates + if (item instanceof NumberItem numberItem && numberItem.getDimension() != null) { + Matcher m = FORMAT_PATTERN.matcher(pattern); + int matcherEnd = 0; + if (m.find() && m.group(1) == null) { + matcherEnd = m.end(); + } + String unit = pattern.substring(matcherEnd).trim(); + String postfix = ""; + int unitEnd = unit.indexOf(" "); + if (unitEnd > -1) { + postfix = unit.substring(unitEnd + 1).trim(); + unit = unit.substring(0, unitEnd); + } + if (!postfix.isBlank()) { + logger.warn( + "Item '{}' with unit, nothing allowed after unit in label pattern '{}', dropping postfix", + itemName, pattern); + } + pattern = pattern.substring(0, matcherEnd) + (!unit.isBlank() ? " " + unit : ""); + } + } catch (ItemNotFoundException e) { + logger.warn("Cannot retrieve item '{}' for widget {}", itemName, w.eClass().getInstanceTypeName()); + } + + return pattern; + } + private @Nullable String getFormatPattern(@Nullable String label) { if (label == null) { return null; @@ -608,7 +661,7 @@ private String transform(String label, boolean matchTransform, @Nullable String Item item = getItem(itemName); return convertState(w, item, item.getState()); } catch (ItemNotFoundException e) { - logger.error("Cannot retrieve item '{}' for widget {}", itemName, w.eClass().getInstanceTypeName()); + logger.warn("Cannot retrieve item '{}' for widget {}", itemName, w.eClass().getInstanceTypeName()); } } return UnDefType.UNDEF; @@ -627,8 +680,8 @@ private String transform(String label, boolean matchTransform, @Nullable String State returnState = null; State itemState = i.getState(); - if (itemState instanceof QuantityType type) { - itemState = convertStateToWidgetUnit(type, w); + if (itemState instanceof QuantityType quantityTypeState) { + itemState = convertStateToWidgetUnit(quantityTypeState, w); } if (w instanceof Switch && i instanceof RollershutterItem) { @@ -637,7 +690,7 @@ private String transform(String label, boolean matchTransform, @Nullable String } else if (w instanceof Slider) { if (i.getAcceptedDataTypes().contains(PercentType.class)) { returnState = itemState.as(PercentType.class); - } else { + } else if (!(itemState instanceof QuantityType)) { returnState = itemState.as(DecimalType.class); } } else if (w instanceof Switch sw) { @@ -1208,7 +1261,7 @@ public boolean getVisiblity(Widget w) { try { item = itemRegistry.getItem(itemName); } catch (ItemNotFoundException e) { - logger.error("Cannot retrieve visibility item {} for widget {}", rule.getItem(), + logger.warn("Cannot retrieve visibility item {} for widget {}", rule.getItem(), w.eClass().getInstanceTypeName()); // Default to visible! @@ -1325,12 +1378,13 @@ public void removeRegistryHook(RegistryHook hook) { // we require the item to define a dimension, otherwise no unit will be reported to the UIs. if (item instanceof NumberItem numberItem && numberItem.getDimension() != null) { - if (w.getLabel() == null) { + String pattern = getFormatPattern(w); + if (pattern == null || pattern.isBlank()) { // if no Label was assigned to the Widget we fallback to the items unit return numberItem.getUnitSymbol(); } - String unit = getUnitFromLabel(w.getLabel()); + String unit = getUnitFromPattern(pattern); if (!UnitUtils.UNIT_PLACEHOLDER.equals(unit)) { return unit; } @@ -1338,7 +1392,7 @@ public void removeRegistryHook(RegistryHook hook) { return numberItem.getUnitSymbol(); } } catch (ItemNotFoundException e) { - logger.debug("Failed to retrieve item during widget rendering: {}", e.getMessage()); + logger.warn("Failed to retrieve item during widget rendering, item does not exist: {}", e.getMessage()); } return ""; @@ -1354,14 +1408,13 @@ public void removeRegistryHook(RegistryHook hook) { return state; } - private @Nullable String getUnitFromLabel(@Nullable String label) { - if (label == null || label.isBlank()) { + private @Nullable String getUnitFromPattern(@Nullable String format) { + if (format == null || format.isBlank()) { return null; } - Matcher m = LABEL_PATTERN.matcher(label); - if (m.matches()) { - return m.group(1); - } - return null; + int index = format.lastIndexOf(" "); + String unit = index > 0 ? format.substring(index + 1) : null; + unit = UnitUtils.UNIT_PERCENT_FORMAT_STRING.equals(unit) ? "%" : unit; + return unit; } } diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/items/ItemUIRegistry.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/items/ItemUIRegistry.java index 998a11d3c4c..a979543d4cf 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/items/ItemUIRegistry.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/items/ItemUIRegistry.java @@ -34,6 +34,7 @@ * @author Kai Kreuzer - Initial contribution * @author Chris Jackson - Initial contribution * @author Laurent Garnier - new method getIconColor + * @author Mark Herwege - new method getFormatPattern */ @NonNullByDefault public interface ItemUIRegistry extends ItemRegistry, ItemUIProvider { @@ -130,6 +131,15 @@ public interface ItemUIRegistry extends ItemRegistry, ItemUIProvider { @Nullable EObject getParent(Widget w); + /** + * Gets the format pattern for the widget value, retrieved from widget label, item label or item state description + * + * @param w Widget + * @return String with the format pattern + */ + @Nullable + String getFormatPattern(Widget w); + /** * Gets the label color for the widget. Checks conditional statements to * find the color based on the item value From 3ec1457583d754acac5d80c3e011a3298154471a Mon Sep 17 00:00:00 2001 From: Gwendal Roulleau Date: Sun, 2 Jul 2023 11:19:34 +0200 Subject: [PATCH 118/126] [audio] Improve audio duration computation (#3675) Allows the use of a Sizeable interface (for AudioStream that we know the length of, but we cannot clone). We can then improve the duration detection, for example for the pulseaudio sink (PR coming after). We can also give the length information to sink in more cases. Add the support of the mark / reset methods to some common AudioStream. We then allow more stream analysis for sink requiring it (Stream analysis often requires to get back in time after consuming a few bytes) Signed-off-by: Gwendal Roulleau --- .../org/openhab/core/audio/AudioFormat.java | 4 +-- .../openhab/core/audio/AudioHTTPServer.java | 2 +- .../core/audio/ByteArrayAudioStream.java | 18 ++++++++++- .../core/audio/ClonableAudioStream.java | 8 ++--- .../openhab/core/audio/FileAudioStream.java | 22 +++++++++++-- .../core/audio/FixedLengthAudioStream.java | 15 ++++----- .../core/audio/SizeableAudioStream.java | 31 +++++++++++++++++++ .../openhab/core/audio/URLAudioStream.java | 2 +- .../core/audio/internal/AudioServlet.java | 10 +++--- .../core/audio/utils/AudioSinkUtilsImpl.java | 13 ++++++-- .../core/audio/internal/AudioServletTest.java | 5 ++- .../audio/internal/fake/AudioSinkFake.java | 6 ++-- .../cache/lru/InputStreamCacheWrapper.java | 16 ++++++++++ 13 files changed, 118 insertions(+), 34 deletions(-) create mode 100644 bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/SizeableAudioStream.java diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioFormat.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioFormat.java index 2a6942b6b97..0eeb7ab2e2b 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioFormat.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioFormat.java @@ -484,7 +484,7 @@ public String toString() { + (bigEndian != null ? "bigEndian=" + bigEndian + ", " : "") + (bitDepth != null ? "bitDepth=" + bitDepth + ", " : "") + (bitRate != null ? "bitRate=" + bitRate + ", " : "") - + (frequency != null ? "frequency=" + frequency : "") + (channels != null ? "channels=" + channels : "") - + "]"; + + (frequency != null ? "frequency=" + frequency + ", " : "") + + (channels != null ? "channels=" + channels : "") + "]"; } } diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioHTTPServer.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioHTTPServer.java index 2cb85ee5833..d74b88ef989 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioHTTPServer.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioHTTPServer.java @@ -31,7 +31,7 @@ public interface AudioHTTPServer { /** * Creates a relative url for a given {@link AudioStream} where it can be requested a single time. * Note that the HTTP header only contains "Content-length", if the passed stream is an instance of - * {@link FixedLengthAudioStream}. + * {@link SizeableAudioStream}. * If the client that requests the url expects this header field to be present, make sure to pass such an instance. * Streams are closed after having been served. * diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/ByteArrayAudioStream.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/ByteArrayAudioStream.java index 6dfcae37ff2..6386e39db53 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/ByteArrayAudioStream.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/ByteArrayAudioStream.java @@ -19,7 +19,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** - * This is an implementation of a {@link FixedLengthAudioStream}, which is based on a simple byte array. + * This is an implementation of an {@link AudioStream} with known length and a clone method, which is based on a simple + * byte array. * * @author Kai Kreuzer - Initial contribution */ @@ -60,4 +61,19 @@ public long length() { public InputStream getClonedStream() { return new ByteArrayAudioStream(bytes, format); } + + @Override + public synchronized void mark(int readlimit) { + stream.mark(readlimit); + } + + @Override + public synchronized void reset() throws IOException { + stream.reset(); + } + + @Override + public boolean markSupported() { + return true; + } } diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/ClonableAudioStream.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/ClonableAudioStream.java index 111e1c1d2d9..57dfcdc3c80 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/ClonableAudioStream.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/ClonableAudioStream.java @@ -17,12 +17,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** - * This is an {@link AudioStream}, that can be cloned + * This is for an {@link AudioStream}, that can be cloned * - * @author Gwendal Roulleau - Initial contribution, separation from FixedLengthAudioStream + * @author Gwendal Roulleau - Initial contribution, separation from {@link FixedLengthAudioStream} */ @NonNullByDefault -public abstract class ClonableAudioStream extends AudioStream { +public interface ClonableAudioStream { /** * Returns a new, fully independent stream instance, which can be read and closed without impacting the original @@ -31,5 +31,5 @@ public abstract class ClonableAudioStream extends AudioStream { * @return a new input stream that can be consumed by the caller * @throws AudioException if stream cannot be created */ - public abstract InputStream getClonedStream() throws AudioException; + public InputStream getClonedStream() throws AudioException; } diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/FileAudioStream.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/FileAudioStream.java index 7247742aaa6..ab35257b6f5 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/FileAudioStream.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/FileAudioStream.java @@ -42,9 +42,11 @@ public class FileAudioStream extends FixedLengthAudioStream implements Disposabl private final File file; private final AudioFormat audioFormat; - private InputStream inputStream; + private FileInputStream inputStream; private final long length; private final boolean isTemporaryFile; + private int markedOffset = 0; + private int alreadyRead = 0; public FileAudioStream(File file) throws AudioException { this(file, getAudioFormat(file)); @@ -87,7 +89,7 @@ private static AudioFormat parseWavFormat(File file) throws AudioException { } } - private static InputStream getInputStream(File file) throws AudioException { + private static FileInputStream getInputStream(File file) throws AudioException { try { return new FileInputStream(file); } catch (FileNotFoundException e) { @@ -102,7 +104,9 @@ public AudioFormat getFormat() { @Override public int read() throws IOException { - return inputStream.read(); + int read = inputStream.read(); + alreadyRead++; + return read; } @Override @@ -124,11 +128,23 @@ public synchronized void reset() throws IOException { } try { inputStream = getInputStream(file); + inputStream.skipNBytes(markedOffset); + alreadyRead = markedOffset; } catch (AudioException e) { throw new IOException("Cannot reset file input stream: " + e.getMessage(), e); } } + @Override + public synchronized void mark(int readlimit) { + markedOffset = alreadyRead; + } + + @Override + public boolean markSupported() { + return true; + } + @Override public InputStream getClonedStream() throws AudioException { return getInputStream(file); diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/FixedLengthAudioStream.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/FixedLengthAudioStream.java index 4a737d50f99..daaf6657a33 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/FixedLengthAudioStream.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/FixedLengthAudioStream.java @@ -15,18 +15,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** - * This is a {@link ClonableAudioStream}, which can also provide information about its absolute length. + * This is a {@link AudioStream}, which can also provide information about its absolute length and get cloned. * * @author Kai Kreuzer - Initial contribution - * @author Gwendal Roulleau - Separate getClonedStream into its own class + * @author Gwendal Roulleau - Separate getClonedStream and length into their own interface. + * @deprecated You should consider using {@link ClonableAudioStream} and/or {@link SizeableAudioStream} to detect audio + * stream capabilities */ @NonNullByDefault -public abstract class FixedLengthAudioStream extends ClonableAudioStream { +@Deprecated +public abstract class FixedLengthAudioStream extends AudioStream implements SizeableAudioStream, ClonableAudioStream { - /** - * Provides the length of the stream in bytes. - * - * @return absolute length in bytes - */ - public abstract long length(); } diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/SizeableAudioStream.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/SizeableAudioStream.java new file mode 100644 index 00000000000..6074f2424c2 --- /dev/null +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/SizeableAudioStream.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.audio; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * This is for an {@link AudioStream}, which size is known + * + * @author Gwendal Roulleau - Initial contribution, separation from {@link FixedLengthAudioStream} + */ +@NonNullByDefault +public interface SizeableAudioStream { + + /** + * Provides the length of the stream in bytes. + * + * @return absolute length in bytes + */ + public long length(); +} diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/URLAudioStream.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/URLAudioStream.java index e43ab639760..d365829097a 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/URLAudioStream.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/URLAudioStream.java @@ -40,7 +40,7 @@ * @author Christoph Weitkamp - Refactored use of filename extension */ @NonNullByDefault -public class URLAudioStream extends ClonableAudioStream { +public class URLAudioStream extends AudioStream implements ClonableAudioStream { private static final Pattern PLS_STREAM_PATTERN = Pattern.compile("^File[0-9]=(.+)$"); diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/AudioServlet.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/AudioServlet.java index e7acbb867ee..3a5ac9377ab 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/AudioServlet.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/internal/AudioServlet.java @@ -48,7 +48,7 @@ import org.openhab.core.audio.ByteArrayAudioStream; import org.openhab.core.audio.ClonableAudioStream; import org.openhab.core.audio.FileAudioStream; -import org.openhab.core.audio.FixedLengthAudioStream; +import org.openhab.core.audio.SizeableAudioStream; import org.openhab.core.audio.StreamServed; import org.openhab.core.audio.utils.AudioSinkUtils; import org.openhab.core.common.ThreadPoolManager; @@ -135,8 +135,8 @@ private InputStream prepareInputStream(final StreamServed streamServed, final Ht } // try to set the content-length, if possible - if (streamServed.audioStream() instanceof FixedLengthAudioStream fixedLengthServedStream) { - final long size = fixedLengthServedStream.length(); + if (streamServed.audioStream() instanceof SizeableAudioStream sizeableServedStream) { + final long size = sizeableServedStream.length(); resp.setContentLength((int) size); } @@ -285,9 +285,9 @@ public StreamServed serve(AudioStream originalStream, int seconds, boolean multi return streamToServe; } - private ClonableAudioStream createClonableInputStream(AudioStream stream, String streamId) throws IOException { + private AudioStream createClonableInputStream(AudioStream stream, String streamId) throws IOException { byte[] dataBytes = stream.readNBytes(ONETIME_STREAM_BUFFER_MAX_SIZE + 1); - ClonableAudioStream clonableAudioStreamResult; + AudioStream clonableAudioStreamResult; if (dataBytes.length <= ONETIME_STREAM_BUFFER_MAX_SIZE) { // we will use an in memory buffer to avoid disk operation clonableAudioStreamResult = new ByteArrayAudioStream(dataBytes, stream.getFormat()); diff --git a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/utils/AudioSinkUtilsImpl.java b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/utils/AudioSinkUtilsImpl.java index f49da0ce8e0..069096baee8 100644 --- a/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/utils/AudioSinkUtilsImpl.java +++ b/bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/utils/AudioSinkUtilsImpl.java @@ -66,11 +66,20 @@ public class AudioSinkUtilsImpl implements AudioSinkUtils { .longValue(); return startTime + computedDuration; } catch (IOException | UnsupportedAudioFileException e) { - logger.debug("Cannot compute the duration of input stream", e); + logger.debug("Cannot compute the duration of input stream with method java stream sound analysis", + e); + Integer bitRate = audioFormat.getBitRate(); + if (bitRate != null && bitRate != 0) { + long computedDuration = Float.valueOf((1f * dataTransferedLength / bitRate) * 1000000000) + .longValue(); + return startTime + computedDuration; + } else { + logger.debug("Cannot compute the duration of input stream by using audio format information"); + } return null; } } else if (AudioFormat.CODEC_MP3.equals(audioFormat.getCodec())) { - // not precise, no VBR, but better than nothing + // not accurate, no VBR support, but better than nothing Bitstream bitstream = new Bitstream(new ByteArrayInputStream(dataBytes)); try { Header h = bitstream.readFrame(); diff --git a/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/AudioServletTest.java b/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/AudioServletTest.java index 6f7200604a6..da5eafc1d0e 100644 --- a/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/AudioServletTest.java +++ b/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/AudioServletTest.java @@ -29,7 +29,6 @@ import org.openhab.core.audio.AudioStream; import org.openhab.core.audio.ByteArrayAudioStream; import org.openhab.core.audio.FileAudioStream; -import org.openhab.core.audio.FixedLengthAudioStream; import org.openhab.core.audio.StreamServed; import org.openhab.core.audio.internal.utils.BundledSoundFileHandler; @@ -214,7 +213,7 @@ public void oneTimeStreamIsClosedAndRemovedAfterServed() throws Exception { @Test public void multiTimeStreamIsClosedAfterExpired() throws Exception { AtomicInteger cloneCounter = new AtomicInteger(); - FixedLengthAudioStream audioStream = mock(FixedLengthAudioStream.class); + ByteArrayAudioStream audioStream = mock(ByteArrayAudioStream.class); AudioStream clonedStream = mock(AudioStream.class); AudioFormat audioFormat = mock(AudioFormat.class); when(audioStream.getFormat()).thenReturn(audioFormat); @@ -250,7 +249,7 @@ public void multiTimeStreamIsClosedAfterExpired() throws Exception { @Test public void streamsAreClosedOnDeactivate() throws Exception { AudioStream oneTimeStream = mock(AudioStream.class); - FixedLengthAudioStream multiTimeStream = mock(FixedLengthAudioStream.class); + ByteArrayAudioStream multiTimeStream = mock(ByteArrayAudioStream.class); serveStream(oneTimeStream); serveStream(multiTimeStream, 10); diff --git a/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/fake/AudioSinkFake.java b/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/fake/AudioSinkFake.java index 89d189c12b8..6bf2a61d40b 100644 --- a/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/fake/AudioSinkFake.java +++ b/bundles/org.openhab.core.audio/src/test/java/org/openhab/core/audio/internal/fake/AudioSinkFake.java @@ -21,7 +21,7 @@ import org.openhab.core.audio.AudioFormat; import org.openhab.core.audio.AudioSink; import org.openhab.core.audio.AudioStream; -import org.openhab.core.audio.FixedLengthAudioStream; +import org.openhab.core.audio.ByteArrayAudioStream; import org.openhab.core.audio.URLAudioStream; import org.openhab.core.audio.UnsupportedAudioFormatException; import org.openhab.core.audio.UnsupportedAudioStreamException; @@ -49,8 +49,8 @@ public class AudioSinkFake implements AudioSink { public boolean isUnsupportedAudioStreamExceptionExpected; private static final Set SUPPORTED_AUDIO_FORMATS = Set.of(AudioFormat.MP3, AudioFormat.WAV); - private static final Set> SUPPORTED_AUDIO_STREAMS = Set - .of(FixedLengthAudioStream.class, URLAudioStream.class); + private static final Set> SUPPORTED_AUDIO_STREAMS = Set.of(ByteArrayAudioStream.class, + URLAudioStream.class); @Override public String getId() { diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/lru/InputStreamCacheWrapper.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/lru/InputStreamCacheWrapper.java index 1797e028db3..5cb13d60ffd 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/lru/InputStreamCacheWrapper.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/cache/lru/InputStreamCacheWrapper.java @@ -37,6 +37,7 @@ public class InputStreamCacheWrapper extends InputStream { private LRUMediaCacheEntry cacheEntry; private int offset = 0; + private int markedOffset = 0; /*** * Construct a transparent InputStream wrapper around data from the cache. @@ -113,4 +114,19 @@ public long length() { public InputStream getClonedStream() throws IOException { return cacheEntry.getInputStream(); } + + @Override + public synchronized void mark(int readlimit) { + markedOffset = offset; + } + + @Override + public synchronized void reset() throws IOException { + offset = markedOffset; + } + + @Override + public boolean markSupported() { + return true; + } } From 6b914162bf746238e8c9187b2fce037bcdba4716 Mon Sep 17 00:00:00 2001 From: GiviMAD Date: Sun, 2 Jul 2023 02:27:10 -0700 Subject: [PATCH 119/126] [voice] Add console commands for register/unregister dialogs and list them (#3459) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [voice] Add voice commands for register/unregister dialogs and list dialogs and dialog registrations Signed-off-by: Miguel Álvarez --- .../org/openhab/core/voice/VoiceManager.java | 5 + .../core/voice/internal/DialogProcessor.java | 7 + .../VoiceConsoleCommandExtension.java | 148 ++++++++++++++++-- .../core/voice/internal/VoiceManagerImpl.java | 5 + 4 files changed, 156 insertions(+), 9 deletions(-) diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/VoiceManager.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/VoiceManager.java index 86ba0b8c04e..d34aa774b13 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/VoiceManager.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/VoiceManager.java @@ -135,6 +135,11 @@ public interface VoiceManager { @Nullable DialogContext getLastDialogContext(); + /** + * Returns a list with the contexts of all running dialogs. + */ + List getDialogsContexts(); + /** * Starts an infinite dialog sequence: keyword spotting on the audio source, audio source listening to retrieve * a question or a command (Speech to Text service), interpretation and handling of the command, and finally diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/DialogProcessor.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/DialogProcessor.java index 2760edbd98e..f23b962140f 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/DialogProcessor.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/DialogProcessor.java @@ -223,6 +223,13 @@ public void stop() { eventListener.onDialogStopped(dialogContext); } + /** + * Returns the dialog context used to start this processor. + */ + public DialogContext getContext() { + return dialogContext; + } + /** * Indicates if voice recognition is running. */ diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceConsoleCommandExtension.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceConsoleCommandExtension.java index c29b0091b57..c4eb1ff0941 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceConsoleCommandExtension.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceConsoleCommandExtension.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -33,6 +34,7 @@ import org.openhab.core.items.ItemNotUniqueException; import org.openhab.core.items.ItemRegistry; import org.openhab.core.voice.DialogContext; +import org.openhab.core.voice.DialogRegistration; import org.openhab.core.voice.KSService; import org.openhab.core.voice.STTService; import org.openhab.core.voice.TTSService; @@ -60,7 +62,11 @@ public class VoiceConsoleCommandExtension extends AbstractConsoleCommandExtensio private static final String SUBCMD_VOICES = "voices"; private static final String SUBCMD_START_DIALOG = "startdialog"; private static final String SUBCMD_STOP_DIALOG = "stopdialog"; + private static final String SUBCMD_REGISTER_DIALOG = "registerdialog"; + private static final String SUBCMD_UNREGISTER_DIALOG = "unregisterdialog"; private static final String SUBCMD_LISTEN_ANSWER = "listenandanswer"; + private static final String SUBCMD_DIALOGS = "dialogs"; + private static final String SUBCMD_DIALOG_REGS = "dialogregs"; private static final String SUBCMD_INTERPRETERS = "interpreters"; private static final String SUBCMD_KEYWORD_SPOTTERS = "keywordspotters"; private static final String SUBCMD_STT_SERVICES = "sttservices"; @@ -87,13 +93,21 @@ public List getUsages() { return List.of(buildCommandUsage(SUBCMD_SAY + " ", "speaks a text"), buildCommandUsage(SUBCMD_INTERPRET + " ", "interprets a human language command"), buildCommandUsage(SUBCMD_VOICES, "lists available voices of the TTS services"), + buildCommandUsage(SUBCMD_DIALOGS, "lists the running dialog and their audio/voice services"), + buildCommandUsage(SUBCMD_DIALOG_REGS, + "lists the existing dialog registrations and their selected audio/voice services"), + buildCommandUsage(SUBCMD_REGISTER_DIALOG + + " [--source ] [--sink ] [--hlis ] [--tts [--voice ]] [--stt ] [--ks ks [--keyword ]] [--listening-item ]", + "register a new dialog processing using the default services or the services identified with provided arguments, it will be persisted and keep running whenever is possible."), + buildCommandUsage(SUBCMD_UNREGISTER_DIALOG + " [source]", + "unregister the dialog processing for the default audio source or the audio source identified with provided argument, stopping it if started"), buildCommandUsage(SUBCMD_START_DIALOG - + " [--source ] [--sink ] [--interpreters ] [--tts [--voice ]] [--stt ] [--ks ks [--keyword ]] [--listening-item ]", + + " [--source ] [--sink ] [--hlis ] [--tts [--voice ]] [--stt ] [--ks ks [--keyword ]] [--listening-item ]", "start a new dialog processing using the default services or the services identified with provided arguments"), buildCommandUsage(SUBCMD_STOP_DIALOG + " []", "stop the dialog processing for the default audio source or the audio source identified with provided argument"), buildCommandUsage(SUBCMD_LISTEN_ANSWER - + " [--source ] [--sink ] [--interpreters ] [--tts [--voice ]] [--stt ] [--listening-item ]", + + " [--source ] [--sink ] [--hlis ] [--tts [--voice ]] [--stt ] [--listening-item ]", "Execute a simple dialog sequence without keyword spotting using the default services or the services identified with provided arguments"), buildCommandUsage(SUBCMD_INTERPRETERS, "lists the interpreters"), buildCommandUsage(SUBCMD_KEYWORD_SPOTTERS, "lists the keyword spotters"), @@ -135,10 +149,41 @@ public void execute(String[] args, Console console) { } return; } + case SUBCMD_REGISTER_DIALOG -> { + DialogRegistration dialogRegistration; + try { + dialogRegistration = parseDialogRegistration(args); + } catch (IllegalStateException e) { + console.println(Objects.requireNonNullElse(e.getMessage(), + "An error occurred while parsing the dialog options")); + break; + } + try { + voiceManager.registerDialog(dialogRegistration); + } catch (IllegalStateException e) { + console.println(Objects.requireNonNullElse(e.getMessage(), + "An error occurred while registering the dialog")); + } + return; + } + case SUBCMD_UNREGISTER_DIALOG -> { + try { + var sourceId = args.length < 2 ? audioManager.getSourceId() : args[1]; + if (sourceId == null) { + console.println("No source provided nor default source available"); + break; + } + voiceManager.unregisterDialog(sourceId); + } catch (IllegalStateException e) { + console.println(Objects.requireNonNullElse(e.getMessage(), + "An error occurred while stopping the dialog")); + } + return; + } case SUBCMD_START_DIALOG -> { DialogContext.Builder dialogContextBuilder; try { - dialogContextBuilder = parseDialogParameters(args); + dialogContextBuilder = parseDialogContext(args); } catch (IllegalStateException e) { console.println(Objects.requireNonNullElse(e.getMessage(), "An error occurred while parsing the dialog options")); @@ -164,7 +209,7 @@ public void execute(String[] args, Console console) { case SUBCMD_LISTEN_ANSWER -> { DialogContext.Builder dialogContextBuilder; try { - dialogContextBuilder = parseDialogParameters(args); + dialogContextBuilder = parseDialogContext(args); } catch (IllegalStateException e) { console.println(Objects.requireNonNullElse(e.getMessage(), "An error occurred while parsing the dialog options")); @@ -178,6 +223,14 @@ public void execute(String[] args, Console console) { } return; } + case SUBCMD_DIALOGS -> { + listDialogs(console); + return; + } + case SUBCMD_DIALOG_REGS -> { + listDialogRegistrations(console); + return; + } case SUBCMD_INTERPRETERS -> { listInterpreters(console); return; @@ -252,6 +305,42 @@ private void say(String[] args, Console console) { voiceManager.say(msg.toString()); } + private void listDialogRegistrations(Console console) { + Collection registrations = voiceManager.getDialogRegistrations(); + if (!registrations.isEmpty()) { + registrations.stream().sorted(comparing(dr -> dr.sourceId)).forEach(dr -> { + console.println( + String.format(" Source: %s - Sink: %s (STT: %s, TTS: %s, HLIs: %s, KS: %s, Keyword: %s)", + dr.sourceId, dr.sinkId, getOrDefault(dr.sttId), getOrDefault(dr.ttsId), + dr.hliIds.isEmpty() ? getOrDefault(null) : String.join("->", dr.hliIds), + getOrDefault(dr.ksId), getOrDefault(dr.keyword))); + }); + } else { + console.println("No dialog registrations."); + } + } + + private String getOrDefault(@Nullable String value) { + return value != null && !value.isBlank() ? value : "**Default**"; + } + + private void listDialogs(Console console) { + Collection dialogContexts = voiceManager.getDialogsContexts(); + if (!dialogContexts.isEmpty()) { + dialogContexts.stream().sorted(comparing(s -> s.source().getId())).forEach(c -> { + var ks = c.ks(); + String ksText = ks != null ? String.format(", KS: %s, Keyword: %s", ks.getId(), c.keyword()) : ""; + console.println( + String.format(" Source: %s - Sink: %s (STT: %s, TTS: %s, HLIs: %s%s)", c.source().getId(), + c.sink().getId(), c.stt().getId(), c.tts().getId(), c.hlis().stream() + .map(HumanLanguageInterpreter::getId).collect(Collectors.joining("->")), + ksText)); + }); + } else { + console.println("No running dialogs."); + } + } + private void listInterpreters(Console console) { Collection interpreters = voiceManager.getHLIs(); if (!interpreters.isEmpty()) { @@ -314,11 +403,7 @@ private void listTTSs(Console console) { .orElse(null); } - private DialogContext.Builder parseDialogParameters(String[] args) { - var dialogContextBuilder = voiceManager.getDialogContextBuilder(); - if (args.length < 2) { - return dialogContextBuilder; - } + private HashMap parseDialogParameters(String[] args) { var parameters = new HashMap(); for (int i = 1; i < args.length; i++) { var arg = args[i].trim(); @@ -333,6 +418,15 @@ private DialogContext.Builder parseDialogParameters(String[] args) { throw new IllegalStateException("Argument name should start by -- " + arg); } } + return parameters; + } + + private DialogContext.Builder parseDialogContext(String[] args) { + var dialogContextBuilder = voiceManager.getDialogContextBuilder(); + if (args.length < 2) { + return dialogContextBuilder; + } + var parameters = parseDialogParameters(args); String sourceId = parameters.remove("source"); if (sourceId != null) { var source = audioManager.getSource(sourceId); @@ -363,4 +457,40 @@ private DialogContext.Builder parseDialogParameters(String[] args) { } return dialogContextBuilder; } + + private DialogRegistration parseDialogRegistration(String[] args) { + var parameters = parseDialogParameters(args); + @Nullable + String sourceId = parameters.remove("source"); + if (sourceId == null) { + sourceId = audioManager.getSourceId(); + } + if (sourceId == null) { + throw new IllegalStateException("A source is required if the default is not configured"); + } + @Nullable + String sinkId = parameters.remove("sink"); + if (sinkId == null) { + sinkId = audioManager.getSinkId(); + } + if (sinkId == null) { + throw new IllegalStateException("A sink is required if the default is not configured"); + } + var dr = new DialogRegistration(sourceId, sinkId); + dr.ksId = parameters.remove("ks"); + dr.keyword = parameters.remove("keyword"); + dr.sttId = parameters.remove("stt"); + dr.ttsId = parameters.remove("tts"); + dr.voiceId = parameters.remove("voice"); + dr.listeningItem = parameters.remove("listening-item"); + String hliIds = parameters.remove("hlis"); + if (hliIds != null) { + dr.hliIds = Arrays.stream(hliIds.split(",")).map(String::trim).collect(Collectors.toList()); + } + if (!parameters.isEmpty()) { + throw new IllegalStateException( + "Argument " + parameters.keySet().stream().findAny().orElse("") + " is not supported"); + } + return dr; + } } diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceManagerImpl.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceManagerImpl.java index de770481a69..c70a2d84c22 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceManagerImpl.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceManagerImpl.java @@ -495,6 +495,11 @@ public DialogContext.Builder getDialogContextBuilder() { .withListeningItem(listeningItem); } + @Override + public List getDialogsContexts() { + return dialogProcessors.values().stream().map(DialogProcessor::getContext).collect(Collectors.toList()); + } + @Override public @Nullable DialogContext getLastDialogContext() { return lastDialogContext; From a656073352ee49bebbc358c2e2603de811ff0b21 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 2 Jul 2023 12:12:53 +0200 Subject: [PATCH 120/126] Improve performance for state update handling (#3635) * Improve threading in EventHandler * refactor pagechangelistener to event * One executor per subscriber type, not per subscriber Signed-off-by: Jan N. Klug --- .../sitemap/SitemapSubscriptionService.java | 65 +++++++------- .../sitemap/internal/PageChangeListener.java | 82 +++++++----------- .../sitemap/internal/SitemapResource.java | 84 ++++++++++--------- .../sitemap/internal/SitemapResourceTest.java | 19 +++-- .../core/internal/events/EventHandler.java | 38 +++++++-- 5 files changed, 152 insertions(+), 136 deletions(-) diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/SitemapSubscriptionService.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/SitemapSubscriptionService.java index 70bafa9e4bf..d788c1762ea 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/SitemapSubscriptionService.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/SitemapSubscriptionService.java @@ -42,6 +42,8 @@ import org.openhab.core.model.sitemap.sitemap.Widget; import org.openhab.core.thing.events.ChannelDescriptionChangedEvent; import org.openhab.core.ui.items.ItemUIRegistry; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; @@ -72,6 +74,7 @@ public class SitemapSubscriptionService implements ModelRepositoryChangeListener private static final int DEFAULT_MAX_SUBSCRIPTIONS = 50; private final Logger logger = LoggerFactory.getLogger(SitemapSubscriptionService.class); + private final BundleContext bundleContext; public interface SitemapSubscriptionCallback { @@ -94,15 +97,17 @@ public interface SitemapSubscriptionCallback { private final Map creationInstants = new ConcurrentHashMap<>(); /* sitemap+page -> listener */ - private final Map pageChangeListeners = new ConcurrentHashMap<>(); + private final Map pageChangeListeners = new ConcurrentHashMap<>(); /* Max number of subscriptions at the same time */ private int maxSubscriptions = DEFAULT_MAX_SUBSCRIPTIONS; @Activate - public SitemapSubscriptionService(Map config, final @Reference ItemUIRegistry itemUIRegistry) { - applyConfig(config); + public SitemapSubscriptionService(Map config, final @Reference ItemUIRegistry itemUIRegistry, + BundleContext bundleContext) { this.itemUIRegistry = itemUIRegistry; + this.bundleContext = bundleContext; + applyConfig(config); } @Deactivate @@ -110,9 +115,7 @@ protected void deactivate() { pageOfSubscription.clear(); callbacks.clear(); creationInstants.clear(); - for (PageChangeListener listener : pageChangeListeners.values()) { - listener.dispose(); - } + pageChangeListeners.values().forEach(l -> l.serviceRegistration.unregister()); pageChangeListeners.clear(); } @@ -150,7 +153,7 @@ protected void removeSitemapProvider(SitemapProvider provider) { * Creates a new subscription with the given id. * * @param callback an instance that should receive the events - * @returns a unique id that identifies the subscription or null if the limit of subscriptions is already reached + * @return a unique id that identifies the subscription or null if the limit of subscriptions is already reached */ public @Nullable String createSubscription(SitemapSubscriptionCallback callback) { if (maxSubscriptions >= 0 && callbacks.size() >= maxSubscriptions) { @@ -176,9 +179,9 @@ public void removeSubscription(String subscriptionId) { String sitemapPage = pageOfSubscription.remove(subscriptionId); if (sitemapPage != null && !pageOfSubscription.values().contains(sitemapPage)) { // this was the only subscription listening on this page, so we can dispose the listener - PageChangeListener listener = pageChangeListeners.remove(sitemapPage); + ListenerRecord listener = pageChangeListeners.remove(sitemapPage); if (listener != null) { - listener.dispose(); + listener.serviceRegistration().unregister(); } } logger.debug("Removed subscription with id {} ({} active subscriptions)", subscriptionId, callbacks.size()); @@ -249,13 +252,14 @@ public void setPageId(String subscriptionId, String sitemapName, String pageId) } private void addCallbackToListener(String sitemapName, String pageId, SitemapSubscriptionCallback callback) { - PageChangeListener listener = pageChangeListeners.get(getValue(sitemapName, pageId)); - if (listener == null) { - // there is no listener for this page yet, so let's try to create one - listener = new PageChangeListener(sitemapName, pageId, itemUIRegistry, collectWidgets(sitemapName, pageId)); - pageChangeListeners.put(getValue(sitemapName, pageId), listener); - } - listener.addCallback(callback); + ListenerRecord listener = pageChangeListeners.computeIfAbsent(getValue(sitemapName, pageId), v -> { + PageChangeListener newListener = new PageChangeListener(sitemapName, pageId, itemUIRegistry, + collectWidgets(sitemapName, pageId)); + ServiceRegistration registration = bundleContext.registerService(EventSubscriber.class.getName(), + newListener, null); + return new ListenerRecord(newListener, registration); + }); + listener.pageChangeListener().addCallback(callback); } private EList collectWidgets(String sitemapName, String pageId) { @@ -278,12 +282,12 @@ private EList collectWidgets(String sitemapName, String pageId) { } private void removeCallbackFromListener(String sitemapPage, SitemapSubscriptionCallback callback) { - PageChangeListener oldListener = pageChangeListeners.get(sitemapPage); + ListenerRecord oldListener = pageChangeListeners.get(sitemapPage); if (oldListener != null) { - oldListener.removeCallback(callback); - if (!pageOfSubscription.values().contains(sitemapPage)) { + oldListener.pageChangeListener().removeCallback(callback); + if (!pageOfSubscription.containsValue(sitemapPage)) { // no other callbacks are left here, so we can safely dispose the listener - oldListener.dispose(); + oldListener.serviceRegistration().unregister(); pageChangeListeners.remove(sitemapPage); } } @@ -311,14 +315,14 @@ public void modelChanged(String modelName, EventType type) { String changedSitemapName = modelName.substring(0, modelName.length() - SITEMAP_SUFFIX.length()); - for (Entry listenerEntry : pageChangeListeners.entrySet()) { + for (Entry listenerEntry : pageChangeListeners.entrySet()) { String sitemapWithPage = listenerEntry.getKey(); String sitemapName = extractSitemapName(sitemapWithPage); String pageId = extractPageId(sitemapWithPage); if (sitemapName.equals(changedSitemapName)) { EList widgets = collectWidgets(sitemapName, pageId); - listenerEntry.getValue().sitemapContentChanged(widgets); + listenerEntry.getValue().pageChangeListener().sitemapContentChanged(widgets); } } } @@ -336,9 +340,7 @@ public void checkAliveClients() { } } // Send an ALIVE event to all subscribers to trigger an exception for dead subscribers - for (Entry listenerEntry : pageChangeListeners.entrySet()) { - listenerEntry.getValue().sendAliveEvent(); - } + pageChangeListeners.values().forEach(l -> l.pageChangeListener().sendAliveEvent()); } @Override @@ -355,19 +357,22 @@ public void receive(Event event) { // members and predictions aren't really possible in that case (or at least would be highly complex). return; } - for (PageChangeListener pageChangeListener : pageChangeListeners.values()) { + for (ListenerRecord listener : pageChangeListeners.values()) { if (prediction.isConfirmation()) { - pageChangeListener.keepCurrentState(item); + listener.pageChangeListener().keepCurrentState(item); } else { - pageChangeListener.changeStateTo(item, prediction.getPredictedState()); + listener.pageChangeListener().changeStateTo(item, prediction.getPredictedState()); } } } else if (event instanceof ChannelDescriptionChangedEvent channelDescriptionChangedEvent) { channelDescriptionChangedEvent.getLinkedItemNames().forEach(itemName -> { - for (PageChangeListener pageChangeListener : pageChangeListeners.values()) { - pageChangeListener.descriptionChanged(itemName); + for (ListenerRecord listener : pageChangeListeners.values()) { + listener.pageChangeListener().descriptionChanged(itemName); } }); } } + + private record ListenerRecord(PageChangeListener pageChangeListener, ServiceRegistration serviceRegistration) { + } } diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/PageChangeListener.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/PageChangeListener.java index 3252af2f6bd..30aa6bc5e92 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/PageChangeListener.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/PageChangeListener.java @@ -20,16 +20,19 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; +import java.util.stream.Collectors; import org.eclipse.emf.common.util.EList; import org.openhab.core.common.ThreadPoolManager; +import org.openhab.core.events.Event; +import org.openhab.core.events.EventSubscriber; import org.openhab.core.io.rest.core.item.EnrichedItemDTOMapper; import org.openhab.core.io.rest.sitemap.SitemapSubscriptionService.SitemapSubscriptionCallback; -import org.openhab.core.items.GenericItem; -import org.openhab.core.items.GroupItem; import org.openhab.core.items.Item; import org.openhab.core.items.ItemNotFoundException; -import org.openhab.core.items.StateChangeListener; +import org.openhab.core.items.events.GroupStateUpdatedEvent; +import org.openhab.core.items.events.ItemEvent; +import org.openhab.core.items.events.ItemStateChangedEvent; import org.openhab.core.library.CoreItemFactory; import org.openhab.core.model.sitemap.sitemap.Chart; import org.openhab.core.model.sitemap.sitemap.ColorArray; @@ -45,7 +48,7 @@ * @author Kai Kreuzer - Initial contribution * @author Laurent Garnier - Added support for icon color */ -public class PageChangeListener implements StateChangeListener { +public class PageChangeListener implements EventSubscriber { private static final int REVERT_INTERVAL = 300; private final ScheduledExecutorService scheduler = ThreadPoolManager @@ -55,6 +58,7 @@ public class PageChangeListener implements StateChangeListener { private final ItemUIRegistry itemUIRegistry; private EList widgets; private Set items; + private final HashSet filterItems = new HashSet<>(); private final List callbacks = Collections.synchronizedList(new ArrayList<>()); private Set distinctCallbacks = Collections.emptySet(); @@ -75,23 +79,10 @@ public PageChangeListener(String sitemapName, String pageId, ItemUIRegistry item } private void updateItemsAndWidgets(EList widgets) { - if (this.widgets != null) { - // cleanup statechange listeners in case widgets were removed - items = getAllItems(this.widgets); - for (Item item : items) { - if (item instanceof GenericItem genericItem) { - genericItem.removeStateChangeListener(this); - } - } - } - this.widgets = widgets; items = getAllItems(widgets); - for (Item item : items) { - if (item instanceof GenericItem genericItem) { - genericItem.addStateChangeListener(this); - } - } + filterItems.clear(); + filterItems.addAll(items.stream().map(Item::getName).collect(Collectors.toSet())); } public String getSitemapName() { @@ -113,19 +104,6 @@ public void removeCallback(SitemapSubscriptionCallback callback) { distinctCallbacks = new HashSet<>(callbacks); } - /** - * Disposes this instance and releases all resources. - */ - public void dispose() { - for (Item item : items) { - if (item instanceof GenericItem genericItem) { - genericItem.removeStateChangeListener(this); - } else if (item instanceof GroupItem groupItem) { - groupItem.removeStateChangeListener(this); - } - } - } - /** * Collects all items that are represented by a given list of widgets * @@ -182,26 +160,6 @@ private void constructAndSendEvents(Item item, State newState) { } } - @Override - public void stateChanged(Item item, State oldState, State newState) { - // For all items except group, send an event only when the event state is changed. - if (item instanceof GroupItem) { - return; - } - constructAndSendEvents(item, newState); - } - - @Override - public void stateUpdated(Item item, State state) { - // For group item only, send an event each time the event state is updated. - // It allows updating the group label while the group state is unchanged, - // for example the count in label for Group:Switch:OR - if (!(item instanceof GroupItem)) { - return; - } - constructAndSendEvents(item, state); - } - public void keepCurrentState(Item item) { scheduler.schedule(() -> { constructAndSendEvents(item, item.getState()); @@ -338,4 +296,24 @@ private Set constructSitemapEventsForUpdatedDescr(Item item, List< } return events; } + + @Override + public Set getSubscribedEventTypes() { + return Set.of(ItemStateChangedEvent.TYPE, GroupStateUpdatedEvent.TYPE); + } + + @Override + public void receive(Event event) { + if (event instanceof ItemEvent itemEvent && filterItems.contains(itemEvent.getItemName())) { + Item item = itemUIRegistry.get(itemEvent.getItemName()); + if (item == null) { + return; + } + if (event instanceof GroupStateUpdatedEvent groupStateUpdatedEvent) { + constructAndSendEvents(item, groupStateUpdatedEvent.getItemState()); + } else if (event instanceof ItemStateChangedEvent itemStateChangedEvent) { + constructAndSendEvents(item, itemStateChangedEvent.getItemState()); + } + } + } } diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java index 84d3823ff44..2c92feed0f7 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java @@ -21,10 +21,12 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; +import java.util.stream.Collectors; import javax.annotation.security.RolesAllowed; import javax.servlet.http.HttpServletRequest; @@ -56,6 +58,8 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.auth.Role; import org.openhab.core.common.ThreadPoolManager; +import org.openhab.core.events.Event; +import org.openhab.core.events.EventSubscriber; import org.openhab.core.io.rest.JSONResponse; import org.openhab.core.io.rest.LocaleService; import org.openhab.core.io.rest.RESTConstants; @@ -67,7 +71,8 @@ import org.openhab.core.items.GenericItem; import org.openhab.core.items.Item; import org.openhab.core.items.ItemNotFoundException; -import org.openhab.core.items.StateChangeListener; +import org.openhab.core.items.events.ItemEvent; +import org.openhab.core.items.events.ItemStateChangedEvent; import org.openhab.core.library.CoreItemFactory; import org.openhab.core.library.types.HSBType; import org.openhab.core.model.sitemap.SitemapProvider; @@ -127,7 +132,7 @@ * @author Laurent Garnier - Added support for icon color * @author Mark Herwege - Added pattern and unit fields */ -@Component(service = RESTResource.class) +@Component(service = { RESTResource.class, EventSubscriber.class }) @JaxrsResource @JaxrsName(SitemapResource.PATH_SITEMAPS) @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") @@ -137,7 +142,7 @@ @Tag(name = SitemapResource.PATH_SITEMAPS) @NonNullByDefault public class SitemapResource - implements RESTResource, SitemapSubscriptionCallback, SseBroadcaster.Listener { + implements RESTResource, SitemapSubscriptionCallback, SseBroadcaster.Listener, EventSubscriber { private final Logger logger = LoggerFactory.getLogger(SitemapResource.class); @@ -177,6 +182,7 @@ public class SitemapResource .getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON); private @Nullable ScheduledFuture cleanSubscriptionsJob; + private Set stateChangeListeners = new CopyOnWriteArraySet<>(); @Activate public SitemapResource( // @@ -686,13 +692,11 @@ private boolean blockUnlessChangeOccurs(String sitemapname, String pageId) { private boolean waitForChanges(EList widgets) { long startTime = (new Date()).getTime(); boolean timeout = false; - BlockingStateChangeListener listener = new BlockingStateChangeListener(); - // let's get all items for these widgets - Set items = getAllItems(widgets); - for (GenericItem item : items) { - item.addStateChangeListener(listener); - } - while (!listener.hasChangeOccurred() && !timeout) { + Set items = getAllItems(widgets).stream().map(Item::getName).collect(Collectors.toSet()); + BlockingStateChangeListener listener = new BlockingStateChangeListener(items); + stateChangeListeners.add(listener); + + while (!listener.hasChanged() && !timeout) { timeout = (new Date()).getTime() - startTime > TIMEOUT_IN_MS; try { Thread.sleep(300); @@ -701,9 +705,8 @@ private boolean waitForChanges(EList widgets) { break; } } - for (GenericItem item : items) { - item.removeStateChangeListener(listener); - } + + stateChangeListeners.remove(listener); return timeout; } @@ -782,34 +785,16 @@ private Set getItemsInColorCond(EList colorList) { return items; } - /** - * This is a state change listener, which is merely used to determine, if a - * state change has occurred on one of a list of items. - * - * @author Kai Kreuzer - Initial contribution - * - */ - private static class BlockingStateChangeListener implements StateChangeListener { - - private boolean changed = false; - - @Override - public void stateChanged(Item item, State oldState, State newState) { - changed = true; - } - - /** - * determines, whether a state change has occurred since its creation - * - * @return true, if a state has changed - */ - public boolean hasChangeOccurred() { - return changed; - } + @Override + public Set getSubscribedEventTypes() { + return Set.of(ItemStateChangedEvent.TYPE); + } - @Override - public void stateUpdated(Item item, State state) { - // ignore if the state did not change + @Override + public void receive(Event event) { + if (event instanceof ItemEvent itemEvent) { + String itemName = itemEvent.getItemName(); + stateChangeListeners.forEach(l -> l.itemChanged(itemName)); } } @@ -855,4 +840,23 @@ public void sseEventSinkRemoved(SseEventSink sink, SseSinkInfo info) { subscriptions.removeSubscription(info.subscriptionId); knownSubscriptions.remove(info.subscriptionId); } + + private static class BlockingStateChangeListener { + private final Set items; + private boolean changed = false; + + public BlockingStateChangeListener(Set items) { + this.items = items; + } + + public void itemChanged(String item) { + if (items.contains(item)) { + changed = true; + } + } + + public boolean hasChanged() { + return changed; + } + } } diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/test/java/org/openhab/core/io/rest/sitemap/internal/SitemapResourceTest.java b/bundles/org.openhab.core.io.rest.sitemap/src/test/java/org/openhab/core/io/rest/sitemap/internal/SitemapResourceTest.java index fe384aeabb0..b8a0b0da6ab 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/test/java/org/openhab/core/io/rest/sitemap/internal/SitemapResourceTest.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/test/java/org/openhab/core/io/rest/sitemap/internal/SitemapResourceTest.java @@ -18,7 +18,6 @@ import static org.hamcrest.collection.IsEmptyCollection.empty; import static org.mockito.Mockito.*; -import java.math.BigDecimal; import java.util.Collection; import java.util.List; import java.util.Locale; @@ -45,7 +44,7 @@ import org.openhab.core.io.rest.sitemap.SitemapSubscriptionService; import org.openhab.core.items.GenericItem; import org.openhab.core.items.ItemNotFoundException; -import org.openhab.core.library.types.DecimalType; +import org.openhab.core.items.events.ItemEvent; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; import org.openhab.core.model.sitemap.SitemapProvider; @@ -158,11 +157,13 @@ public void whenSitemapsAreProvidedShouldReturnSitemapBeans() { @Test public void whenLongPollingShouldObserveItems() { + ItemEvent itemEvent = mock(ItemEvent.class); + when(itemEvent.getItemName()).thenReturn(item.getName()); new Thread(() -> { try { Thread.sleep(STATE_UPDATE_WAIT_TIME); // wait for the #getPageData call and listeners to attach to the // item - item.setState(PercentType.ZERO); + sitemapResource.receive(itemEvent); } catch (InterruptedException e) { } }).start(); @@ -180,11 +181,13 @@ public void whenLongPollingShouldObserveItems() { @Test public void whenLongPollingShouldObserveItemsFromVisibilityRules() { + ItemEvent itemEvent = mock(ItemEvent.class); + when(itemEvent.getItemName()).thenReturn(visibilityRuleItem.getName()); new Thread(() -> { try { Thread.sleep(STATE_UPDATE_WAIT_TIME); // wait for the #getPageData call and listeners to attach to the // item - visibilityRuleItem.setState(new DecimalType(BigDecimal.ONE)); + sitemapResource.receive(itemEvent); } catch (InterruptedException e) { } }).start(); @@ -202,11 +205,13 @@ public void whenLongPollingShouldObserveItemsFromVisibilityRules() { @Test public void whenLongPollingShouldObserveItemsFromLabelColorConditions() { + ItemEvent itemEvent = mock(ItemEvent.class); + when(itemEvent.getItemName()).thenReturn(labelColorItem.getName()); new Thread(() -> { try { Thread.sleep(STATE_UPDATE_WAIT_TIME); // wait for the #getPageData call and listeners to attach to the // item - labelColorItem.setState(new DecimalType(BigDecimal.ONE)); + sitemapResource.receive(itemEvent); } catch (InterruptedException e) { } }).start(); @@ -224,11 +229,13 @@ public void whenLongPollingShouldObserveItemsFromLabelColorConditions() { @Test public void whenLongPollingShouldObserveItemsFromValueColorConditions() { + ItemEvent itemEvent = mock(ItemEvent.class); + when(itemEvent.getItemName()).thenReturn(valueColorItem.getName()); new Thread(() -> { try { Thread.sleep(STATE_UPDATE_WAIT_TIME); // wait for the #getPageData call and listeners to attach to the // item - valueColorItem.setState(new DecimalType(BigDecimal.ONE)); + sitemapResource.receive(itemEvent); } catch (InterruptedException e) { } }).start(); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/events/EventHandler.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/events/EventHandler.java index 4217bec0a24..aa8cbd624e3 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/events/EventHandler.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/events/EventHandler.java @@ -13,14 +13,18 @@ package org.openhab.core.internal.events; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -47,9 +51,7 @@ public class EventHandler implements AutoCloseable { private final Map> typedEventSubscribers; private final Map typedEventFactories; - private final ScheduledExecutorService watcher = Executors - .newSingleThreadScheduledExecutor(new NamedThreadFactory("eventwatcher")); - private final ExecutorService executor = Executors.newSingleThreadExecutor(new NamedThreadFactory("eventexecutor")); + private final Map, ExecutorRecord> executors = new HashMap<>(); /** * Create a new event handler. @@ -63,10 +65,19 @@ public EventHandler(final Map> typedEventSubscriber this.typedEventFactories = typedEventFactories; } + private synchronized ExecutorRecord createExecutorRecord(Class subscriber) { + return new ExecutorRecord( + Executors.newSingleThreadExecutor(new NamedThreadFactory("eventexecutor-" + executors.size())), + Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("eventwatcher-" + executors.size())), + new AtomicInteger()); + } + @Override public void close() { - watcher.shutdownNow(); - executor.shutdownNow(); + executors.values().forEach(r -> { + r.executor.shutdownNow(); + r.watcher.shutdownNow(); + }); } public void handleEvent(org.osgi.service.event.Event osgiEvent) { @@ -140,8 +151,16 @@ private synchronized void dispatchEvent(final Set eventSubscrib EventFilter filter = eventSubscriber.getEventFilter(); if (filter == null || filter.apply(event)) { logger.trace("Delegate event to subscriber ({}).", eventSubscriber.getClass()); - executor.submit(() -> { - ScheduledFuture logTimeout = watcher.schedule( + ExecutorRecord executorRecord = Objects.requireNonNull( + executors.computeIfAbsent(eventSubscriber.getClass(), this::createExecutorRecord)); + int queueSize = executorRecord.count().incrementAndGet(); + if (queueSize > 1000) { + logger.warn( + "The queue for a subscriber of type '{}' exceeds 1000 elements. System may be unstable.", + eventSubscriber.getClass()); + } + CompletableFuture.runAsync(() -> { + ScheduledFuture logTimeout = executorRecord.watcher().schedule( () -> logger.warn("Dispatching event to subscriber '{}' takes more than {}ms.", eventSubscriber, EVENTSUBSCRIBER_EVENTHANDLING_MAX_MS), EVENTSUBSCRIBER_EVENTHANDLING_MAX_MS, TimeUnit.MILLISECONDS); @@ -152,10 +171,13 @@ private synchronized void dispatchEvent(final Set eventSubscrib EventSubscriber.class.getName(), ex.getMessage(), ex); } logTimeout.cancel(false); - }); + }, executorRecord.executor()).thenRun(executorRecord.count::decrementAndGet); } else { logger.trace("Skip event subscriber ({}) because of its filter.", eventSubscriber.getClass()); } } } + + private record ExecutorRecord(ExecutorService executor, ScheduledExecutorService watcher, AtomicInteger count) { + } } From 4be95788f18d815bd6bf7e62ecb403b0c40b71c5 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Tue, 4 Jul 2023 07:34:27 +0200 Subject: [PATCH 121/126] Remove error logging from PersistenceIncludeFilter (#3682) Signed-off-by: Jan N. Klug --- .../core/persistence/filter/PersistenceIncludeFilter.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceIncludeFilter.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceIncludeFilter.java index 105562f1df0..c25aa181a3d 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceIncludeFilter.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceIncludeFilter.java @@ -82,14 +82,8 @@ public boolean apply(Item item) { } if (inverted) { - logger.error("Compare {} {} to {} -> {}, {} -> {}", inverted, compareValue, lower, - compareValue.compareTo(lower) <= 0, upper, compareValue.compareTo(upper) >= 0); - return compareValue.compareTo(lower) <= 0 || compareValue.compareTo(upper) >= 0; } else { - - logger.error("Compare {} {} to {} -> {}, {} -> {}", inverted, compareValue, lower, - compareValue.compareTo(lower) >= 0, upper, compareValue.compareTo(upper) <= 0); return compareValue.compareTo(lower) >= 0 && compareValue.compareTo(upper) <= 0; } } From b2934ad044deb163347cf006d3178c5f57a78339 Mon Sep 17 00:00:00 2001 From: lolodomo Date: Wed, 5 Jul 2023 20:15:10 +0200 Subject: [PATCH 122/126] Avoid warning log introduced by PR #3644 (#3686) Fix openhab/openhab-webui#1951 Signed-off-by: Laurent Garnier --- .../ui/internal/items/ItemUIRegistryImpl.java | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java index d5a69469427..1862082e2e8 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java @@ -1373,26 +1373,29 @@ public void removeRegistryHook(RegistryHook hook) { @Override public @Nullable String getUnitForWidget(Widget w) { - try { - Item item = getItem(w.getItem()); + String itemName = w.getItem(); + if (itemName != null) { + try { + Item item = getItem(itemName); - // we require the item to define a dimension, otherwise no unit will be reported to the UIs. - if (item instanceof NumberItem numberItem && numberItem.getDimension() != null) { - String pattern = getFormatPattern(w); - if (pattern == null || pattern.isBlank()) { - // if no Label was assigned to the Widget we fallback to the items unit - return numberItem.getUnitSymbol(); - } + // we require the item to define a dimension, otherwise no unit will be reported to the UIs. + if (item instanceof NumberItem numberItem && numberItem.getDimension() != null) { + String pattern = getFormatPattern(w); + if (pattern == null || pattern.isBlank()) { + // if no Label was assigned to the Widget we fallback to the items unit + return numberItem.getUnitSymbol(); + } - String unit = getUnitFromPattern(pattern); - if (!UnitUtils.UNIT_PLACEHOLDER.equals(unit)) { - return unit; - } + String unit = getUnitFromPattern(pattern); + if (!UnitUtils.UNIT_PLACEHOLDER.equals(unit)) { + return unit; + } - return numberItem.getUnitSymbol(); + return numberItem.getUnitSymbol(); + } + } catch (ItemNotFoundException e) { + logger.warn("Failed to retrieve item during widget rendering, item does not exist: {}", e.getMessage()); } - } catch (ItemNotFoundException e) { - logger.warn("Failed to retrieve item during widget rendering, item does not exist: {}", e.getMessage()); } return ""; From e9719eb57763bed8ca5a81957bd2b65ddc5f9939 Mon Sep 17 00:00:00 2001 From: lolodomo Date: Wed, 5 Jul 2023 21:02:24 +0200 Subject: [PATCH 123/126] Make firmwareStatus field not final in DTO object (#3687) Fix #3679 If not, calls of method limitToFields by PR #3335 fails when the list of fields does not contain firmwareStatus. Signed-off-by: Laurent Garnier --- .../org/openhab/core/io/rest/core/thing/EnrichedThingDTO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/thing/EnrichedThingDTO.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/thing/EnrichedThingDTO.java index dfa37e601a3..c17ce4df1b4 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/thing/EnrichedThingDTO.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/thing/EnrichedThingDTO.java @@ -31,7 +31,7 @@ public class EnrichedThingDTO extends AbstractThingDTO { public List channels; public ThingStatusInfo statusInfo; - public final FirmwareStatusDTO firmwareStatus; + public FirmwareStatusDTO firmwareStatus; public boolean editable; /** From 6a0b268c3e3ced6322d0c3c4a23128a0c316332b Mon Sep 17 00:00:00 2001 From: J-N-K Date: Thu, 6 Jul 2023 08:25:13 +0200 Subject: [PATCH 124/126] Fix wrong ready marker in ScriptEngineFactoryBundleTracker (#3683) * Fix wrong ready marker in ScriptEngineFactoryBundleTracker * fix ready marker handling Signed-off-by: Jan N. Klug --- .../ScriptEngineFactoryBundleTracker.java | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryBundleTracker.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryBundleTracker.java index 3b4f6f4dd9f..69c23fecc66 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryBundleTracker.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryBundleTracker.java @@ -50,22 +50,19 @@ public class ScriptEngineFactoryBundleTracker extends BundleTracker impl private final Logger logger = LoggerFactory.getLogger(ScriptEngineFactoryBundleTracker.class); private final ReadyService readyService; - private final StartLevelService startLevelService; private final Map bundles = new ConcurrentHashMap<>(); + private boolean startLevel = false; private boolean ready = false; @Activate - public ScriptEngineFactoryBundleTracker(final @Reference ReadyService readyService, - final @Reference StartLevelService startLevelService, BundleContext bc) { + public ScriptEngineFactoryBundleTracker(final @Reference ReadyService readyService, BundleContext bc) { super(bc, STATE_MASK, null); this.readyService = readyService; - this.startLevelService = startLevelService; - this.open(); readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE) - .withIdentifier(Integer.toString(StartLevelService.STARTLEVEL_OSGI))); + .withIdentifier(Integer.toString(StartLevelService.STARTLEVEL_MODEL))); } @Deactivate @@ -85,8 +82,8 @@ public Bundle addingBundle(@NonNullByDefault({}) Bundle bundle, @Nullable Bundle if (isScriptingBundle(bundle)) { logger.debug("Added {}: {} ", bsn, stateToString(state)); bundles.put(bsn, state); + checkReady(); } - checkReady(); return bundle; } @@ -99,8 +96,8 @@ public void modifiedBundle(@NonNullByDefault({}) Bundle bundle, @Nullable Bundle if (isScriptingBundle(bundle)) { logger.debug("Modified {}: {}", bsn, stateToString(state)); bundles.put(bsn, state); + checkReady(); } - checkReady(); } @Override @@ -110,27 +107,35 @@ public void removedBundle(@NonNullByDefault({}) Bundle bundle, @Nullable BundleE if (isScriptingBundle(bundle)) { logger.debug("Removed {}", bsn); bundles.remove(bsn); + checkReady(); } - checkReady(); } @Override public void onReadyMarkerAdded(ReadyMarker readyMarker) { + logger.debug("Readymarker {} added", readyMarker); + startLevel = true; checkReady(); } @Override public void onReadyMarkerRemoved(ReadyMarker readyMarker) { + logger.debug("Readymarker {} removed", readyMarker); + startLevel = false; ready = false; readyService.unmarkReady(READY_MARKER); } - private void checkReady() { - if (!ready && startLevelService.getStartLevel() > StartLevelService.STARTLEVEL_OSGI && allBundlesActive()) { - logger.info("All automation bundles ready."); + private synchronized void checkReady() { + boolean allBundlesActive = allBundlesActive(); + logger.trace("ready: {}, startlevel: {}, allActive: {}", ready, startLevel, allBundlesActive); + + if (!ready && startLevel && allBundlesActive) { + logger.debug("Adding ready marker: All automation bundles ready ({})", bundles); readyService.markReady(READY_MARKER); ready = true; - } else if (ready && !allBundlesActive()) { + } else if (ready && !allBundlesActive) { + logger.debug("Removing ready marker: Not all automation bundles ready ({})", bundles); readyService.unmarkReady(READY_MARKER); ready = false; } From abcfe546780a45c754bf5a1f8dc68b9de12753b0 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Thu, 6 Jul 2023 08:26:33 +0200 Subject: [PATCH 125/126] Fix marketplace add-ons missing config description URI (#3688) Signed-off-by: Jan N. Klug --- .../AbstractRemoteAddonService.java | 20 +++++++++++--- .../CommunityMarketplaceAddonService.java | 22 +++++++++------ .../internal/json/JsonAddonService.java | 6 +++-- .../AbstractRemoteAddonServiceTest.java | 6 +++-- .../marketplace/test/TestAddonService.java | 5 ++-- .../java/org/openhab/core/addon/Addon.java | 27 +++++++++++++++++++ 6 files changed, 69 insertions(+), 17 deletions(-) diff --git a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/AbstractRemoteAddonService.java b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/AbstractRemoteAddonService.java index b07f57086dd..95f4b79f9e4 100644 --- a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/AbstractRemoteAddonService.java +++ b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/AbstractRemoteAddonService.java @@ -20,6 +20,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -29,6 +30,8 @@ import org.openhab.core.OpenHAB; import org.openhab.core.addon.Addon; import org.openhab.core.addon.AddonEventFactory; +import org.openhab.core.addon.AddonInfo; +import org.openhab.core.addon.AddonInfoRegistry; import org.openhab.core.addon.AddonService; import org.openhab.core.addon.AddonType; import org.openhab.core.cache.ExpiringCache; @@ -66,13 +69,15 @@ public abstract class AbstractRemoteAddonService implements AddonService { protected final ConfigurationAdmin configurationAdmin; protected final ExpiringCache> cachedRemoteAddons = new ExpiringCache<>(Duration.ofMinutes(15), this::getRemoteAddons); + protected final AddonInfoRegistry addonInfoRegistry; protected List cachedAddons = List.of(); protected List installedAddons = List.of(); private final Logger logger = LoggerFactory.getLogger(AbstractRemoteAddonService.class); public AbstractRemoteAddonService(EventPublisher eventPublisher, ConfigurationAdmin configurationAdmin, - StorageService storageService, String servicePid) { + StorageService storageService, AddonInfoRegistry addonInfoRegistry, String servicePid) { + this.addonInfoRegistry = addonInfoRegistry; this.eventPublisher = eventPublisher; this.configurationAdmin = configurationAdmin; this.installedAddonStorage = storageService.getStorage(servicePid); @@ -83,6 +88,16 @@ protected BundleVersion getCoreVersion() { return new BundleVersion(FrameworkUtil.getBundle(OpenHAB.class).getVersion().toString()); } + private Addon convertFromStorage(Map.Entry entry) { + Addon storedAddon = Objects.requireNonNull(gson.fromJson(entry.getValue(), Addon.class)); + AddonInfo addonInfo = addonInfoRegistry.getAddonInfo(storedAddon.getType() + "-" + storedAddon.getId()); + if (addonInfo != null && storedAddon.getConfigDescriptionURI().isBlank()) { + return Addon.create(storedAddon).withConfigDescriptionURI(addonInfo.getConfigDescriptionURI()).build(); + } else { + return storedAddon; + } + } + @Override public void refreshSource() { if (!addonHandlers.stream().allMatch(MarketplaceAddonHandler::isReady)) { @@ -92,8 +107,7 @@ public void refreshSource() { } List addons = new ArrayList<>(); try { - installedAddonStorage.stream().map(e -> Objects.requireNonNull(gson.fromJson(e.getValue(), Addon.class))) - .forEach(addons::add); + installedAddonStorage.stream().map(this::convertFromStorage).forEach(addons::add); } catch (JsonSyntaxException e) { List.copyOf(installedAddonStorage.getKeys()).forEach(installedAddonStorage::remove); logger.error( diff --git a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/community/CommunityMarketplaceAddonService.java b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/community/CommunityMarketplaceAddonService.java index 7054b6f94bd..69104bec7e0 100644 --- a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/community/CommunityMarketplaceAddonService.java +++ b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/community/CommunityMarketplaceAddonService.java @@ -36,6 +36,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.addon.Addon; +import org.openhab.core.addon.AddonInfoRegistry; import org.openhab.core.addon.AddonService; import org.openhab.core.addon.AddonType; import org.openhab.core.addon.marketplace.AbstractRemoteAddonService; @@ -115,8 +116,8 @@ public class CommunityMarketplaceAddonService extends AbstractRemoteAddonService @Activate public CommunityMarketplaceAddonService(final @Reference EventPublisher eventPublisher, @Reference ConfigurationAdmin configurationAdmin, @Reference StorageService storageService, - Map config) { - super(eventPublisher, configurationAdmin, storageService, SERVICE_PID); + @Reference AddonInfoRegistry addonInfoRegistry, Map config) { + super(eventPublisher, configurationAdmin, storageService, addonInfoRegistry, SERVICE_PID); modified(config); } @@ -200,10 +201,13 @@ protected List getRemoteAddons() { @Override public @Nullable Addon getAddon(String uid, @Nullable Locale locale) { + String queryId = uid.startsWith(ADDON_ID_PREFIX) ? uid : ADDON_ID_PREFIX + uid; + // check if it is an installed add-on (cachedAddons also contains possibly incomplete results from the remote // side, we need to retrieve them from Discourse) - if (installedAddons.contains(uid)) { - return cachedAddons.stream().filter(e -> uid.equals(e.getUid())).findAny().orElse(null); + + if (installedAddons.contains(queryId)) { + return cachedAddons.stream().filter(e -> queryId.equals(e.getUid())).findAny().orElse(null); } if (!remoteEnabled()) { @@ -437,11 +441,13 @@ private Addon convertTopicToAddon(DiscourseTopicResponseDTO topic) { boolean installed = addonHandlers.stream() .anyMatch(handler -> handler.supports(type, contentType) && handler.isInstalled(uid)); - return Addon.create(uid).withType(type).withId(id).withContentType(contentType).withLabel(topic.title) - .withImageLink(topic.imageUrl).withLink(COMMUNITY_TOPIC_URL + topic.id.toString()) + Addon.Builder builder = Addon.create(uid).withType(type).withId(id).withContentType(contentType) + .withLabel(topic.title).withImageLink(topic.imageUrl) + .withLink(COMMUNITY_TOPIC_URL + topic.id.toString()) .withAuthor(topic.postStream.posts[0].displayUsername).withMaturity(maturity) - .withDetailedDescription(detailedDescription).withInstalled(installed).withProperties(properties) - .build(); + .withDetailedDescription(detailedDescription).withInstalled(installed).withProperties(properties); + + return builder.build(); } private @Nullable String determineIdFromUrl(String url) { diff --git a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/json/JsonAddonService.java b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/json/JsonAddonService.java index 626c6852724..0b39fd1460c 100644 --- a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/json/JsonAddonService.java +++ b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/json/JsonAddonService.java @@ -30,6 +30,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.addon.Addon; +import org.openhab.core.addon.AddonInfoRegistry; import org.openhab.core.addon.AddonService; import org.openhab.core.addon.marketplace.AbstractRemoteAddonService; import org.openhab.core.addon.marketplace.MarketplaceAddonHandler; @@ -78,8 +79,9 @@ public class JsonAddonService extends AbstractRemoteAddonService { @Activate public JsonAddonService(@Reference EventPublisher eventPublisher, @Reference StorageService storageService, - @Reference ConfigurationAdmin configurationAdmin, Map config) { - super(eventPublisher, configurationAdmin, storageService, SERVICE_PID); + @Reference ConfigurationAdmin configurationAdmin, @Reference AddonInfoRegistry addonInfoRegistry, + Map config) { + super(eventPublisher, configurationAdmin, storageService, addonInfoRegistry, SERVICE_PID); modified(config); } diff --git a/bundles/org.openhab.core.addon.marketplace/src/test/java/org/openhab/core/addon/marketplace/AbstractRemoteAddonServiceTest.java b/bundles/org.openhab.core.addon.marketplace/src/test/java/org/openhab/core/addon/marketplace/AbstractRemoteAddonServiceTest.java index 58004557856..dac9fa3fc09 100644 --- a/bundles/org.openhab.core.addon.marketplace/src/test/java/org/openhab/core/addon/marketplace/AbstractRemoteAddonServiceTest.java +++ b/bundles/org.openhab.core.addon.marketplace/src/test/java/org/openhab/core/addon/marketplace/AbstractRemoteAddonServiceTest.java @@ -43,6 +43,7 @@ import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.openhab.core.addon.Addon; +import org.openhab.core.addon.AddonInfoRegistry; import org.openhab.core.addon.marketplace.test.TestAddonHandler; import org.openhab.core.addon.marketplace.test.TestAddonService; import org.openhab.core.events.Event; @@ -64,6 +65,7 @@ @NonNullByDefault public class AbstractRemoteAddonServiceTest { private @Mock @NonNullByDefault({}) StorageService storageService; + private @Mock @NonNullByDefault({}) AddonInfoRegistry addonInfoRegistry; private @Mock @NonNullByDefault({}) ConfigurationAdmin configurationAdmin; private @Mock @NonNullByDefault({}) EventPublisher eventPublisher; private @Mock @NonNullByDefault({}) Configuration configuration; @@ -82,7 +84,7 @@ public void initialize() throws IOException { addonHandler = new TestAddonHandler(); - addonService = new TestAddonService(eventPublisher, configurationAdmin, storageService); + addonService = new TestAddonService(eventPublisher, configurationAdmin, storageService, addonInfoRegistry); addonService.addAddonHandler(addonHandler); } @@ -93,7 +95,7 @@ public void testSourceNotRefreshedIfAddonHandlerNotReady() { addonHandler = new TestAddonHandler(); addonHandler.setReady(false); - addonService = new TestAddonService(eventPublisher, configurationAdmin, storageService); + addonService = new TestAddonService(eventPublisher, configurationAdmin, storageService, addonInfoRegistry); addonService.addAddonHandler(addonHandler); List addons = addonService.getAddons(null); diff --git a/bundles/org.openhab.core.addon.marketplace/src/test/java/org/openhab/core/addon/marketplace/test/TestAddonService.java b/bundles/org.openhab.core.addon.marketplace/src/test/java/org/openhab/core/addon/marketplace/test/TestAddonService.java index 73ffa5cead0..fe5bc672a5a 100644 --- a/bundles/org.openhab.core.addon.marketplace/src/test/java/org/openhab/core/addon/marketplace/test/TestAddonService.java +++ b/bundles/org.openhab.core.addon.marketplace/src/test/java/org/openhab/core/addon/marketplace/test/TestAddonService.java @@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.addon.Addon; +import org.openhab.core.addon.AddonInfoRegistry; import org.openhab.core.addon.marketplace.AbstractRemoteAddonService; import org.openhab.core.addon.marketplace.BundleVersion; import org.openhab.core.addon.marketplace.MarketplaceAddonHandler; @@ -51,8 +52,8 @@ public class TestAddonService extends AbstractRemoteAddonService { private int remoteCalls = 0; public TestAddonService(EventPublisher eventPublisher, ConfigurationAdmin configurationAdmin, - StorageService storageService) { - super(eventPublisher, configurationAdmin, storageService, SERVICE_PID); + StorageService storageService, AddonInfoRegistry addonInfoRegistry) { + super(eventPublisher, configurationAdmin, storageService, addonInfoRegistry, SERVICE_PID); } @Override diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/Addon.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/Addon.java index 84019c79fb9..e1d06175ae9 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/Addon.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/Addon.java @@ -304,6 +304,33 @@ public static Builder create(String uid) { return new Builder(uid); } + public static Builder create(Addon addon) { + Addon.Builder builder = new Builder(addon.uid); + builder.id = addon.id; + builder.label = addon.label; + builder.version = addon.version; + builder.maturity = addon.maturity; + builder.compatible = addon.compatible; + builder.contentType = addon.contentType; + builder.link = addon.link; + builder.author = addon.author; + builder.verifiedAuthor = addon.verifiedAuthor; + builder.installed = addon.installed; + builder.type = addon.type; + builder.description = addon.description; + builder.detailedDescription = addon.detailedDescription; + builder.configDescriptionURI = addon.configDescriptionURI; + builder.keywords = addon.keywords; + builder.countries = addon.countries; + builder.license = addon.license; + builder.connection = addon.connection; + builder.backgroundColor = addon.backgroundColor; + builder.imageLink = addon.imageLink; + builder.properties = addon.properties; + builder.loggerPackages = addon.loggerPackages; + return builder; + } + public static class Builder { private final String uid; private String id; From 591b16ff35b78f3918373410577998991113e7c0 Mon Sep 17 00:00:00 2001 From: lolodomo Date: Fri, 7 Jul 2023 20:26:12 +0200 Subject: [PATCH 126/126] Avoid null hash warning in WatchServiceImpl when possible (#3691) Fix #3680 Signed-off-by: Laurent Garnier --- .../core/internal/service/WatchServiceImpl.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/WatchServiceImpl.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/WatchServiceImpl.java index b9c093d9220..c77010e37d9 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/WatchServiceImpl.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/service/WatchServiceImpl.java @@ -215,6 +215,7 @@ public void unregisterListener(WatchEventListener watchEventListener) { @Override public void onEvent(@Nullable DirectoryChangeEvent directoryChangeEvent) throws IOException { + logger.trace("onEvent {}", directoryChangeEvent); if (directoryChangeEvent == null || directoryChangeEvent.isDirectory() || directoryChangeEvent.eventType() == DirectoryChangeEvent.EventType.OVERFLOW) { // exit early, we are neither interested in directory events nor in OVERFLOW events @@ -223,12 +224,6 @@ public void onEvent(@Nullable DirectoryChangeEvent directoryChangeEvent) throws Path path = directoryChangeEvent.path(); - if (directoryChangeEvent.eventType() != DirectoryChangeEvent.EventType.DELETE - && directoryChangeEvent.hash() == null) { - logger.warn("Detected invalid event (hash must not be null for CREATE/MODIFY): {}", directoryChangeEvent); - return; - } - synchronized (scheduledEvents) { ScheduledFuture future = scheduledEvents.remove(path); if (future != null && !future.isDone()) { @@ -259,9 +254,17 @@ private void notifyListeners(Path path) { hashCache.remove(lastElement.path()); doNotify(path, Kind.DELETE); } else if (firstElement.eventType() == DirectoryChangeEvent.EventType.CREATE) { + if (lastElement.hash() == null) { + logger.warn("Detected invalid event (hash must not be null for CREATE/MODIFY): {}", lastElement); + return; + } hashCache.put(lastElement.path(), lastElement.hash()); doNotify(path, Kind.CREATE); } else { + if (lastElement.hash() == null) { + logger.warn("Detected invalid event (hash must not be null for CREATE/MODIFY): {}", lastElement); + return; + } FileHash oldHash = hashCache.put(lastElement.path(), lastElement.hash()); if (!Objects.equals(oldHash, lastElement.hash())) { // only notify if hashes are different, otherwise the file content did not chnge