]> git.basschouten.com Git - openhab-addons.git/commitdiff
[bosesoundtouch] Improve SAT errors and remove dependency (#13842)
authorlsiepel <leosiepel@gmail.com>
Sat, 10 Dec 2022 08:42:09 +0000 (09:42 +0100)
committerGitHub <noreply@github.com>
Sat, 10 Dec 2022 08:42:09 +0000 (09:42 +0100)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
24 files changed:
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/APIRequest.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/AvailableSources.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchBindingConstants.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchConfiguration.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchHandlerFactory.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchNotFoundException.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/BoseSoundTouchNotificationChannelConfiguration.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/CommandExecutor.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/ContentItem.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/ContentItemMaker.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/ContentItemNotPresetableException.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/NoInternetRadioPresetFoundException.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/NoPresetFoundException.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/NoStoredMusicPresetFoundException.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/OperationModeNotAvailableException.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/OperationModeType.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/PresetContainer.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/RemoteKeyType.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLHandlerState.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseHandler.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseProcessor.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/discovery/DiscoveryUtil.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/discovery/SoundTouchDiscoveryParticipant.java
bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/handler/BoseSoundTouchHandler.java

index 246ca994f5f2fe548657cf8ccbe869d2a4cc078c..9913ed463c5607c0107e4cde6e826854356e0aa0 100644 (file)
  */
 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"),
index c90069c458aaf1fccb6589da789ce9bf9972a4f6..8b4625fea438444c73d7b94743fbe3feeefdfe38 100644 (file)
  */
 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();
index b4fb6cbb6b240521aff731e05545a360cbc9bdf6..0bb236eb6b37354e8a82bbbf8477848f575d1ee9 100644 (file)
@@ -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";
index 169a96aa2519e371dcbf154951c17c63514cdc77..e7450fa6119c34cf5709ac9ac06ebc9b4f2e1e71 100644 (file)
@@ -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 = "";
 }
index 0ffd28c3a63c4e9a0dc96ad6d1c2ec79f3dbdf16..47544dc2f21691286f065c3a87fd549fee9dd2df 100644 (file)
@@ -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<ContentItem> 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<ContentItem> 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
index bbe3f3593ee676e4e2e6c8768e726996b49c14ad..40c580a4022b0f47ed7261fcba78e2958ed75e3d 100644 (file)
  */
 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;
 
index bb12d39a7dde2f62096aa88292a00d281c2feca5..5fe3552b6db7fb239ae6e5179e46cfba16f1247e 100644 (file)
  */
 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) {
index d069090755112c8ab5c19f80d6cba7cc9f66f480..7a01af6069857d8cae62e659ba464c7fcbcb172c 100644 (file)
@@ -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<String, Boolean> mapOfAvailableFunctions;
+    private final Map<String, Boolean> 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<Integer, ContentItem> 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 = "<msg><header " + "deviceID=\"" + handler.getMacAddress() + "\"" + " url=\"" + apiRequest
                 + "\" method=\"GET\"><request requestID=\"0\"><info type=\"new\"/></request></header></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<ContentItem> listOfPresets = handler.getPresetContainer().getAllPresets();
-                for (ContentItem ps : listOfPresets) {
-                    if (ps.isPresetable()) {
-                        if (ps.getLocation().equals(contentItem.getLocation())) {
+            Collection<ContentItem> 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 = "<msg><header " + "deviceID=\"" + handler.getMacAddress() + "\"" + " url=\"" + url
                 + "\" method=\"POST\"><request requestID=\"" + id + "\"><info " + infoAddon
                 + " type=\"new\"/></request></header><body>" + postData + "</body></msg>";
-        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;
         }
index 933c3b0513250e4458e16808c3a3e2ec0004962e..348eb7c676600b53389c223c06551a342df5ee10 100644 (file)
@@ -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<String, String> 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<String, String> 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 :
+     * & - &amp;
+     * < - &lt;
+     * > - &gt;
+     * " - &quot;
+     * ' - &apos;
+     */
+    private String escapeXml(String xml) {
+        xml = xml.replaceAll("&", "&amp;");
+        xml = xml.replaceAll("<", "&lt;");
+        xml = xml.replaceAll(">", "&gt;");
+        xml = xml.replaceAll("\"", "&quot;");
+        xml = xml.replaceAll("'", "&apos;");
+        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("<ContentItem");
-                if (source != null) {
-                    sbXml.append(" source=\"").append(StringEscapeUtils.escapeXml(source)).append("\"");
-                }
-                if (location != null) {
-                    sbXml.append(" location=\"").append(StringEscapeUtils.escapeXml(location)).append("\"");
+
+                sbXml.append(" source=\"").append(escapeXml(source)).append("\"");
+
+                String localLocation = location;
+                if (localLocation != null) {
+                    sbXml.append(" location=\"").append(escapeXml(localLocation)).append("\"");
                 }
-                if (sourceAccount != null) {
-                    sbXml.append(" sourceAccount=\"").append(StringEscapeUtils.escapeXml(sourceAccount)).append("\"");
+                String localSourceAccount = sourceAccount;
+                if (localSourceAccount != null) {
+                    sbXml.append(" sourceAccount=\"").append(escapeXml(localSourceAccount)).append("\"");
                 }
                 sbXml.append(" isPresetable=\"").append(presetable).append("\"");
                 for (Map.Entry<String, String> 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 : "";
     }
 }
index 05fe9b5621920cfca0292a724b4b9a0cc3b1f914..8d341f15496c0b10fc3a3bcc6aed22ef8e54bb2a 100644 (file)
@@ -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;
index d7c9d78aca84d58cfd0df28ccd4ce9ed963cf0bb..2d33066b94bd1c3db1ade459db90d3977cc7d671 100644 (file)
  */
 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;
 
index 6f3422aea7360f39c9a3305e570a7725e2a6b24f..56f08c56509b61b1e7b3391431fa09bd556214f1 100644 (file)
  */
 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;
 
index 2406a6633656b7ffcebbedc89dc1db0b4319a04c..f7b72a8d43fb461b0c17d26dd9166656a8acdb49 100644 (file)
  */
 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;
 
index 83f85e726ba75d6b34cc5742ec2da5d91e6cf290..21e1f5dc5f4084d2f03d61897ec123f4dfa15fbd 100644 (file)
  */
 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;
 
index 8221973c4664d590c3ea81f2e8c59fcba6ec5432..976edd21f382c1837476e970fe8016b8f04c239a 100644 (file)
  */
 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;
 
index f59955a53965fc79dae160ca1bd73d084ceaacb8..ba72f5ab51a5af280995d7f1e06a90370af91856 100644 (file)
  */
 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,
index 8d7946d9f286f510b9dbf5b5c51e35411011c8d0..dfc033fe2ed3946d0f55a64c0e26a1fa23faba98 100644 (file)
@@ -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<Integer, ContentItem> mapOfPresets;
+    private final Map<Integer, ContentItem> mapOfPresets = new HashMap<>();
     private Storage<ContentItem> storage;
 
     /**
@@ -42,11 +46,6 @@ public class PresetContainer {
      */
     public PresetContainer(Storage<ContentItem> storage) {
         this.storage = storage;
-        init();
-    }
-
-    private void init() {
-        this.mapOfPresets = new HashMap<>();
         readFromStorage();
     }
 
@@ -133,10 +132,12 @@ public class PresetContainer {
     }
 
     private void readFromStorage() {
-        Collection<ContentItem> 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());
             }
index 89994b5c0e6787e6daab113f97d9cf8733a1779e..cca679acd017a1cf25fd26e58a7637ff9eebc49e 100644 (file)
  */
 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,
index 30993d9e0bdbf4e7cb831ad67988a5812de10463..6c600d996e22bba2560e8f1567094ac9dc6017cc 100644 (file)
  */
 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,
index 653ef3342bc957b8824afad21b29a0b251a1e903..502680e58beabe8841e6f52bafc69c8c6f42e493 100644 (file)
@@ -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<XMLHandlerState, Map<String, XMLHandlerState>> stateSwitchingMap;
 
-    private Stack<XMLHandlerState> states;
-    private XMLHandlerState state;
+    private final Stack<XMLHandlerState> 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<Integer, ContentItem> 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<String, XMLHandlerState> 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<String, XMLHandlerState> 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<String, String> 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<String, String> 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);
+            }
         }
     }
 
index cddcf2315766d66c622a354621c4ea5dbfc153f5..533e538210ff214437bf526418dbb88b6fc68165 100644 (file)
@@ -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<XMLHandlerState, Map<String, XMLHandlerState>> stateSwitchingMap;
+    private final Map<XMLHandlerState, Map<String, XMLHandlerState>> 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<String, XMLHandlerState> msgInitMap = new HashMap<>();
         stateSwitchingMap.put(XMLHandlerState.INIT, msgInitMap);
         msgInitMap.put("msg", XMLHandlerState.Msg);
index 15c1ee3b11c3eb5faed7d3fa9d97eebc9ececf15..d85d90b9e10a6bb310ef3e733b37d73eee6e75da 100644 (file)
@@ -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 = "</" + element + ">";
 
@@ -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 "";
         }
index 6cd347ef6fd3283cb43a0868b4f91ea93a5321a1..f8ab182584d84df6146bf42b87b410c25f09a6ef 100644 (file)
@@ -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;
     }
 }
index 63093e1dae5891d4a10caabcae7c447c9b5179df..c5a1e836010882e353b77422fd8fbefe58b5d24d 100644 (file)
@@ -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)) {