]> git.basschouten.com Git - openhab-addons.git/commitdiff
[boschshc] Code Enhancements (#14667)
authorDavid Pace <dev@davidpace.de>
Thu, 30 Mar 2023 16:31:54 +0000 (18:31 +0200)
committerGitHub <noreply@github.com>
Thu, 30 Mar 2023 16:31:54 +0000 (18:31 +0200)
* [boschshc] Code Enhancements

This commit fixes several compiler warnings as well as several
Checkstyle, FindBugs, PMD and Sonar issues.

* instantiate loggers consistently
* add missing logger calls
* add chained exceptions to logger calls
* provide central Gson instance that does not serialize or deserialize
loggers
* avoid catching NullPointerExceptions
* extract methods where cyclomatic complexity was too high
* remove unnecessary null check in combination with instanceof
* rename local variables that shadow fields
* add missing @NonNull and @Nullable annotations

Signed-off-by: David Pace <dev@davidpace.de>
20 files changed:
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCDeviceHandler.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPolling.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/climatecontrol/ClimateControlHandler.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandler.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryService.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/GsonUtils.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/LoggerExclusionStrategy.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCSystemService.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/dto/HumidityLevelServiceState.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/roomclimatecontrol/dto/RoomClimateControlServiceState.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/smokedetectorcheck/SmokeDetectorCheckState.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/temperaturelevel/dto/TemperatureLevelServiceState.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandlerTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/LongPollResultTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/batterylevel/BatteryLevelTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceStateTest.java

index 2168e1223f1141c7c15bddd99e6a9cabca3df7a1..21d8d2ad4f70f9f9b53543c253f1c70580bf01c9 100644 (file)
@@ -21,6 +21,8 @@ import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingStatus;
 import org.openhab.core.thing.ThingStatusDetail;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Handler for physical Bosch devices with configurable IDs (as opposed to system services, which have static IDs).
@@ -42,12 +44,14 @@ import org.openhab.core.thing.ThingStatusDetail;
 @NonNullByDefault
 public abstract class BoschSHCDeviceHandler extends BoschSHCHandler {
 
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
     /**
      * Bosch SHC configuration loaded from openHAB configuration.
      */
     private @Nullable BoschSHCConfiguration config;
 
-    public BoschSHCDeviceHandler(Thing thing) {
+    protected BoschSHCDeviceHandler(Thing thing) {
         super(thing);
     }
 
index 13f764f9386a565fb0438b60f2aba0aac4fff0b7..a6e9747650b2aa3f17f85cec7ce508b906f34283 100644 (file)
@@ -40,7 +40,6 @@ import org.openhab.core.types.RefreshType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.gson.Gson;
 import com.google.gson.JsonElement;
 
 /**
@@ -80,12 +79,7 @@ public abstract class BoschSHCHandler extends BaseThingHandler {
         public final Collection<String> affectedChannels;
     }
 
-    /**
-     * Reusable gson instance to convert a class to json string and back in derived classes.
-     */
-    protected static final Gson GSON = new Gson();
-
-    protected final Logger logger = LoggerFactory.getLogger(getClass());
+    private final Logger logger = LoggerFactory.getLogger(getClass());
 
     /**
      * Services of the device.
@@ -450,7 +444,7 @@ public abstract class BoschSHCHandler extends BaseThingHandler {
      */
     private <TState extends BoschSHCServiceState> void registerService(BoschSHCService<TState> service,
             Collection<String> affectedChannels) {
-        this.services.add(new DeviceService<TState>(service, affectedChannels));
+        this.services.add(new DeviceService<>(service, affectedChannels));
     }
 
     /**
index a07f9f5be3b993acb195ba198922dbb1b402eab2..1e85509b77412e4d07e86b923c152cba24b1d35c 100644 (file)
@@ -37,10 +37,10 @@ import org.eclipse.jetty.http.HttpMethod;
 import org.eclipse.jetty.http.HttpStatus;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.gson.Gson;
 import com.google.gson.JsonSyntaxException;
 
 /**
@@ -51,9 +51,8 @@ import com.google.gson.JsonSyntaxException;
  */
 @NonNullByDefault
 public class BoschHttpClient extends HttpClient {
-    private static final Gson GSON = new Gson();
 
-    private final Logger logger = LoggerFactory.getLogger(BoschHttpClient.class);
+    private final Logger logger = LoggerFactory.getLogger(getClass());
 
     private final String ipAddress;
     private final String systemPassword;
@@ -177,8 +176,10 @@ public class BoschHttpClient extends HttpClient {
                 logger.debug("Online check failed with status code: {}", contentResponse.getStatus());
                 return false;
             }
-        } catch (TimeoutException | ExecutionException | NullPointerException e) {
-            logger.debug("Online check failed because of {}!", e.getMessage());
+        } catch (InterruptedException e) {
+            throw e;
+        } catch (Exception e) {
+            logger.debug("Online check failed because of {}!", e.getMessage(), e);
             return false;
         }
     }
@@ -203,8 +204,10 @@ public class BoschHttpClient extends HttpClient {
                 logger.debug("Access check failed with status code: {}", contentResponse.getStatus());
                 return false;
             }
-        } catch (TimeoutException | ExecutionException | NullPointerException e) {
-            logger.debug("Access check failed because of {}!", e.getMessage());
+        } catch (InterruptedException e) {
+            throw e;
+        } catch (Exception e) {
+            logger.debug("Access check failed because of {}!", e.getMessage(), e);
             return false;
         }
     }
@@ -249,8 +252,8 @@ public class BoschHttpClient extends HttpClient {
                 logger.info("Pairing failed with response status {}.", contentResponse.getStatus());
                 return false;
             }
-        } catch (TimeoutException | CertificateEncodingException | KeyStoreException | NullPointerException e) {
-            logger.warn("Pairing failed with exception {}", e.getMessage());
+        } catch (TimeoutException | CertificateEncodingException | KeyStoreException | RuntimeException e) {
+            logger.warn("Pairing failed with exception {}", e.getMessage(), e);
             return false;
         } catch (ExecutionException e) {
             // javax.net.ssl.SSLHandshakeException: General SSLEngine problem
@@ -281,15 +284,15 @@ public class BoschHttpClient extends HttpClient {
      * @return created HTTP request instance
      */
     public Request createRequest(String url, HttpMethod method, @Nullable Object content) {
-        logger.trace("Create request for http client {}", this.toString());
+        logger.trace("Create request for http client {}", this);
 
         Request request = this.newRequest(url).method(method).header("Content-Type", "application/json")
                 .header("api-version", "2.1") // see https://github.com/BoschSmartHome/bosch-shc-api-docs/issues/46
                 .timeout(10, TimeUnit.SECONDS); // Set default timeout
 
         if (content != null) {
-            String body = GSON.toJson(content);
-            logger.trace("create request for {} and content {}", url, content.toString());
+            String body = GsonUtils.DEFAULT_GSON_INSTANCE.toJson(content);
+            logger.trace("create request for {} and content {}", url, content);
             request = request.content(new StringContentProvider(body));
         } else {
             logger.trace("create request for {}", url);
@@ -314,7 +317,7 @@ public class BoschHttpClient extends HttpClient {
             Predicate<TContent> contentValidator,
             @Nullable BiFunction<Integer, String, BoschSHCException> errorResponseHandler)
             throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
-        logger.trace("Send request: {}", request.toString());
+        logger.trace("Send request: {}", request);
 
         ContentResponse contentResponse = request.send();
 
@@ -334,7 +337,7 @@ public class BoschHttpClient extends HttpClient {
 
         try {
             @Nullable
-            TContent content = GSON.fromJson(textContent, responseContentClass);
+            TContent content = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(textContent, responseContentClass);
             if (content == null) {
                 throw new ExecutionException(String.format("Received no content in response, expected type %s",
                         responseContentClass.getName()), null);
index 6c0752c78d6b3487b1739d816c0cc0a32149fb1f..926c450637a787ca6d126c904995f1f2e85f20c5 100644 (file)
@@ -42,6 +42,7 @@ import org.openhab.binding.boschshc.internal.discovery.ThingDiscoveryService;
 import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
 import org.openhab.binding.boschshc.internal.exceptions.LongPollingFailedException;
 import org.openhab.binding.boschshc.internal.exceptions.PairingFailedException;
+import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
 import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
 import org.openhab.binding.boschshc.internal.services.dto.JsonRestExceptionResponse;
 import org.openhab.core.thing.Bridge;
@@ -58,7 +59,6 @@ import org.osgi.framework.FrameworkUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.gson.Gson;
 import com.google.gson.JsonElement;
 import com.google.gson.reflect.TypeToken;
 
@@ -76,11 +76,6 @@ public class BridgeHandler extends BaseBridgeHandler {
 
     private final Logger logger = LoggerFactory.getLogger(BridgeHandler.class);
 
-    /**
-     * gson instance to convert a class to json string and back.
-     */
-    private final Gson gson = new Gson();
-
     /**
      * Handler to do long polling.
      */
@@ -143,6 +138,7 @@ public class BridgeHandler extends BaseBridgeHandler {
             // prepare SSL key and certificates
             factory = new BoschSslUtil(ipAddress).getSslContextFactory();
         } catch (PairingFailedException e) {
+            logger.debug("Error while obtaining SSL context factory.", e);
             this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
                     "@text/offline.conf-error-ssl");
             return;
@@ -187,7 +183,7 @@ public class BridgeHandler extends BaseBridgeHandler {
             try {
                 httpClient.stop();
             } catch (Exception e) {
-                logger.debug("HttpClient failed on bridge disposal: {}", e.getMessage());
+                logger.debug("HttpClient failed on bridge disposal: {}", e.getMessage(), e);
             }
             this.httpClient = null;
         }
@@ -197,6 +193,7 @@ public class BridgeHandler extends BaseBridgeHandler {
 
     @Override
     public void handleCommand(ChannelUID channelUID, Command command) {
+        // commands are handled by individual device handlers
     }
 
     /**
@@ -262,11 +259,7 @@ public class BridgeHandler extends BaseBridgeHandler {
 
             // start long polling loop
             this.updateStatus(ThingStatus.ONLINE);
-            try {
-                this.longPolling.start(httpClient);
-            } catch (LongPollingFailedException e) {
-                this.handleLongPollFailure(e);
-            }
+            startLongPolling(httpClient);
 
         } catch (InterruptedException e) {
             this.updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.UNKNOWN.NONE, "@text/offline.interrupted");
@@ -274,6 +267,14 @@ public class BridgeHandler extends BaseBridgeHandler {
         }
     }
 
+    private void startLongPolling(BoschHttpClient httpClient) {
+        try {
+            this.longPolling.start(httpClient);
+        } catch (LongPollingFailedException e) {
+            this.handleLongPollFailure(e);
+        }
+    }
+
     /**
      * Check the bridge access by sending an HTTP request.
      * Does not throw any exception in case the request fails.
@@ -334,11 +335,10 @@ public class BridgeHandler extends BaseBridgeHandler {
 
             Type collectionType = new TypeToken<ArrayList<Device>>() {
             }.getType();
-            @Nullable
-            List<Device> nullableDevices = gson.fromJson(content, collectionType);
+            List<Device> nullableDevices = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content, collectionType);
             return Optional.ofNullable(nullableDevices).orElse(Collections.emptyList());
         } catch (TimeoutException | ExecutionException e) {
-            logger.debug("Request devices failed because of {}!", e.getMessage());
+            logger.debug("Request devices failed because of {}!", e.getMessage(), e);
             return Collections.emptyList();
         }
     }
@@ -371,7 +371,7 @@ public class BridgeHandler extends BaseBridgeHandler {
                 Type collectionType = new TypeToken<ArrayList<Room>>() {
                 }.getType();
 
-                ArrayList<Room> rooms = gson.fromJson(content, collectionType);
+                ArrayList<Room> rooms = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content, collectionType);
                 return Objects.requireNonNullElse(rooms, emptyRooms);
             } catch (TimeoutException | ExecutionException e) {
                 logger.debug("Request rooms failed because of {}!", e.getMessage());
@@ -452,7 +452,7 @@ public class BridgeHandler extends BaseBridgeHandler {
         // the battery level service receives no individual state object but rather requires the DeviceServiceData
         // structure
         if ("BatteryLevel".equals(deviceServiceData.id)) {
-            return gson.toJsonTree(deviceServiceData);
+            return GsonUtils.DEFAULT_GSON_INSTANCE.toJsonTree(deviceServiceData);
         }
 
         return deviceServiceData.state;
@@ -473,8 +473,7 @@ public class BridgeHandler extends BaseBridgeHandler {
             // All children of this should implement BoschSHCHandler
             @Nullable
             ThingHandler baseHandler = childThing.getHandler();
-            if (baseHandler != null && baseHandler instanceof BoschSHCHandler) {
-                BoschSHCHandler handler = (BoschSHCHandler) baseHandler;
+            if (baseHandler instanceof BoschSHCHandler handler) {
                 @Nullable
                 String deviceId = handler.getBoschID();
 
@@ -530,7 +529,8 @@ public class BridgeHandler extends BaseBridgeHandler {
         Request request = httpClient.createRequest(url, GET);
 
         return httpClient.sendRequest(request, Device.class, Device::isValid, (Integer statusCode, String content) -> {
-            JsonRestExceptionResponse errorResponse = gson.fromJson(content, JsonRestExceptionResponse.class);
+            JsonRestExceptionResponse errorResponse = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content,
+                    JsonRestExceptionResponse.class);
             if (errorResponse != null && JsonRestExceptionResponse.isValid(errorResponse)) {
                 if (errorResponse.errorCode.equals(JsonRestExceptionResponse.ENTITY_NOT_FOUND)) {
                     return new BoschSHCException("@text/offline.conf-error.invalid-device-id");
@@ -628,7 +628,8 @@ public class BridgeHandler extends BaseBridgeHandler {
 
         int statusCode = contentResponse.getStatus();
         if (statusCode != 200) {
-            JsonRestExceptionResponse errorResponse = gson.fromJson(content, JsonRestExceptionResponse.class);
+            JsonRestExceptionResponse errorResponse = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content,
+                    JsonRestExceptionResponse.class);
             if (errorResponse != null) {
                 throw new BoschSHCException(
                         String.format("State request with URL %s failed with status code %d and error code %s", url,
index e5fd1842da26be11658f97e3fc3853e18a517b1b..f1a843af6b901d9a4dc772e3eec50a257a3cba0b 100644 (file)
@@ -30,11 +30,10 @@ import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult;
 import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
 import org.openhab.binding.boschshc.internal.exceptions.LongPollingFailedException;
+import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.gson.Gson;
-
 /**
  * Handles the long polling to the Smart Home Controller.
  *
@@ -45,11 +44,6 @@ public class LongPolling {
 
     private final Logger logger = LoggerFactory.getLogger(LongPolling.class);
 
-    /**
-     * gson instance to convert a class to json string and back.
-     */
-    private final Gson gson = new Gson();
-
     /**
      * Executor to schedule long polls.
      */
@@ -107,17 +101,15 @@ public class LongPolling {
     private String subscribe(BoschHttpClient httpClient) throws LongPollingFailedException {
         try {
             String url = httpClient.getBoschShcUrl("remote/json-rpc");
-            JsonRpcRequest request = new JsonRpcRequest("2.0", "RE/subscribe",
+            JsonRpcRequest subscriptionRequest = new JsonRpcRequest("2.0", "RE/subscribe",
                     new String[] { "com/bosch/sh/remote/*", null });
-            logger.debug("Subscribe: Sending request: {} - using httpClient {}", request.toString(),
-                    httpClient.toString());
-            Request httpRequest = httpClient.createRequest(url, POST, request);
+            logger.debug("Subscribe: Sending request: {} - using httpClient {}", subscriptionRequest, httpClient);
+            Request httpRequest = httpClient.createRequest(url, POST, subscriptionRequest);
             SubscribeResult response = httpClient.sendRequest(httpRequest, SubscribeResult.class,
                     SubscribeResult::isValid, null);
 
             logger.debug("Subscribe: Got subscription ID: {} {}", response.getResult(), response.getJsonrpc());
-            String subscriptionId = response.getResult();
-            return subscriptionId;
+            return response.getResult();
         } catch (TimeoutException | ExecutionException | BoschSHCException e) {
             throw new LongPollingFailedException(
                     String.format("Error on subscribe (Http client: %s): %s", httpClient.toString(), e.getMessage()),
@@ -141,7 +133,6 @@ public class LongPolling {
             this.executeLongPoll(httpClient, subscriptionId);
         } catch (LongPollingFailedException e) {
             this.handleFailure.accept(e);
-            return;
         }
     }
 
@@ -157,15 +148,15 @@ public class LongPolling {
 
         JsonRpcRequest requestContent = new JsonRpcRequest("2.0", "RE/longPoll", new String[] { subscriptionId, "20" });
         String url = httpClient.getBoschShcUrl("remote/json-rpc");
-        Request request = httpClient.createRequest(url, POST, requestContent);
+        Request longPollRequest = httpClient.createRequest(url, POST, requestContent);
 
         // Long polling responds after 20 seconds with an empty response if no update has happened.
         // 10 second threshold was added to not time out if response from controller takes a bit longer than 20 seconds.
-        request.timeout(30, TimeUnit.SECONDS);
+        longPollRequest.timeout(30, TimeUnit.SECONDS);
 
-        this.request = request;
+        this.request = longPollRequest;
         LongPolling longPolling = this;
-        request.send(new BufferingResponseListener() {
+        longPollRequest.send(new BufferingResponseListener() {
             @Override
             public void onComplete(@Nullable Result result) {
                 // NOTE: This handler runs inside the HTTP thread, so we schedule the response handling in a new thread
@@ -195,31 +186,20 @@ public class LongPolling {
         // Check if response was failure or success
         Throwable failure = result != null ? result.getFailure() : null;
         if (failure != null) {
-            if (failure instanceof ExecutionException) {
-                if (failure.getCause() instanceof AbortLongPolling) {
-                    logger.debug("Canceling long polling for subscription id {} because it was aborted",
-                            subscriptionId);
-                } else {
-                    this.handleFailure.accept(new LongPollingFailedException(
-                            "Unexpected exception during long polling request", failure));
-                }
-            } else {
-                this.handleFailure.accept(
-                        new LongPollingFailedException("Unexpected exception during long polling request", failure));
-            }
+            handleLongPollFailure(subscriptionId, failure);
         } else {
             logger.debug("Long poll response: {}", content);
 
             String nextSubscriptionId = subscriptionId;
 
-            LongPollResult longPollResult = gson.fromJson(content, LongPollResult.class);
+            LongPollResult longPollResult = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content, LongPollResult.class);
             if (longPollResult != null && longPollResult.result != null) {
                 this.handleResult.accept(longPollResult);
             } else {
                 logger.debug("Long poll response contained no result: {}", content);
 
                 // Check if we got a proper result from the SHC
-                LongPollError longPollError = gson.fromJson(content, LongPollError.class);
+                LongPollError longPollError = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content, LongPollError.class);
 
                 if (longPollError != null && longPollError.error != null) {
                     logger.debug("Got long poll error: {} (code: {})", longPollError.error.message,
@@ -238,6 +218,20 @@ public class LongPolling {
         }
     }
 
+    private void handleLongPollFailure(String subscriptionId, Throwable failure) {
+        if (failure instanceof ExecutionException) {
+            if (failure.getCause() instanceof AbortLongPolling) {
+                logger.debug("Canceling long polling for subscription id {} because it was aborted", subscriptionId);
+            } else {
+                this.handleFailure.accept(
+                        new LongPollingFailedException("Unexpected exception during long polling request", failure));
+            }
+        } else {
+            this.handleFailure.accept(
+                    new LongPollingFailedException("Unexpected exception during long polling request", failure));
+        }
+    }
+
     @SuppressWarnings("serial")
     private class AbortLongPolling extends BoschSHCException {
     }
index 1d584c9ef921181e86cbacc698eadd677102c4c5..af55dc1824365d59b0fad1540510dd3c0d8174a7 100644 (file)
@@ -12,8 +12,7 @@
  */
 package org.openhab.binding.boschshc.internal.devices.climatecontrol;
 
-import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_SETPOINT_TEMPERATURE;
-import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE;
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.*;
 
 import java.util.List;
 
@@ -29,20 +28,24 @@ import org.openhab.core.library.unit.SIUnits;
 import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * A virtual device which controls up to six Bosch Smart Home radiator thermostats in a room.
- * 
+ *
  * @author Christian Oeing - Initial contribution
  */
 @NonNullByDefault
 public final class ClimateControlHandler extends BoschSHCDeviceHandler {
 
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
     private RoomClimateControlService roomClimateControlService;
 
     /**
      * Constructor.
-     * 
+     *
      * @param thing The Bosch Smart Home device that should be handled.
      */
     public ClimateControlHandler(Thing thing) {
@@ -71,7 +74,7 @@ public final class ClimateControlHandler extends BoschSHCDeviceHandler {
 
     /**
      * Updates the channels which are linked to the {@link TemperatureLevelService} of the device.
-     * 
+     *
      * @param state Current state of {@link TemperatureLevelService}.
      */
     private void updateChannels(TemperatureLevelServiceState state) {
@@ -80,7 +83,7 @@ public final class ClimateControlHandler extends BoschSHCDeviceHandler {
 
     /**
      * Updates the channels which are linked to the {@link RoomClimateControlService} of the device.
-     * 
+     *
      * @param state Current state of {@link RoomClimateControlService}.
      */
     private void updateChannels(RoomClimateControlServiceState state) {
@@ -89,7 +92,7 @@ public final class ClimateControlHandler extends BoschSHCDeviceHandler {
 
     /**
      * Sets the desired temperature for the device.
-     * 
+     *
      * @param quantityType Command which contains the new desired temperature.
      */
     private void updateSetpointTemperature(QuantityType<?> quantityType) {
index 3ad8b502e9986ae25f89d652c7755269a70a83fd..a1875aecc6a7991892558149a5644ab6e6cb9343 100644 (file)
@@ -28,10 +28,12 @@ import org.openhab.core.library.types.UpDownType;
 import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Control of your shutter to take any position you desire.
- * 
+ *
  * @author Christian Oeing - Initial contribution
  */
 @NonNullByDefault
@@ -49,6 +51,8 @@ public class ShutterControlHandler extends BoschSHCDeviceHandler {
         }
     }
 
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
     private ShutterControlService shutterControlService;
 
     public ShutterControlHandler(Thing thing) {
index 2759c5510fa5a6c29b3e2b59411595e487c803fc..a647b1d6a857a1b95c5a440ba812402ff597e9a0 100644 (file)
  */
 package org.openhab.binding.boschshc.internal.discovery;
 
-import java.util.*;
+import java.util.AbstractMap;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
@@ -139,9 +143,9 @@ public class ThingDiscoveryService extends AbstractDiscoveryService implements T
 
     @Override
     public void setThingHandler(@Nullable ThingHandler handler) {
-        if (handler instanceof BridgeHandler) {
+        if (handler instanceof BridgeHandler bridgeHandler) {
             logger.trace("Set bridge handler {}", handler);
-            shcBridgeHandler = (BridgeHandler) handler;
+            shcBridgeHandler = bridgeHandler;
         }
     }
 
@@ -215,8 +219,9 @@ public class ThingDiscoveryService extends AbstractDiscoveryService implements T
     }
 
     private String getNiceName(String name, String roomName) {
-        if (!name.startsWith("-"))
+        if (!name.startsWith("-")) {
             return name;
+        }
 
         // convert "-IntrusionDetectionSystem-" into "Intrusion Detection System"
         // convert "-RoomClimateControl-" into "Room Climate Control myRoomName"
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/GsonUtils.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/GsonUtils.java
new file mode 100644 (file)
index 0000000..efa652a
--- /dev/null
@@ -0,0 +1,40 @@
+/**
+ * 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.binding.boschshc.internal.serialization;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * Utilities for JSON serialization and deserialization using Google Gson.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public final class GsonUtils {
+    private GsonUtils() {
+        // Utility Class
+    }
+
+    /**
+     * The default Gson instance to be used for serialization and deserialization.
+     * <p>
+     * This instance does not serialize or deserialize fields named <code>logger</code>.
+     */
+    public static final Gson DEFAULT_GSON_INSTANCE = new GsonBuilder()
+            .addSerializationExclusionStrategy(new LoggerExclusionStrategy())
+            .addDeserializationExclusionStrategy(new LoggerExclusionStrategy()).create();
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/LoggerExclusionStrategy.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/LoggerExclusionStrategy.java
new file mode 100644 (file)
index 0000000..88e8824
--- /dev/null
@@ -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.binding.boschshc.internal.serialization;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+
+/**
+ * A GSON exclusion strategy that prevents loggers from being serialized and deserialized.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class LoggerExclusionStrategy implements ExclusionStrategy {
+
+    @Override
+    public boolean shouldSkipField(@NonNullByDefault({}) FieldAttributes f) {
+        return "logger".equalsIgnoreCase(f.getName());
+    }
+
+    @Override
+    public boolean shouldSkipClass(@NonNullByDefault({}) Class<?> clazz) {
+        return false;
+    }
+}
index 71a02181dfc53bdb9c4d2e5ea576d6419a862d60..21498e6fa4ab8379f3e5ba0e43b71ac3d25e09d8 100644 (file)
@@ -27,23 +27,23 @@ import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
  * <p>
  * The endpoints to retrieve system states are different from the ones for physical devices, i.e. they do not follow the
  * pattern
- * 
+ *
  * <pre>
  * https://{IP}:8444/smarthome/devices/{deviceId}/services/{serviceName}/state
  * </pre>
- * 
+ *
  * Instead, system services have endpoints like
- * 
+ *
  * <pre>
  * /intrusion/states/system
  * </pre>
- * 
+ *
  * <p>
  * The services of the devices and their official APIs can be found
  * <a href="https://apidocs.bosch-smarthome.com/local/">here</a>.
  *
  * @param <TState> type used for representing the service state
- * 
+ *
  * @author David Pace - Initial contribution
  */
 @NonNullByDefault
@@ -53,7 +53,7 @@ public abstract class BoschSHCSystemService<TState extends BoschSHCServiceState>
 
     /**
      * Constructs a system service instance.
-     * 
+     *
      * @param serviceName name of the service, such as <code>intrusionDetectionService</code>
      * @param stateClass the class representing states of the system
      * @param endpoint the part of the URL after <code>https://{IP}:8444/smarthome/</code>, e.g.
index ffe23619dc425fea356e5e4f01d24b6d6064ea2a..488356b30534f159ecd89d88a53e52bcce2317b9 100644 (file)
 package org.openhab.binding.boschshc.internal.services.dto;
 
 import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.gson.Gson;
 import com.google.gson.JsonElement;
 import com.google.gson.annotations.SerializedName;
 
@@ -27,15 +27,7 @@ import com.google.gson.annotations.SerializedName;
  */
 public class BoschSHCServiceState {
 
-    /**
-     * gson instance to convert a class to json string and back.
-     */
-    private static final Gson GSON = new Gson();
-
-    /**
-     * Logger marked as transient to exclude the logger from JSON serialization.
-     */
-    private final transient Logger logger = LoggerFactory.getLogger(BoschSHCServiceState.class);
+    private final Logger logger = LoggerFactory.getLogger(getClass());
 
     /**
      * State type. Initialized when instance is created.
@@ -70,7 +62,7 @@ public class BoschSHCServiceState {
 
     public static <TState extends BoschSHCServiceState> @Nullable TState fromJson(String json,
             Class<TState> stateClass) {
-        var state = GSON.fromJson(json, stateClass);
+        var state = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, stateClass);
         if (state == null || !state.isValid()) {
             return null;
         }
@@ -80,7 +72,7 @@ public class BoschSHCServiceState {
 
     public static <TState extends BoschSHCServiceState> @Nullable TState fromJson(JsonElement json,
             Class<TState> stateClass) {
-        var state = GSON.fromJson(json, stateClass);
+        var state = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, stateClass);
         if (state == null || !state.isValid()) {
             return null;
         }
index d931211f62b4d53e9162f86bf5c506b8b4e2356b..b79408e17e41bc1e940c36f98444074041e0a346 100644 (file)
@@ -14,7 +14,9 @@ package org.openhab.binding.boschshc.internal.services.humiditylevel.dto;
 
 import javax.measure.quantity.Dimensionless;
 
+import org.eclipse.jdt.annotation.NonNull;
 import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
+import org.openhab.binding.boschshc.internal.services.humiditylevel.HumidityLevelService;
 import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.unit.Units;
 import org.openhab.core.types.State;
@@ -36,6 +38,6 @@ public class HumidityLevelServiceState extends BoschSHCServiceState {
     public double humidity;
 
     public State getHumidityState() {
-        return new QuantityType<Dimensionless>(this.humidity, Units.PERCENT);
+        return new QuantityType<@NonNull Dimensionless>(this.humidity, Units.PERCENT);
     }
 }
index 525d8c7b83b8226bcb951403d5065353a5a67350..f4b5344931cf6a79a1b86cfc63f9ec341eddf0c7 100644 (file)
@@ -14,37 +14,39 @@ package org.openhab.binding.boschshc.internal.services.roomclimatecontrol.dto;
 
 import javax.measure.quantity.Temperature;
 
+import org.eclipse.jdt.annotation.NonNull;
 import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
+import org.openhab.binding.boschshc.internal.services.roomclimatecontrol.RoomClimateControlService;
 import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.unit.SIUnits;
 import org.openhab.core.types.State;
 
 /**
  * State for {@link RoomClimateControlService} to get and set the desired temperature of a room.
- * 
+ *
  * @author Christian Oeing - Initial contribution
  */
 public class RoomClimateControlServiceState extends BoschSHCServiceState {
 
-    private static final String TYPE = "climateControlState";
+    private static final String CLIMATE_CONTROL_STATE_TYPE = "climateControlState";
 
     public RoomClimateControlServiceState() {
-        super(TYPE);
+        super(CLIMATE_CONTROL_STATE_TYPE);
     }
 
     /**
      * Constructor.
-     * 
+     *
      * @param setpointTemperature Desired temperature (in degree celsius).
      */
     public RoomClimateControlServiceState(double setpointTemperature) {
-        super(TYPE);
+        super(CLIMATE_CONTROL_STATE_TYPE);
         this.setpointTemperature = setpointTemperature;
     }
 
     /**
      * Desired temperature (in degree celsius).
-     * 
+     *
      * @apiNote Min: 5.0, Max: 30.0.
      * @apiNote Can be set in 0.5 steps.
      */
@@ -52,10 +54,10 @@ public class RoomClimateControlServiceState extends BoschSHCServiceState {
 
     /**
      * Desired temperature state to set for a thing.
-     * 
+     *
      * @return Desired temperature state to set for a thing.
      */
     public State getSetpointTemperatureState() {
-        return new QuantityType<Temperature>(this.setpointTemperature, SIUnits.CELSIUS);
+        return new QuantityType<@NonNull Temperature>(this.setpointTemperature, SIUnits.CELSIUS);
     }
 }
index a5f5a003521c4fc17aed949e088d81951fbe689c..bd29d00f538d3d2a09426da8efec893b55035bee 100644 (file)
@@ -14,6 +14,7 @@ package org.openhab.binding.boschshc.internal.services.temperaturelevel.dto;
 
 import javax.measure.quantity.Temperature;
 
+import org.eclipse.jdt.annotation.NonNull;
 import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
 import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.unit.SIUnits;
@@ -21,7 +22,7 @@ import org.openhab.core.types.State;
 
 /**
  * TemperatureLevel service state.
- * 
+ *
  * @author Christian Oeing - Initial contribution
  */
 public class TemperatureLevelServiceState extends BoschSHCServiceState {
@@ -37,10 +38,10 @@ public class TemperatureLevelServiceState extends BoschSHCServiceState {
 
     /**
      * Current temperature state to set for a thing.
-     * 
+     *
      * @return Current temperature state to use for a thing.
      */
     public State getTemperatureState() {
-        return new QuantityType<Temperature>(this.temperature, SIUnits.CELSIUS);
+        return new QuantityType<@NonNull Temperature>(this.temperature, SIUnits.CELSIUS);
     }
 }
index 1f77e17416b2bce5e9e7cf6477f898fdaf8d6414..a6be12174ecba3f9c392b4cd7ac64bcecea9b3c9 100644 (file)
@@ -27,6 +27,7 @@ import java.util.concurrent.TimeoutException;
 import java.util.function.BiFunction;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
 import org.eclipse.jetty.client.api.ContentResponse;
 import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.http.HttpMethod;
@@ -91,7 +92,7 @@ class BridgeHandlerTest {
         fixture.setCallback(thingHandlerCallback);
 
         Configuration bridgeConfiguration = new Configuration();
-        Map<String, Object> properties = new HashMap<>();
+        Map<@Nullable String, @Nullable Object> properties = new HashMap<>();
         properties.put("ipAddress", "localhost");
         properties.put("password", "test");
         bridgeConfiguration.setProperties(properties);
index fb8a3e3c248172d5eb42fd788116c6bf37863113..71bc6e3a23d649d924b9376ad41a75e261b0452e 100644 (file)
@@ -16,8 +16,7 @@ import static org.junit.jupiter.api.Assertions.*;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.api.Test;
-
-import com.google.gson.Gson;
+import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
 
 /**
  * Unit tests for LongPollResult
@@ -26,11 +25,10 @@ import com.google.gson.Gson;
  */
 @NonNullByDefault
 public class LongPollResultTest {
-    private final Gson gson = new Gson();
 
     @Test
     void noResultsForErrorResult() {
-        LongPollResult longPollResult = gson.fromJson(
+        LongPollResult longPollResult = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(
                 "{\"jsonrpc\":\"2.0\", \"error\": { \"code\":-32001, \"message\":\"No subscription with id: e8fei62b0-0\" } }",
                 LongPollResult.class);
         assertNotNull(longPollResult);
index 04b3c328cd0087a6ea102ac653443882f44607ee..579e8da3c7bba3debe457eb3c794b591e567f3b9 100644 (file)
@@ -15,8 +15,10 @@ package org.openhab.binding.boschshc.internal.services.batterylevel;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.util.ArrayList;
+import java.util.List;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
 import org.junit.jupiter.api.Test;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Fault;
@@ -58,7 +60,7 @@ class BatteryLevelTest {
         deviceServiceData.faults = faults;
         assertSame(BatteryLevel.OK, BatteryLevel.fromDeviceServiceData(deviceServiceData));
 
-        ArrayList<Fault> entries = new ArrayList<>();
+        List<@Nullable Fault> entries = new ArrayList<>();
         faults.entries = entries;
         assertSame(BatteryLevel.OK, BatteryLevel.fromDeviceServiceData(deviceServiceData));
 
index dd780413596e0c048e4a634bab1806b0c5e4356f..f0f1a29a5fb22db81f6d95a54eb2dbcfac9e995c 100644 (file)
@@ -16,8 +16,8 @@ import static org.junit.jupiter.api.Assertions.*;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.api.Test;
+import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
 
-import com.google.gson.Gson;
 import com.google.gson.JsonObject;
 
 /**
@@ -49,18 +49,19 @@ class TestState2 extends BoschSHCServiceState {
  */
 @NonNullByDefault
 public class BoschSHCServiceStateTest {
-    private final Gson gson = new Gson();
 
     @Test
     public void fromJsonNullStateForDifferentType() {
-        var state = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"differentState\"}", JsonObject.class),
+        var state = BoschSHCServiceState.fromJson(
+                GsonUtils.DEFAULT_GSON_INSTANCE.fromJson("{\"@type\":\"differentState\"}", JsonObject.class),
                 TestState.class);
         assertEquals(null, state);
     }
 
     @Test
     public void fromJsonStateObjectForValidJson() {
-        var state = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState\"}", JsonObject.class),
+        var state = BoschSHCServiceState.fromJson(
+                GsonUtils.DEFAULT_GSON_INSTANCE.fromJson("{\"@type\":\"testState\"}", JsonObject.class),
                 TestState.class);
         assertNotEquals(null, state);
     }
@@ -70,8 +71,11 @@ public class BoschSHCServiceStateTest {
      */
     @Test
     public void fromJsonStateObjectForValidJsonAfterOtherState() {
-        BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState\"}", JsonObject.class), TestState.class);
-        var state2 = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState2\"}", JsonObject.class),
+        BoschSHCServiceState.fromJson(
+                GsonUtils.DEFAULT_GSON_INSTANCE.fromJson("{\"@type\":\"testState\"}", JsonObject.class),
+                TestState.class);
+        var state2 = BoschSHCServiceState.fromJson(
+                GsonUtils.DEFAULT_GSON_INSTANCE.fromJson("{\"@type\":\"testState2\"}", JsonObject.class),
                 TestState2.class);
         assertNotEquals(null, state2);
     }