*/
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"),
*/
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();
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.eclipse.jdt.annotation.NonNullByDefault;
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";
*/
package org.openhab.binding.bosesoundtouch.internal;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Thing;
/**
*
* @author Ivaylo Ivanov - Initial contribution
*/
+@NonNullByDefault
public class BoseSoundTouchConfiguration {
// Device configuration parameters;
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 = "";
}
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;
*
* @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) {
}
@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
*/
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;
*/
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";
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) {
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;
* @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
*/
public CommandExecutor(BoseSoundTouchHandler handler) {
this.handler = handler;
- init();
+ getInformations(APIRequest.INFO);
+ currentOperationMode = OperationModeType.OFFLINE;
}
/**
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());
}
*/
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());
}
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);
+ }
}
/**
* @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();
}
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) {
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;
}
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;
* @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
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;
+ }
}
}
* @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);
}
*/
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;
}
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;
}
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
*
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) {
// buffer.append(presetID);
// return buffer.toString();
// }
- return itemName;
+ String localString = itemName;
+ return (localString != null) ? localString : "";
}
}
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;
*/
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;
*/
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;
*/
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;
*/
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;
*/
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;
*/
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,
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;
* @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;
/**
*/
public PresetContainer(Storage<ContentItem> storage) {
this.storage = storage;
- init();
- }
-
- private void init() {
- this.mapOfPresets = new HashMap<>();
readFromStorage();
}
}
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());
}
*/
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,
*/
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,
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;
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;
private OnOffType rateEnabled;
private OnOffType skipEnabled;
private OnOffType skipPreviousEnabled;
-
private State nowPlayingSource;
private BoseSoundTouchConfiguration masterDeviceId;
+
String deviceId;
private Map<Integer, ContentItem> playerPresets;
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);
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)) {
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;
}
}
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;
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;
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);
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) {
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;
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));
}
}
}
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;
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);
}
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);
+ }
}
}
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
* @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)));
// 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);
import java.io.IOException;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.io.net.http.HttpUtil;
/**
*
* @author Thomas Traunbauer - Initial contribution
*/
+@NonNullByDefault
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 + ">";
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 "";
}
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;
* @author Christian Niessner - Initial contribution
* @author Thomas Traunbauer - Initial contribution
*/
+@NonNullByDefault
@Component(configurationPid = "discovery.bosesoundtouch")
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) {
}
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)
}
@Override
- public ThingUID getThingUID(ServiceInfo info) {
+ public @Nullable ThingUID getThingUID(ServiceInfo info) {
logger.trace("ServiceInfo: {}", info);
ThingTypeUID typeUID = getThingTypeUID(info);
if (typeUID != null) {
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;
}
}
}
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;
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;
}
return BST_10_THING_TYPE_UID;
}
} catch (IOException e) {
+ logger.debug("Ignoring IOException during Discovery: {}", e.getMessage());
return null;
}
}
}
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;
}
}
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;
* @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;
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;
private PresetContainer presetContainer;
private BoseStateDescriptionOptionProvider stateOptionProvider;
- private Future<?> sessionFuture;
+ private @Nullable Future<?> sessionFuture;
/**
* Creates a new instance of this class for the {@link Thing}.
@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();
@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;
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... ?
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,
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);
}
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);
}
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);
}
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);
}
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);
.getConfiguration()
.as(BoseSoundTouchNotificationChannelConfiguration.class);
if (!url.isEmpty()) {
- commandExecutor.playNotificationSound(appKey, notificationConfiguration,
- url);
+ localCommandExecutor.playNotificationSound(appKey,
+ notificationConfiguration, url);
}
}
} else {
*
* @return the CommandExecutor of this handler
*/
- public CommandExecutor getCommandExecutor() {
+ public @Nullable CommandExecutor getCommandExecutor() {
return commandExecutor;
}
*
* @return the Session this handler has opened
*/
- public Session getSession() {
+ public @Nullable Session getSession() {
return session;
}
*
* @return the name of the device delivered from itself
*/
- public String getDeviceName() {
+ public @Nullable String getDeviceName() {
return getThing().getProperties().get(DEVICE_INFO_NAME);
}
*
* @return the type of the device delivered from itself
*/
- public String getDeviceType() {
+ public @Nullable String getDeviceType() {
return getThing().getProperties().get(DEVICE_INFO_TYPE);
}
*
* @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(":",
"");
}
*
* @return the IP Address of this device
*/
- public String getIPAddress() {
+ public @Nullable String getIPAddress() {
return (String) getThing().getConfiguration().getProperties().get(BoseSoundTouchConfiguration.HOST);
}
}
@Override
- public void onWebSocketConnect(Session session) {
+ public void onWebSocketConnect(@Nullable Session session) {
logger.debug("{}: onWebSocketConnect('{}')", getDeviceName(), session);
this.session = session;
commandExecutor = new CommandExecutor(this);
}
@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());
|| 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();
+ }
}
}
}
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)) {