[boschshc] Code Enhancements ()

* [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>
This commit is contained in:
David Pace 2023-03-30 18:31:54 +02:00 committed by GitHub
parent b6004a77de
commit ed20ebb41b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 219 additions and 131 deletions

@ -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);
}

@ -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));
}
/**

@ -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);

@ -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,

@ -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 {
}

@ -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) {

@ -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) {

@ -12,7 +12,11 @@
*/
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"

@ -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();
}

@ -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;
}
}

@ -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.

@ -13,10 +13,10 @@
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;
}

@ -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);
}
}

@ -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);
}
}

@ -16,7 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Possible states for a smoke detector.
*
*
* @author Christian Oeing - Initial contribution
*/
@NonNullByDefault

@ -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);
}
}

@ -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);

@ -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);

@ -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));

@ -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);
}