From db86d291dab319bfd7449cf2b823690d38d56a74 Mon Sep 17 00:00:00 2001 From: lsiepel Date: Sat, 10 Dec 2022 09:42:09 +0100 Subject: [PATCH] [bosesoundtouch] Improve SAT errors and remove dependency (#13842) Signed-off-by: Leo Siepel --- .../bosesoundtouch/internal/APIRequest.java | 4 + .../internal/AvailableSources.java | 4 + .../BoseSoundTouchBindingConstants.java | 2 + .../internal/BoseSoundTouchConfiguration.java | 11 +- .../BoseSoundTouchHandlerFactory.java | 26 +- .../BoseSoundTouchNotFoundException.java | 3 + ...TouchNotificationChannelConfiguration.java | 14 +- .../internal/CommandExecutor.java | 89 +++---- .../bosesoundtouch/internal/ContentItem.java | 128 +++++----- .../internal/ContentItemMaker.java | 3 + .../ContentItemNotPresetableException.java | 3 + .../NoInternetRadioPresetFoundException.java | 3 + .../internal/NoPresetFoundException.java | 3 + .../NoStoredMusicPresetFoundException.java | 3 + .../OperationModeNotAvailableException.java | 3 + .../internal/OperationModeType.java | 3 + .../internal/PresetContainer.java | 17 +- .../internal/RemoteKeyType.java | 3 + .../internal/XMLHandlerState.java | 3 + .../internal/XMLResponseHandler.java | 232 ++++++++++-------- .../internal/XMLResponseProcessor.java | 18 +- .../internal/discovery/DiscoveryUtil.java | 8 +- .../SoundTouchDiscoveryParticipant.java | 55 +++-- .../handler/BoseSoundTouchHandler.java | 181 ++++++++------ 24 files changed, 473 insertions(+), 346 deletions(-) diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/APIRequest.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/APIRequest.java index 246ca994f..9913ed463 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/APIRequest.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/APIRequest.java @@ -12,11 +12,15 @@ */ package org.openhab.binding.bosesoundtouch.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link APIRequest} class handles the API requests * * @author Thomas Traunbauer - Initial contribution */ + +@NonNullByDefault public enum APIRequest { KEY("key"), SELECT("select"), diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/AvailableSources.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/AvailableSources.java index c90069c45..8b4625fea 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/AvailableSources.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/AvailableSources.java @@ -12,11 +12,15 @@ */ package org.openhab.binding.bosesoundtouch.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link AvailableSources} is used to find out, which sources and functions are available * * @author Thomas Traunbauer - Initial contribution */ + +@NonNullByDefault public interface AvailableSources { public boolean isBluetoothAvailable(); diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchBindingConstants.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchBindingConstants.java index b4fb6cbb6..0bb236eb6 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchBindingConstants.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchBindingConstants.java @@ -19,6 +19,7 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.thing.ThingTypeUID; /** @@ -28,6 +29,7 @@ import org.openhab.core.thing.ThingTypeUID; * @author Christian Niessner - Initial contribution * @author Thomas Traunbauer - Initial contribution */ +@NonNullByDefault public class BoseSoundTouchBindingConstants { public static final String BINDING_ID = "bosesoundtouch"; diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchConfiguration.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchConfiguration.java index 169a96aa2..e7450fa61 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchConfiguration.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchConfiguration.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.bosesoundtouch.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.thing.Thing; /** @@ -19,6 +21,7 @@ import org.openhab.core.thing.Thing; * * @author Ivaylo Ivanov - Initial contribution */ +@NonNullByDefault public class BoseSoundTouchConfiguration { // Device configuration parameters; @@ -26,10 +29,10 @@ public class BoseSoundTouchConfiguration { public static final String MAC_ADDRESS = Thing.PROPERTY_MAC_ADDRESS; public static final String APP_KEY = "appKey"; - public String host; - public String macAddress; - public String appKey; + public @Nullable String host; + public @Nullable String macAddress; + public @Nullable String appKey; // Not an actual configuration field, but it will contain the name of the group (in case of Stereo Pair) - public String groupName; + public String groupName = ""; } diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchHandlerFactory.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchHandlerFactory.java index 0ffd28c3a..47544dc2f 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchHandlerFactory.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchHandlerFactory.java @@ -14,6 +14,8 @@ package org.openhab.binding.bosesoundtouch.internal; import static org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchBindingConstants.SUPPORTED_THING_TYPES_UIDS; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler; import org.openhab.core.storage.Storage; import org.openhab.core.storage.StorageService; @@ -31,11 +33,12 @@ import org.osgi.service.component.annotations.Reference; * * @author Christian Niessner - Initial contribution */ +@NonNullByDefault @Component(service = ThingHandlerFactory.class, configurationPid = "binding.bosesoundtouch") public class BoseSoundTouchHandlerFactory extends BaseThingHandlerFactory { - private StorageService storageService; - private BoseStateDescriptionOptionProvider stateOptionProvider; + private @Nullable StorageService storageService; + private @Nullable BoseStateDescriptionOptionProvider stateOptionProvider; @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { @@ -43,12 +46,19 @@ public class BoseSoundTouchHandlerFactory extends BaseThingHandlerFactory { } @Override - protected ThingHandler createHandler(Thing thing) { - Storage storage = storageService.getStorage(thing.getUID().toString(), - ContentItem.class.getClassLoader()); - BoseSoundTouchHandler handler = new BoseSoundTouchHandler(thing, new PresetContainer(storage), - stateOptionProvider); - return handler; + protected @Nullable ThingHandler createHandler(Thing thing) { + StorageService localStorageService = storageService; + if (localStorageService != null) { + Storage storage = localStorageService.getStorage(thing.getUID().toString(), + ContentItem.class.getClassLoader()); + BoseStateDescriptionOptionProvider localDescriptionOptionProvider = stateOptionProvider; + if (localDescriptionOptionProvider != null) { + BoseSoundTouchHandler handler = new BoseSoundTouchHandler(thing, new PresetContainer(storage), + localDescriptionOptionProvider); + return handler; + } + } + return null; } @Reference diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchNotFoundException.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchNotFoundException.java index bbe3f3593..40c580a40 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchNotFoundException.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchNotFoundException.java @@ -12,11 +12,14 @@ */ package org.openhab.binding.bosesoundtouch.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link BoseSoundTouchNotFoundException} class is an exception * * @author Thomas Traunbauer - Initial contribution */ +@NonNullByDefault public class BoseSoundTouchNotFoundException extends Exception { private static final long serialVersionUID = 1L; diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchNotificationChannelConfiguration.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchNotificationChannelConfiguration.java index bb12d39a7..5fe3552b6 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchNotificationChannelConfiguration.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchNotificationChannelConfiguration.java @@ -12,11 +12,15 @@ */ package org.openhab.binding.bosesoundtouch.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + /** * Configuration class for soundtouch notification channel * * @author Ivaylo Ivanov - Initial contribution */ +@NonNullByDefault public class BoseSoundTouchNotificationChannelConfiguration { public static final String MIN_FIRMWARE = "14"; @@ -27,13 +31,13 @@ public class BoseSoundTouchNotificationChannelConfiguration { public static final String NOTIFICATION_REASON = "notificationReason"; public static final String NOTIFICATION_MESSAGE = "notificationMessage"; - public Integer notificationVolume; - public String notificationService; - public String notificationReason; - public String notificationMessage; + public @Nullable Integer notificationVolume; + public @Nullable String notificationService; + public @Nullable String notificationReason; + public @Nullable String notificationMessage; public static boolean isSupportedFirmware(String firmware) { - return firmware != null && firmware.compareTo(MIN_FIRMWARE) > 0; + return firmware.compareTo(MIN_FIRMWARE) > 0; } public static boolean isSupportedHardware(String hardware) { diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/CommandExecutor.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/CommandExecutor.java index d06909075..7a01af606 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/CommandExecutor.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/CommandExecutor.java @@ -18,6 +18,9 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.websocket.api.Session; import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.NextPreviousType; @@ -36,16 +39,17 @@ import org.slf4j.LoggerFactory; * @author Thomas Traunbauer - Initial contribution * @author Kai Kreuzer - code clean up */ +@NonNullByDefault public class CommandExecutor implements AvailableSources { private final Logger logger = LoggerFactory.getLogger(CommandExecutor.class); private final BoseSoundTouchHandler handler; private boolean currentMuted; - private ContentItem currentContentItem; - private OperationModeType currentOperationMode; + private @Nullable ContentItem currentContentItem = null; + private @Nullable OperationModeType currentOperationMode; - private Map mapOfAvailableFunctions; + private final Map mapOfAvailableFunctions = new HashMap<>(); /** * Creates a new instance of this class @@ -54,7 +58,8 @@ public class CommandExecutor implements AvailableSources { */ public CommandExecutor(BoseSoundTouchHandler handler) { this.handler = handler; - init(); + getInformations(APIRequest.INFO); + currentOperationMode = OperationModeType.OFFLINE; } /** @@ -66,11 +71,7 @@ public class CommandExecutor implements AvailableSources { public void updatePresetContainerFromPlayer(Map playerPresets) { playerPresets.forEach((k, v) -> { try { - if (v != null) { - handler.getPresetContainer().put(k, v); - } else { - handler.getPresetContainer().remove(k); - } + handler.getPresetContainer().put(k, v); } catch (ContentItemNotPresetableException e) { logger.debug("{}: ContentItem is not presetable", handler.getDeviceName()); } @@ -104,7 +105,10 @@ public class CommandExecutor implements AvailableSources { */ public void addCurrentContentItemToPresetContainer(DecimalType command) { if (command.intValue() > 6) { - addContentItemToPresetContainer(command.intValue(), currentContentItem); + ContentItem localContentItem = currentContentItem; + if (localContentItem != null) { + addContentItemToPresetContainer(command.intValue(), localContentItem); + } } else { logger.warn("{}: Only PresetID >6 is allowed", handler.getDeviceName()); } @@ -118,8 +122,11 @@ public class CommandExecutor implements AvailableSources { public void getInformations(APIRequest apiRequest) { String msg = "
"; - handler.getSession().getRemote().sendStringByFuture(msg); - logger.debug("{}: sending request: {}", handler.getDeviceName(), msg); + Session localSession = handler.getSession(); + if (localSession != null) { + localSession.getRemote().sendStringByFuture(msg); + logger.debug("{}: sending request: {}", handler.getDeviceName(), msg); + } } /** @@ -128,25 +135,27 @@ public class CommandExecutor implements AvailableSources { * @param contentItem */ public void setCurrentContentItem(ContentItem contentItem) { - if ((contentItem != null) && (contentItem.isValid())) { + if (contentItem.isValid()) { ContentItem psFound = null; - if (handler.getPresetContainer() != null) { - Collection listOfPresets = handler.getPresetContainer().getAllPresets(); - for (ContentItem ps : listOfPresets) { - if (ps.isPresetable()) { - if (ps.getLocation().equals(contentItem.getLocation())) { + Collection listOfPresets = handler.getPresetContainer().getAllPresets(); + for (ContentItem ps : listOfPresets) { + if (ps.isPresetable()) { + String localLocation = ps.getLocation(); + if (localLocation != null) { + if (localLocation.equals(contentItem.getLocation())) { psFound = ps; } } } - int presetID = 0; - if (psFound != null) { - presetID = psFound.getPresetID(); - } - contentItem.setPresetID(presetID); - - currentContentItem = contentItem; } + int presetID = 0; + if (psFound != null) { + presetID = psFound.getPresetID(); + } + contentItem.setPresetID(presetID); + + currentContentItem = contentItem; + } updateOperatingValues(); } @@ -350,19 +359,9 @@ public class CommandExecutor implements AvailableSources { handler.updateState(CHANNEL_PRESET, state); } - private void init() { - getInformations(APIRequest.INFO); - currentOperationMode = OperationModeType.OFFLINE; - currentContentItem = null; - - mapOfAvailableFunctions = new HashMap<>(); - } - private void postContentItem(ContentItem contentItem) { - if (contentItem != null) { - setCurrentContentItem(contentItem); - sendPostRequestInWebSocket("select", "", contentItem.generateXML()); - } + setCurrentContentItem(contentItem); + sendPostRequestInWebSocket("select", "", contentItem.generateXML()); } private void sendPostRequestInWebSocket(String url, String postData) { @@ -374,19 +373,21 @@ public class CommandExecutor implements AvailableSources { String msg = "
" + postData + "
"; - try { - handler.getSession().getRemote().sendStringByFuture(msg); + Session localSession = handler.getSession(); + if (localSession != null) { + localSession.getRemote().sendStringByFuture(msg); logger.debug("{}: sending request: {}", handler.getDeviceName(), msg); - } catch (NullPointerException e) { - handler.onWebSocketError(e); + } else { + handler.onWebSocketError(new NullPointerException("NPE: Session is unexpected null")); } } private void updateOperatingValues() { OperationModeType operationMode; - if (currentContentItem != null) { - updatePresetGUIState(new DecimalType(currentContentItem.getPresetID())); - operationMode = currentContentItem.getOperationMode(); + ContentItem localContentItem = currentContentItem; + if (localContentItem != null) { + updatePresetGUIState(new DecimalType(localContentItem.getPresetID())); + operationMode = localContentItem.getOperationMode(); } else { operationMode = OperationModeType.STANDBY; } diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/ContentItem.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/ContentItem.java index 933c3b051..348eb7c67 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/ContentItem.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/ContentItem.java @@ -16,7 +16,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.apache.commons.lang3.StringEscapeUtils; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.types.StateOption; import com.google.gson.annotations.Expose; @@ -27,31 +28,18 @@ import com.google.gson.annotations.Expose; * @author Christian Niessner - Initial contribution * @author Thomas Traunbauer - Initial contribution */ +@NonNullByDefault public class ContentItem { - private String source; - private String sourceAccount; - private String location; - private boolean presetable; - private String itemName; - private int presetID; - private String containerArt; + private String source = ""; + private @Nullable String sourceAccount; + private @Nullable String location; + private boolean presetable = false; + private @Nullable String itemName; + private int presetID = 0; + private @Nullable String containerArt; @Expose - private final Map additionalAttributes; - - /** - * Creates a new instance of this class - */ - public ContentItem() { - source = ""; - sourceAccount = null; - location = null; - presetable = false; - itemName = null; - presetID = 0; - containerArt = null; - additionalAttributes = new HashMap<>(); - } + private final Map additionalAttributes = new HashMap<>(); /** * Returns true if this ContentItem is defined as Preset @@ -74,11 +62,13 @@ public class ContentItem { public boolean isValid() { if (getOperationMode() == OperationModeType.STANDBY) { return true; - } - if (itemName == null || source == null || itemName.isEmpty() || source.isEmpty()) { - return false; } else { - return true; + String localItemName = itemName; + if (localItemName != null) { + return !(localItemName.isEmpty() || source.isEmpty()); + } else { + return false; + } } } @@ -88,25 +78,12 @@ public class ContentItem { * @return true if source, sourceAccount, location, itemName, and presetable are equal */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof ContentItem) { ContentItem other = (ContentItem) obj; - if (!Objects.equals(other.source, this.source)) { - return false; - } - if (!Objects.equals(other.sourceAccount, this.sourceAccount)) { - return false; - } - if (other.presetable != this.presetable) { - return false; - } - if (!Objects.equals(other.location, this.location)) { - return false; - } - if (!Objects.equals(other.itemName, this.itemName)) { - return false; - } - return true; + return Objects.equals(other.source, this.source) || Objects.equals(other.sourceAccount, this.sourceAccount) + || other.presetable == this.presetable || Objects.equals(other.location, this.location) + || Objects.equals(other.itemName, this.itemName); } return super.equals(obj); } @@ -118,15 +95,18 @@ public class ContentItem { */ public OperationModeType getOperationMode() { OperationModeType operationMode = OperationModeType.OTHER; - if (source == null || source.equals("")) { + if ("".equals(source)) { return OperationModeType.OTHER; } if (source.contains("PRODUCT")) { - if (sourceAccount.contains("TV")) { - operationMode = OperationModeType.TV; - } - if (sourceAccount.contains("HDMI")) { - operationMode = OperationModeType.HDMI1; + String localSourceAccount = sourceAccount; + if (localSourceAccount != null) { + if (localSourceAccount.contains("TV")) { + operationMode = OperationModeType.TV; + } + if (localSourceAccount.contains("HDMI")) { + operationMode = OperationModeType.HDMI1; + } } return operationMode; } @@ -174,15 +154,15 @@ public class ContentItem { return source; } - public String getSourceAccount() { + public @Nullable String getSourceAccount() { return sourceAccount; } - public String getLocation() { + public @Nullable String getLocation() { return location; } - public String getItemName() { + public @Nullable String getItemName() { return itemName; } @@ -194,10 +174,28 @@ public class ContentItem { return presetID; } - public String getContainerArt() { + public @Nullable String getContainerArt() { return containerArt; } + /** + * Simple method to escape XML special characters in String. + * There are five XML Special characters which needs to be escaped : + * & - & + * < - < + * > - > + * " - " + * ' - ' + */ + private String escapeXml(String xml) { + xml = xml.replaceAll("&", "&"); + xml = xml.replaceAll("<", "<"); + xml = xml.replaceAll(">", ">"); + xml = xml.replaceAll("\"", """); + xml = xml.replaceAll("'", "'"); + return xml; + } + /** * Returns the XML Code that is needed to switch to this ContentItem * @@ -223,19 +221,20 @@ public class ContentItem { break; default: StringBuilder sbXml = new StringBuilder(" aae : additionalAttributes.entrySet()) { - sbXml.append(" ").append(aae.getKey()).append("=\"") - .append(StringEscapeUtils.escapeXml(aae.getValue())).append("\""); + sbXml.append(" ").append(aae.getKey()).append("=\"").append(escapeXml(aae.getValue())).append("\""); } sbXml.append(">"); if (itemName != null) { @@ -264,6 +263,7 @@ public class ContentItem { // buffer.append(presetID); // return buffer.toString(); // } - return itemName; + String localString = itemName; + return (localString != null) ? localString : ""; } } diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/ContentItemMaker.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/ContentItemMaker.java index 05fe9b562..8d341f154 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/ContentItemMaker.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/ContentItemMaker.java @@ -14,11 +14,14 @@ package org.openhab.binding.bosesoundtouch.internal; import java.util.Collection; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link ContentItemMaker} class makes ContentItems for sources * * @author Thomas Traunbauer - Initial contribution */ +@NonNullByDefault public class ContentItemMaker { private final PresetContainer presetContainer; diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/ContentItemNotPresetableException.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/ContentItemNotPresetableException.java index d7c9d78ac..2d33066b9 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/ContentItemNotPresetableException.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/ContentItemNotPresetableException.java @@ -12,11 +12,14 @@ */ package org.openhab.binding.bosesoundtouch.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link ContentItemNotPresetableException} class is an exception * * @author Thomas Traunbauer - Initial contribution */ +@NonNullByDefault public class ContentItemNotPresetableException extends NoPresetFoundException { private static final long serialVersionUID = 1L; diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/NoInternetRadioPresetFoundException.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/NoInternetRadioPresetFoundException.java index 6f3422aea..56f08c565 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/NoInternetRadioPresetFoundException.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/NoInternetRadioPresetFoundException.java @@ -12,11 +12,14 @@ */ package org.openhab.binding.bosesoundtouch.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link NoInternetRadioPresetFoundException} class is an exception * * @author Thomas Traunbauer - Initial contribution */ +@NonNullByDefault public class NoInternetRadioPresetFoundException extends NoPresetFoundException { private static final long serialVersionUID = 1L; diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/NoPresetFoundException.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/NoPresetFoundException.java index 2406a6633..f7b72a8d4 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/NoPresetFoundException.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/NoPresetFoundException.java @@ -12,11 +12,14 @@ */ package org.openhab.binding.bosesoundtouch.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link NoPresetFoundException} class is an exception * * @author Thomas Traunbauer - Initial contribution */ +@NonNullByDefault public class NoPresetFoundException extends Exception { private static final long serialVersionUID = 1L; diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/NoStoredMusicPresetFoundException.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/NoStoredMusicPresetFoundException.java index 83f85e726..21e1f5dc5 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/NoStoredMusicPresetFoundException.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/NoStoredMusicPresetFoundException.java @@ -12,11 +12,14 @@ */ package org.openhab.binding.bosesoundtouch.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link NoStoredMusicPresetFoundException} class is an exception * * @author Thomas Traunbauer - Initial contribution */ +@NonNullByDefault public class NoStoredMusicPresetFoundException extends NoPresetFoundException { private static final long serialVersionUID = 1L; diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/OperationModeNotAvailableException.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/OperationModeNotAvailableException.java index 8221973c4..976edd21f 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/OperationModeNotAvailableException.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/OperationModeNotAvailableException.java @@ -12,11 +12,14 @@ */ package org.openhab.binding.bosesoundtouch.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link OperationModeNotAvailableException} class is an exception * * @author Thomas Traunbauer - Initial contribution */ +@NonNullByDefault public class OperationModeNotAvailableException extends Exception { private static final long serialVersionUID = 1L; diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/OperationModeType.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/OperationModeType.java index f59955a53..ba72f5ab5 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/OperationModeType.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/OperationModeType.java @@ -12,12 +12,15 @@ */ package org.openhab.binding.bosesoundtouch.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link OperationModeType} class is holding all OperationModes * * @author Christian Niessner - Initial contribution * @author Thomas Traunbauer - Initial contribution */ +@NonNullByDefault public enum OperationModeType { OFFLINE, STANDBY, diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/PresetContainer.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/PresetContainer.java index 8d7946d9f..dfc033fe2 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/PresetContainer.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/PresetContainer.java @@ -17,8 +17,11 @@ import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.storage.DeletableStorage; import org.openhab.core.storage.Storage; import org.slf4j.Logger; @@ -30,11 +33,12 @@ import org.slf4j.LoggerFactory; * @author Thomas Traunbauer - Initial contribution * @author Kai Kreuzer - Refactored it to use storage instead of file */ +@NonNullByDefault public class PresetContainer { private final Logger logger = LoggerFactory.getLogger(PresetContainer.class); - private HashMap mapOfPresets; + private final Map mapOfPresets = new HashMap<>(); private Storage storage; /** @@ -42,11 +46,6 @@ public class PresetContainer { */ public PresetContainer(Storage storage) { this.storage = storage; - init(); - } - - private void init() { - this.mapOfPresets = new HashMap<>(); readFromStorage(); } @@ -133,10 +132,12 @@ public class PresetContainer { } private void readFromStorage() { - Collection items = storage.getValues(); + Collection<@Nullable ContentItem> items = storage.getValues(); for (ContentItem item : items) { try { - put(item.getPresetID(), item); + if (item != null) { + put(item.getPresetID(), item); + } } catch (ContentItemNotPresetableException e) { logger.debug("Item '{}' is not presetable - ignoring it.", item.getItemName()); } diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/RemoteKeyType.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/RemoteKeyType.java index 89994b5c0..cca679acd 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/RemoteKeyType.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/RemoteKeyType.java @@ -12,11 +12,14 @@ */ package org.openhab.binding.bosesoundtouch.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link RemoteKeyType} class is holding the Keys on a remote. For simulating key presses * * @author Christian Niessner - Initial contribution */ +@NonNullByDefault public enum RemoteKeyType { PLAY, PAUSE, diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLHandlerState.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLHandlerState.java index 30993d9e0..6c600d996 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLHandlerState.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLHandlerState.java @@ -12,12 +12,15 @@ */ package org.openhab.binding.bosesoundtouch.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link XMLHandlerState} class defines the XML States provided from Bose Soundtouch * * @author Christian Niessner - Initial contribution * @author Thomas Traunbauer - Initial contribution */ +@NonNullByDefault public enum XMLHandlerState { INIT, Msg, diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseHandler.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseHandler.java index 653ef3342..502680e58 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseHandler.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseHandler.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Objects; import java.util.Stack; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler; import org.openhab.core.io.net.http.HttpUtil; import org.openhab.core.library.types.DecimalType; @@ -54,8 +55,8 @@ public class XMLResponseHandler extends DefaultHandler { private Map> stateSwitchingMap; - private Stack states; - private XMLHandlerState state; + private final Stack states = new Stack<>(); + private XMLHandlerState state = XMLHandlerState.INIT; private boolean msgHeaderWasValid; private ContentItem contentItem; @@ -63,10 +64,10 @@ public class XMLResponseHandler extends DefaultHandler { private OnOffType rateEnabled; private OnOffType skipEnabled; private OnOffType skipPreviousEnabled; - private State nowPlayingSource; private BoseSoundTouchConfiguration masterDeviceId; + String deviceId; private Map playerPresets; @@ -82,11 +83,11 @@ public class XMLResponseHandler extends DefaultHandler { this.handler = handler; this.commandExecutor = handler.getCommandExecutor(); this.stateSwitchingMap = stateSwitchingMap; - init(); } @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + public void startElement(@Nullable String uri, @Nullable String localName, @Nullable String qName, + @Nullable Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); logger.trace("{}: startElement('{}'; state: {})", handler.getDeviceName(), localName, state); states.push(state); @@ -94,7 +95,13 @@ public class XMLResponseHandler extends DefaultHandler { Map stateMap = stateSwitchingMap.get(state); state = XMLHandlerState.Unprocessed; // set default value; we avoid default in select to have the compiler // showing a - // warning for unhandled states + // warning for unhandled states + + XMLHandlerState localState = null; + if (stateMap != null) { + localState = stateMap.get(localName); + } + switch (curState) { case INIT: if ("updates".equals(localName)) { @@ -105,12 +112,9 @@ public class XMLResponseHandler extends DefaultHandler { state = XMLHandlerState.Unprocessed; } } else { - state = stateMap.get(localName); - if (state == null) { - if (logger.isDebugEnabled()) { - logger.warn("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState, - localName); - } + if (localState == null) { + logger.debug("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState, + localName); state = XMLHandlerState.Unprocessed; } } @@ -131,10 +135,9 @@ public class XMLResponseHandler extends DefaultHandler { state = XMLHandlerState.Unprocessed; } } else { - if (logger.isDebugEnabled()) { - logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState, - localName); - } + logger.debug("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState, + localName); + state = XMLHandlerState.Unprocessed; } break; @@ -142,10 +145,8 @@ public class XMLResponseHandler extends DefaultHandler { if ("request".equals(localName)) { state = XMLHandlerState.Unprocessed; // TODO implement request id / response tracking... } else { - if (logger.isDebugEnabled()) { - logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState, - localName); - } + logger.debug("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState, + localName); state = XMLHandlerState.Unprocessed; } break; @@ -161,7 +162,10 @@ public class XMLResponseHandler extends DefaultHandler { skipEnabled = OnOffType.OFF; skipPreviousEnabled = OnOffType.OFF; state = XMLHandlerState.NowPlaying; - String source = attributes.getValue("source"); + String source = ""; + if (attributes != null) { + source = attributes.getValue("source"); + } if (nowPlayingSource == null || !nowPlayingSource.toString().equals(source)) { // source changed nowPlayingSource = new StringType(source); @@ -192,14 +196,11 @@ public class XMLResponseHandler extends DefaultHandler { state = XMLHandlerState.Presets; } else if ("group".equals(localName)) { this.masterDeviceId = new BoseSoundTouchConfiguration(); - state = stateMap.get(localName); } else { - state = stateMap.get(localName); - if (state == null) { - if (logger.isDebugEnabled()) { - logger.warn("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState, - localName); - } + if (localState == null) { + logger.debug("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState, + localName); + state = XMLHandlerState.Unprocessed; } else if (state != XMLHandlerState.Volume && state != XMLHandlerState.Presets && state != XMLHandlerState.Group && state != XMLHandlerState.Unprocessed) { @@ -213,63 +214,78 @@ public class XMLResponseHandler extends DefaultHandler { case Presets: if ("preset".equals(localName)) { state = XMLHandlerState.Preset; - String id = attributes.getValue("id"); + String id = "0"; + if (attributes != null) { + id = attributes.getValue("id"); + } if (contentItem == null) { contentItem = new ContentItem(); } contentItem.setPresetID(Integer.parseInt(id)); } else { - if (logger.isDebugEnabled()) { - logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState, - localName); - } + logger.debug("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState, + localName); + state = XMLHandlerState.Unprocessed; } break; case Sources: if ("sourceItem".equals(localName)) { state = XMLHandlerState.Unprocessed; - String source = attributes.getValue("source"); - String sourceAccount = attributes.getValue("sourceAccount"); - String status = attributes.getValue("status"); - if (status.equals("READY")) { - if (source.equals("AUX")) { - if (sourceAccount.equals("AUX")) { - commandExecutor.setAUXAvailable(true); - } - if (sourceAccount.equals("AUX1")) { - commandExecutor.setAUX1Available(true); - } - if (sourceAccount.equals("AUX2")) { - commandExecutor.setAUX2Available(true); - } - if (sourceAccount.equals("AUX3")) { - commandExecutor.setAUX3Available(true); - } - } - if (source.equals("STORED_MUSIC")) { - commandExecutor.setStoredMusicAvailable(true); - } - if (source.equals("INTERNET_RADIO")) { - commandExecutor.setInternetRadioAvailable(true); - } - if (source.equals("BLUETOOTH")) { - commandExecutor.setBluetoothAvailable(true); - } - if (source.equals("PRODUCT")) { - if (sourceAccount.equals("TV")) { - commandExecutor.setTVAvailable(true); - } - if (sourceAccount.equals("HDMI_1")) { - commandExecutor.setHDMI1Available(true); - } + String source = ""; + String status = ""; + String sourceAccount = ""; + if (attributes != null) { + source = attributes.getValue("source"); + sourceAccount = attributes.getValue("sourceAccount"); + status = attributes.getValue("status"); + } + if ("READY".equals(status)) { + switch (source) { + case "AUX": + if ("AUX".equals(sourceAccount)) { + commandExecutor.setAUXAvailable(true); + } + if ("AUX1".equals(sourceAccount)) { + commandExecutor.setAUX1Available(true); + } + if ("AUX2".equals(sourceAccount)) { + commandExecutor.setAUX2Available(true); + } + if ("AUX3".equals(sourceAccount)) { + commandExecutor.setAUX3Available(true); + } + break; + case "STORED_MUSIC": + commandExecutor.setStoredMusicAvailable(true); + break; + case "INTERNET_RADIO": + commandExecutor.setInternetRadioAvailable(true); + break; + case "BLUETOOTH": + commandExecutor.setBluetoothAvailable(true); + break; + case "PRODUCT": + switch (sourceAccount) { + case "TV": + commandExecutor.setTVAvailable(true); + break; + case "HDMI_1": + commandExecutor.setHDMI1Available(true); + break; + default: + logger.debug("{}: has an unknown source account: '{}'", handler.getDeviceName(), + sourceAccount); + break; + } + default: + logger.debug("{}: has an unknown source: '{}'", handler.getDeviceName(), source); + break; } } } else { - if (logger.isDebugEnabled()) { - logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState, - localName); - } + logger.debug("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState, + localName); state = XMLHandlerState.Unprocessed; } break; @@ -350,25 +366,46 @@ public class XMLResponseHandler extends DefaultHandler { if (contentItem == null) { contentItem = new ContentItem(); } - contentItem.setSource(attributes.getValue("source")); - contentItem.setSourceAccount(attributes.getValue("sourceAccount")); - contentItem.setLocation(attributes.getValue("location")); - contentItem.setPresetable(Boolean.parseBoolean(attributes.getValue("isPresetable"))); - for (int attrId = 0; attrId < attributes.getLength(); attrId++) { - String attrName = attributes.getLocalName(attrId); - if ("source".equalsIgnoreCase(attrName)) { - continue; + String source = ""; + String location = ""; + String sourceAccount = ""; + Boolean isPresetable = false; + + if (attributes != null) { + source = attributes.getValue("source"); + sourceAccount = attributes.getValue("sourceAccount"); + location = attributes.getValue("location"); + isPresetable = Boolean.parseBoolean(attributes.getValue("isPresetable")); + + if (source != null) { + contentItem.setSource(source); } - if ("location".equalsIgnoreCase(attrName)) { - continue; + if (sourceAccount != null) { + contentItem.setSourceAccount(sourceAccount); } - if ("sourceAccount".equalsIgnoreCase(attrName)) { - continue; + if (location != null) { + contentItem.setLocation(location); } - if ("isPresetable".equalsIgnoreCase(attrName)) { - continue; + contentItem.setPresetable(isPresetable); + + for (int attrId = 0; attrId < attributes.getLength(); attrId++) { + String attrName = attributes.getLocalName(attrId); + if ("source".equalsIgnoreCase(attrName)) { + continue; + } + if ("location".equalsIgnoreCase(attrName)) { + continue; + } + if ("sourceAccount".equalsIgnoreCase(attrName)) { + continue; + } + if ("isPresetable".equalsIgnoreCase(attrName)) { + continue; + } + if (attrName != null) { + contentItem.setAdditionalAttribute(attrName, attributes.getValue(attrId)); + } } - contentItem.setAdditionalAttribute(attrName, attributes.getValue(attrId)); } } } @@ -599,8 +636,9 @@ public class XMLResponseHandler extends DefaultHandler { super.skippedEntity(name); } - private boolean checkDeviceId(String localName, Attributes attributes, boolean allowFromMaster) { - String deviceID = attributes.getValue("deviceID"); + private boolean checkDeviceId(@Nullable String localName, @Nullable Attributes attributes, + boolean allowFromMaster) { + String deviceID = (attributes != null) ? attributes.getValue("deviceID") : null; if (deviceID == null) { logger.warn("{}: No device-ID in entity {}", handler.getDeviceName(), localName); return false; @@ -613,12 +651,6 @@ public class XMLResponseHandler extends DefaultHandler { return false; } - private void init() { - states = new Stack<>(); - state = XMLHandlerState.INIT; - nowPlayingSource = null; - } - private XMLHandlerState nextState(Map stateMap, XMLHandlerState curState, String localName) { XMLHandlerState state = stateMap.get(localName); @@ -632,11 +664,13 @@ public class XMLResponseHandler extends DefaultHandler { } private void setConfigOption(String option, String value) { - Map prop = handler.getThing().getProperties(); - String cur = prop.get(option); - if (cur == null || !cur.equals(value)) { - logger.debug("{}: Option '{}' updated: From '{}' to '{}'", handler.getDeviceName(), option, cur, value); - handler.getThing().setProperty(option, value); + if (option != null) { + Map prop = handler.getThing().getProperties(); + String cur = prop.get(option); + if (cur == null || !cur.equals(value)) { + logger.debug("{}: Option '{}' updated: From '{}' to '{}'", handler.getDeviceName(), option, cur, value); + handler.getThing().setProperty(option, value); + } } } diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseProcessor.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseProcessor.java index cddcf2315..533e53821 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseProcessor.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseProcessor.java @@ -17,11 +17,15 @@ import java.io.StringReader; import java.util.HashMap; import java.util.Map; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; -import org.xml.sax.helpers.XMLReaderFactory; /** * The {@link XMLResponseProcessor} class handles the XML mapping @@ -29,18 +33,22 @@ import org.xml.sax.helpers.XMLReaderFactory; * @author Christian Niessner - Initial contribution * @author Thomas Traunbauer - Initial contribution */ + +@NonNullByDefault public class XMLResponseProcessor { private BoseSoundTouchHandler handler; - private Map> stateSwitchingMap; + private final Map> stateSwitchingMap = new HashMap<>(); public XMLResponseProcessor(BoseSoundTouchHandler handler) { this.handler = handler; init(); } - public void handleMessage(String msg) throws SAXException, IOException { - XMLReader reader = XMLReaderFactory.createXMLReader(); + public void handleMessage(String msg) throws SAXException, IOException, ParserConfigurationException { + SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + SAXParser parser = parserFactory.newSAXParser(); + XMLReader reader = parser.getXMLReader(); reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); reader.setContentHandler(new XMLResponseHandler(handler, stateSwitchingMap)); reader.parse(new InputSource(new StringReader(msg))); @@ -48,8 +56,6 @@ public class XMLResponseProcessor { // initializes our XML parsing state machine private void init() { - stateSwitchingMap = new HashMap<>(); - Map msgInitMap = new HashMap<>(); stateSwitchingMap.put(XMLHandlerState.INIT, msgInitMap); msgInitMap.put("msg", XMLHandlerState.Msg); diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/discovery/DiscoveryUtil.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/discovery/DiscoveryUtil.java index 15c1ee3b1..d85d90b9e 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/discovery/DiscoveryUtil.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/discovery/DiscoveryUtil.java @@ -14,6 +14,7 @@ package org.openhab.binding.bosesoundtouch.internal.discovery; import java.io.IOException; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.io.net.http.HttpUtil; /** @@ -21,6 +22,7 @@ import org.openhab.core.io.net.http.HttpUtil; * * @author Thomas Traunbauer - Initial contribution */ +@NonNullByDefault public class DiscoveryUtil { /** @@ -29,9 +31,6 @@ public class DiscoveryUtil { * This is a quick and dirty method, it always delivers the first appearance of content in an element */ public static String getContentOfFirstElement(String content, String element) { - if (content == null) { - return ""; - } String beginTag = "<" + element + ">"; String endTag = ""; @@ -39,7 +38,8 @@ public class DiscoveryUtil { int endIndex = content.indexOf(endTag); if (startIndex != -1 && endIndex != -1) { - return content.substring(startIndex, endIndex); + String result = content.substring(startIndex, endIndex); + return result != null ? result : ""; } else { return ""; } diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/discovery/SoundTouchDiscoveryParticipant.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/discovery/SoundTouchDiscoveryParticipant.java index 6cd347ef6..f8ab18258 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/discovery/SoundTouchDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/discovery/SoundTouchDiscoveryParticipant.java @@ -26,6 +26,8 @@ import java.util.Set; import javax.jmdns.ServiceInfo; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchConfiguration; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; @@ -44,6 +46,7 @@ import org.slf4j.LoggerFactory; * @author Christian Niessner - Initial contribution * @author Thomas Traunbauer - Initial contribution */ +@NonNullByDefault @Component(configurationPid = "discovery.bosesoundtouch") public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant { @@ -55,7 +58,7 @@ public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant } @Override - public DiscoveryResult createResult(ServiceInfo info) { + public @Nullable DiscoveryResult createResult(ServiceInfo info) { DiscoveryResult result = null; ThingUID uid = getThingUID(info); if (uid != null) { @@ -89,9 +92,10 @@ public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant } properties.put(BoseSoundTouchConfiguration.HOST, addrs[0].getHostAddress()); - if (getMacAddress(info) != null) { + byte[] localMacAddress = getMacAddress(info); + if (localMacAddress.length > 0) { properties.put(BoseSoundTouchConfiguration.MAC_ADDRESS, - new String(getMacAddress(info), StandardCharsets.UTF_8)); + new String(localMacAddress, StandardCharsets.UTF_8)); } // Set manufacturer as thing property (if available) @@ -105,7 +109,7 @@ public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant } @Override - public ThingUID getThingUID(ServiceInfo info) { + public @Nullable ThingUID getThingUID(ServiceInfo info) { logger.trace("ServiceInfo: {}", info); ThingTypeUID typeUID = getThingTypeUID(info); if (typeUID != null) { @@ -113,10 +117,8 @@ public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant if (info.getType().equals(getServiceType())) { logger.trace("Discovered a Bose SoundTouch thing with name '{}'", info.getName()); byte[] mac = getMacAddress(info); - if (mac != null) { + if (mac.length > 0) { return new ThingUID(typeUID, new String(mac, StandardCharsets.UTF_8)); - } else { - return null; } } } @@ -129,13 +131,13 @@ public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant return "_soundtouch._tcp.local."; } - private ThingTypeUID getThingTypeUID(ServiceInfo info) { + private @Nullable ThingTypeUID getThingTypeUID(ServiceInfo info) { InetAddress[] addrs = info.getInetAddresses(); if (addrs.length > 0) { String ip = addrs[0].getHostAddress(); String deviceId = null; byte[] mac = getMacAddress(info); - if (mac != null) { + if (mac.length > 0) { deviceId = new String(mac, StandardCharsets.UTF_8); } String deviceType; @@ -143,6 +145,7 @@ public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant String content = DiscoveryUtil.executeUrl("http://" + ip + ":8090/info"); deviceType = DiscoveryUtil.getContentOfFirstElement(content, "type"); } catch (IOException e) { + logger.debug("Ignoring IOException during Discovery: {}", e.getMessage()); return null; } @@ -163,6 +166,7 @@ public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant return BST_10_THING_TYPE_UID; } } catch (IOException e) { + logger.debug("Ignoring IOException during Discovery: {}", e.getMessage()); return null; } } @@ -190,24 +194,21 @@ public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant } private byte[] getMacAddress(ServiceInfo info) { - if (info != null) { - // sometimes we see empty messages - ignore them - if (!info.hasData()) { - return null; - } - byte[] mac = info.getPropertyBytes("MAC"); - if (mac == null) { - logger.warn("SoundTouch Device {} delivered no MAC address!", info.getName()); - return null; - } - if (mac.length != 12) { - BigInteger bi = new BigInteger(1, mac); - logger.warn("SoundTouch Device {} delivered an invalid MAC address: 0x{}", info.getName(), - String.format("%0" + (mac.length << 1) + "X", bi)); - return null; - } - return mac; + // sometimes we see empty messages - ignore them + if (!info.hasData()) { + return new byte[0]; } - return null; + byte[] mac = info.getPropertyBytes("MAC"); + if (mac == null) { + logger.warn("SoundTouch Device {} delivered no MAC address!", info.getName()); + return new byte[0]; + } + if (mac.length != 12) { + BigInteger bi = new BigInteger(1, mac); + logger.warn("SoundTouch Device {} delivered an invalid MAC address: 0x{}", info.getName(), + String.format("%0" + (mac.length << 1) + "X", bi)); + return new byte[0]; + } + return mac; } } diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/handler/BoseSoundTouchHandler.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/handler/BoseSoundTouchHandler.java index 63093e1da..c5a1e8360 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/handler/BoseSoundTouchHandler.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/handler/BoseSoundTouchHandler.java @@ -27,6 +27,8 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +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.StatusCode; import org.eclipse.jetty.websocket.api.WebSocketFrameListener; @@ -75,6 +77,7 @@ import org.slf4j.LoggerFactory; * @author Kai Kreuzer - code clean up * @author Alexander Kostadinov - Handling of websocket ping-pong mechanism for thing status check */ +@NonNullByDefault public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocketListener, WebSocketFrameListener { private static final int MAX_MISSED_PONGS_COUNT = 2; @@ -83,10 +86,10 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket private final Logger logger = LoggerFactory.getLogger(BoseSoundTouchHandler.class); - private ScheduledFuture connectionChecker; - private WebSocketClient client; - private volatile Session session; - private volatile CommandExecutor commandExecutor; + private @Nullable ScheduledFuture connectionChecker; + private @Nullable WebSocketClient client; + private @Nullable volatile Session session; + private @Nullable volatile CommandExecutor commandExecutor; private volatile int missedPongsCount = 0; private XMLResponseProcessor xmlResponseProcessor; @@ -94,7 +97,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket private PresetContainer presetContainer; private BoseStateDescriptionOptionProvider stateOptionProvider; - private Future sessionFuture; + private @Nullable Future sessionFuture; /** * Creates a new instance of this class for the {@link Thing}. @@ -120,9 +123,12 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket @Override public void dispose() { - if (connectionChecker != null && !connectionChecker.isCancelled()) { - connectionChecker.cancel(true); - connectionChecker = null; + ScheduledFuture localConnectionChecker = connectionChecker; + if (localConnectionChecker != null) { + if (!localConnectionChecker.isCancelled()) { + localConnectionChecker.cancel(true); + connectionChecker = null; + } } closeConnection(); super.dispose(); @@ -146,7 +152,8 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket @Override public void handleCommand(ChannelUID channelUID, Command command) { - if (commandExecutor == null) { + CommandExecutor localCommandExecutor = commandExecutor; + if (localCommandExecutor == null) { logger.debug("{}: Can't handle command '{}' for channel '{}' because of not initialized connection.", getDeviceName(), command, channelUID); return; @@ -157,7 +164,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket if (command.equals(RefreshType.REFRESH)) { switch (channelUID.getIdWithoutGroup()) { case CHANNEL_BASS: - commandExecutor.getInformations(APIRequest.BASS); + localCommandExecutor.getInformations(APIRequest.BASS); break; case CHANNEL_KEY_CODE: // refresh makes no sense... ? @@ -174,10 +181,10 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket case CHANNEL_RATEENABLED: case CHANNEL_SKIPENABLED: case CHANNEL_SKIPPREVIOUSENABLED: - commandExecutor.getInformations(APIRequest.NOW_PLAYING); + localCommandExecutor.getInformations(APIRequest.NOW_PLAYING); break; case CHANNEL_VOLUME: - commandExecutor.getInformations(APIRequest.VOLUME); + localCommandExecutor.getInformations(APIRequest.VOLUME); break; default: logger.debug("{} : Got command '{}' for channel '{}' which is unhandled!", getDeviceName(), command, @@ -188,21 +195,21 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket switch (channelUID.getIdWithoutGroup()) { case CHANNEL_POWER: if (command instanceof OnOffType) { - commandExecutor.postPower((OnOffType) command); + localCommandExecutor.postPower((OnOffType) command); } else { logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command); } break; case CHANNEL_VOLUME: if (command instanceof PercentType) { - commandExecutor.postVolume((PercentType) command); + localCommandExecutor.postVolume((PercentType) command); } else { logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command); } break; case CHANNEL_MUTE: if (command instanceof OnOffType) { - commandExecutor.postVolumeMuted((OnOffType) command); + localCommandExecutor.postVolumeMuted((OnOffType) command); } else { logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command); } @@ -212,7 +219,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket String cmd = command.toString().toUpperCase().trim(); try { OperationModeType mode = OperationModeType.valueOf(cmd); - commandExecutor.postOperationMode(mode); + localCommandExecutor.postOperationMode(mode); } catch (IllegalArgumentException iae) { logger.warn("{}: OperationMode \"{}\" is not valid!", getDeviceName(), cmd); } @@ -220,28 +227,28 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket break; case CHANNEL_PLAYER_CONTROL: if ((command instanceof PlayPauseType) || (command instanceof NextPreviousType)) { - commandExecutor.postPlayerControl(command); + localCommandExecutor.postPlayerControl(command); } else { logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command); } break; case CHANNEL_PRESET: if (command instanceof DecimalType) { - commandExecutor.postPreset((DecimalType) command); + localCommandExecutor.postPreset((DecimalType) command); } else { logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command); } break; case CHANNEL_BASS: if (command instanceof DecimalType) { - commandExecutor.postBass((DecimalType) command); + localCommandExecutor.postBass((DecimalType) command); } else { logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command); } break; case CHANNEL_SAVE_AS_PRESET: if (command instanceof DecimalType) { - commandExecutor.addCurrentContentItemToPresetContainer((DecimalType) command); + localCommandExecutor.addCurrentContentItemToPresetContainer((DecimalType) command); } else { logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command); } @@ -251,7 +258,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket String cmd = command.toString().toUpperCase().trim(); try { RemoteKeyType keyCommand = RemoteKeyType.valueOf(cmd); - commandExecutor.postRemoteKey(keyCommand); + localCommandExecutor.postRemoteKey(keyCommand); } catch (IllegalArgumentException e) { logger.debug("{}: Unhandled remote key: {}", getDeviceName(), cmd); } @@ -262,7 +269,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket if (channel != null) { ChannelTypeUID chTypeUid = channel.getChannelTypeUID(); if (chTypeUid != null) { - switch (channel.getChannelTypeUID().getId()) { + switch (chTypeUid.getId()) { case CHANNEL_NOTIFICATION_SOUND: String appKey = Objects.toString(getConfig().get(BoseSoundTouchConfiguration.APP_KEY), null); @@ -273,8 +280,8 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket .getConfiguration() .as(BoseSoundTouchNotificationChannelConfiguration.class); if (!url.isEmpty()) { - commandExecutor.playNotificationSound(appKey, notificationConfiguration, - url); + localCommandExecutor.playNotificationSound(appKey, + notificationConfiguration, url); } } } else { @@ -295,7 +302,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket * * @return the CommandExecutor of this handler */ - public CommandExecutor getCommandExecutor() { + public @Nullable CommandExecutor getCommandExecutor() { return commandExecutor; } @@ -304,7 +311,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket * * @return the Session this handler has opened */ - public Session getSession() { + public @Nullable Session getSession() { return session; } @@ -313,7 +320,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket * * @return the name of the device delivered from itself */ - public String getDeviceName() { + public @Nullable String getDeviceName() { return getThing().getProperties().get(DEVICE_INFO_NAME); } @@ -322,7 +329,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket * * @return the type of the device delivered from itself */ - public String getDeviceType() { + public @Nullable String getDeviceType() { return getThing().getProperties().get(DEVICE_INFO_TYPE); } @@ -331,7 +338,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket * * @return the MAC Address of this device (in format "123456789ABC") */ - public String getMacAddress() { + public @Nullable String getMacAddress() { return ((String) getThing().getConfiguration().get(BoseSoundTouchConfiguration.MAC_ADDRESS)).replaceAll(":", ""); } @@ -341,7 +348,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket * * @return the IP Address of this device */ - public String getIPAddress() { + public @Nullable String getIPAddress() { return (String) getThing().getConfiguration().getProperties().get(BoseSoundTouchConfiguration.HOST); } @@ -359,7 +366,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket } @Override - public void onWebSocketConnect(Session session) { + public void onWebSocketConnect(@Nullable Session session) { logger.debug("{}: onWebSocketConnect('{}')", getDeviceName(), session); this.session = session; commandExecutor = new CommandExecutor(this); @@ -367,88 +374,106 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket } @Override - public void onWebSocketError(Throwable e) { - logger.debug("{}: Error during websocket communication: {}", getDeviceName(), e.getMessage(), e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - if (commandExecutor != null) { - commandExecutor.postOperationMode(OperationModeType.OFFLINE); + public void onWebSocketError(@Nullable Throwable e) { + Throwable localThrowable = (e != null) ? e + : new IllegalStateException("Null Exception passed to onWebSocketError"); + logger.debug("{}: Error during websocket communication: {}", getDeviceName(), localThrowable.getMessage(), + localThrowable); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, localThrowable.getMessage()); + CommandExecutor localCommandExecutor = commandExecutor; + if (localCommandExecutor != null) { + localCommandExecutor.postOperationMode(OperationModeType.OFFLINE); commandExecutor = null; } - if (session != null) { - session.close(StatusCode.SERVER_ERROR, getDeviceName() + ": Failure: " + e.getMessage()); + Session localSession = session; + if (localSession != null) { + localSession.close(StatusCode.SERVER_ERROR, getDeviceName() + ": Failure: " + localThrowable.getMessage()); session = null; } } @Override - public void onWebSocketText(String msg) { + public void onWebSocketText(@Nullable String msg) { logger.debug("{}: onWebSocketText('{}')", getDeviceName(), msg); try { - xmlResponseProcessor.handleMessage(msg); + String localMessage = msg; + if (localMessage != null) { + xmlResponseProcessor.handleMessage(localMessage); + } } catch (Exception e) { logger.warn("{}: Could not parse XML from string '{}'.", getDeviceName(), msg, e); } } @Override - public void onWebSocketBinary(byte[] arr, int pos, int len) { + public void onWebSocketBinary(byte @Nullable [] payload, int offset, int len) { // we don't expect binary data so just dump if we get some... - logger.debug("{}: onWebSocketBinary({}, {}, '{}')", getDeviceName(), pos, len, Arrays.toString(arr)); + logger.debug("{}: onWebSocketBinary({}, {}, '{}')", getDeviceName(), offset, len, Arrays.toString(payload)); } @Override - public void onWebSocketClose(int code, String reason) { + public void onWebSocketClose(int code, @Nullable String reason) { logger.debug("{}: onClose({}, '{}')", getDeviceName(), code, reason); missedPongsCount = 0; updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason); - if (commandExecutor != null) { - commandExecutor.postOperationMode(OperationModeType.OFFLINE); + CommandExecutor localCommandExecutor = commandExecutor; + if (localCommandExecutor != null) { + localCommandExecutor.postOperationMode(OperationModeType.OFFLINE); } } @Override - public void onWebSocketFrame(Frame frame) { - if (frame.getType() == Type.PONG) { - missedPongsCount = 0; + public void onWebSocketFrame(@Nullable Frame frame) { + Frame localFrame = frame; + if (localFrame != null) { + if (localFrame.getType() == Type.PONG) { + missedPongsCount = 0; + } } } private synchronized void openConnection() { closeConnection(); try { - client = new WebSocketClient(); + WebSocketClient localClient = new WebSocketClient(); // we need longer timeouts for web socket. - client.setMaxIdleTimeout(360 * 1000); + localClient.setMaxIdleTimeout(360 * 1000); // Port seems to be hard coded, therefore no user input or discovery is necessary String wsUrl = "ws://" + getIPAddress() + ":8080/"; logger.debug("{}: Connecting to: {}", getDeviceName(), wsUrl); ClientUpgradeRequest request = new ClientUpgradeRequest(); request.setSubProtocols("gabbo"); - client.setStopTimeout(1000); - client.start(); - sessionFuture = client.connect(this, new URI(wsUrl), request); + localClient.setStopTimeout(1000); + localClient.start(); + sessionFuture = localClient.connect(this, new URI(wsUrl), request); + client = localClient; } catch (Exception e) { onWebSocketError(e); } } private synchronized void closeConnection() { - if (session != null) { + Session localSession = this.session; + if (localSession != null) { try { - session.close(StatusCode.NORMAL, "Binding shutdown"); + localSession.close(StatusCode.NORMAL, "Binding shutdown"); } catch (Exception e) { logger.debug("{}: Error while closing websocket communication: {} ({})", getDeviceName(), e.getClass().getName(), e.getMessage()); } session = null; } - if (sessionFuture != null && !sessionFuture.isDone()) { - sessionFuture.cancel(true); + Future localSessionFuture = sessionFuture; + if (localSessionFuture != null) { + if (!localSessionFuture.isDone()) { + localSessionFuture.cancel(true); + } } - if (client != null) { + WebSocketClient localClient = client; + if (localClient != null) { try { - client.stop(); - client.destroy(); + localClient.stop(); + localClient.destroy(); } catch (Exception e) { logger.debug("{}: Error while closing websocket communication: {} ({})", getDeviceName(), e.getClass().getName(), e.getMessage()); @@ -464,23 +489,25 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket || commandExecutor == null) { openConnection(); // try to reconnect.... } + Session localSession = this.session; + if (localSession != null) { + if (getThing().getStatus() == ThingStatus.ONLINE && localSession.isOpen()) { + try { + localSession.getRemote().sendPing(null); + missedPongsCount++; + } catch (IOException e) { + onWebSocketError(e); + closeConnection(); + openConnection(); + } - if (getThing().getStatus() == ThingStatus.ONLINE && this.session != null && this.session.isOpen()) { - try { - this.session.getRemote().sendPing(null); - missedPongsCount++; - } catch (IOException | NullPointerException e) { - onWebSocketError(e); - closeConnection(); - openConnection(); - } - - if (missedPongsCount >= MAX_MISSED_PONGS_COUNT) { - logger.debug("{}: Closing connection because of too many missed PONGs: {} (max allowed {}) ", - getDeviceName(), missedPongsCount, MAX_MISSED_PONGS_COUNT); - missedPongsCount = 0; - closeConnection(); - openConnection(); + if (missedPongsCount >= MAX_MISSED_PONGS_COUNT) { + logger.debug("{}: Closing connection because of too many missed PONGs: {} (max allowed {}) ", + getDeviceName(), missedPongsCount, MAX_MISSED_PONGS_COUNT); + missedPongsCount = 0; + closeConnection(); + openConnection(); + } } } } @@ -494,7 +521,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket public void handleGroupUpdated(BoseSoundTouchConfiguration masterPlayerConfiguration) { String deviceId = getMacAddress(); - if (masterPlayerConfiguration != null && masterPlayerConfiguration.macAddress != null) { + if (masterPlayerConfiguration.macAddress != null) { // Stereo pair if (Objects.equals(masterPlayerConfiguration.macAddress, deviceId)) { if (getThing().getThingTypeUID().equals(BST_10_THING_TYPE_UID)) {