[knx] Improve thread safety, null-analysis (#14509)
Carryover from smarthomej/addons#13 and smarthomej/addons#46, smarthomej/addons#60. Also-by: Jan N. Klug <github@klug.nrw> Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
This commit is contained in:
parent
539f49e4ec
commit
7ec7de55e3
@ -38,7 +38,7 @@ public interface KNXTypeMapper {
|
||||
* @return datapoint value as a string
|
||||
*/
|
||||
@Nullable
|
||||
public String toDPTValue(Type type, @Nullable String dpt);
|
||||
String toDPTValue(Type type, @Nullable String dpt);
|
||||
|
||||
/**
|
||||
* maps a datapoint value to an openHAB command or state
|
||||
@ -49,8 +49,8 @@ public interface KNXTypeMapper {
|
||||
* @return a command or state of openHAB
|
||||
*/
|
||||
@Nullable
|
||||
public Type toType(Datapoint datapoint, byte[] data);
|
||||
Type toType(Datapoint datapoint, byte[] data);
|
||||
|
||||
@Nullable
|
||||
public Class<? extends Type> toTypeClass(@Nullable String dpt);
|
||||
Class<? extends Type> toTypeClass(@Nullable String dpt);
|
||||
}
|
||||
|
||||
@ -12,12 +12,10 @@
|
||||
*/
|
||||
package org.openhab.binding.knx.internal.channel;
|
||||
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
@ -40,7 +38,7 @@ class TypeDimmer extends KNXChannelType {
|
||||
|
||||
@Override
|
||||
protected Set<String> getAllGAKeys() {
|
||||
return Stream.of(SWITCH_GA, POSITION_GA, INCREASE_DECREASE_GA).collect(toSet());
|
||||
return Set.of(SWITCH_GA, POSITION_GA, INCREASE_DECREASE_GA);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -12,12 +12,10 @@
|
||||
*/
|
||||
package org.openhab.binding.knx.internal.channel;
|
||||
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
@ -53,6 +51,6 @@ class TypeRollershutter extends KNXChannelType {
|
||||
|
||||
@Override
|
||||
protected Set<String> getAllGAKeys() {
|
||||
return Stream.of(UP_DOWN_GA, STOP_MOVE_GA, POSITION_GA).collect(toSet());
|
||||
return Set.of(UP_DOWN_GA, STOP_MOVE_GA, POSITION_GA);
|
||||
}
|
||||
}
|
||||
|
||||
@ -297,12 +297,11 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
protected void releaseConnection() {
|
||||
logger.debug("Bridge {} is disconnecting from KNX bus", thingUID);
|
||||
var tmplink = link;
|
||||
if (tmplink != null) {
|
||||
link.removeLinkListener(this);
|
||||
tmplink.removeLinkListener(this);
|
||||
}
|
||||
busJob = nullify(busJob, j -> j.cancel(true));
|
||||
readDatapoints.clear();
|
||||
@ -321,7 +320,7 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
||||
logger.trace("Bridge {} disconnected from KNX bus", thingUID);
|
||||
}
|
||||
|
||||
private <T> @Nullable T nullify(T target, @Nullable Consumer<T> lastWill) {
|
||||
private <T> @Nullable T nullify(@Nullable T target, @Nullable Consumer<T> lastWill) {
|
||||
if (target != null && lastWill != null) {
|
||||
lastWill.accept(target);
|
||||
}
|
||||
|
||||
@ -977,7 +977,7 @@ public class KNXCoreTypeMapper implements KNXTypeMapper {
|
||||
if (typeClass.equals(DateTimeType.class)) {
|
||||
String date = formatDateTime(value, datapoint.getDPT());
|
||||
if (date.isEmpty()) {
|
||||
logger.debug("toType: KNX clock msg ignored: date object null or empty {}.", date);
|
||||
logger.debug("toType: KNX clock msg ignored: date object empty {}.", date);
|
||||
return null;
|
||||
} else {
|
||||
return DateTimeType.valueOf(date);
|
||||
@ -1047,7 +1047,7 @@ public class KNXCoreTypeMapper implements KNXTypeMapper {
|
||||
* @return a formatted String like </code>yyyy-MM-dd'T'HH:mm:ss</code> which
|
||||
* is target format of the {@link DateTimeType}
|
||||
*/
|
||||
private String formatDateTime(String value, String dpt) {
|
||||
private String formatDateTime(String value, @Nullable String dpt) {
|
||||
Date date = null;
|
||||
|
||||
try {
|
||||
|
||||
@ -14,8 +14,8 @@ package org.openhab.binding.knx.internal.factory;
|
||||
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
@ -50,7 +50,7 @@ import org.osgi.service.component.annotations.Reference;
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.knx")
|
||||
public class KNXHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
public static final Collection<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Arrays.asList(THING_TYPE_DEVICE,
|
||||
public static final Collection<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_DEVICE,
|
||||
THING_TYPE_IP_BRIDGE, THING_TYPE_SERIAL_BRIDGE);
|
||||
|
||||
@Nullable
|
||||
@ -58,9 +58,11 @@ public class KNXHandlerFactory extends BaseThingHandlerFactory {
|
||||
private final SerialPortManager serialPortManager;
|
||||
|
||||
@Activate
|
||||
public KNXHandlerFactory(final @Reference TranslationProvider translationProvider,
|
||||
final @Reference LocaleProvider localeProvider, final @Reference SerialPortManager serialPortManager) {
|
||||
public KNXHandlerFactory(final @Reference NetworkAddressService networkAddressService,
|
||||
final @Reference TranslationProvider translationProvider, final @Reference LocaleProvider localeProvider,
|
||||
final @Reference SerialPortManager serialPortManager) {
|
||||
KNXTranslationProvider.I18N.setProvider(localeProvider, translationProvider);
|
||||
this.networkAddressService = networkAddressService;
|
||||
this.serialPortManager = serialPortManager;
|
||||
SerialTransportAdapter.setSerialPortManager(serialPortManager);
|
||||
}
|
||||
@ -84,7 +86,7 @@ public class KNXHandlerFactory extends BaseThingHandlerFactory {
|
||||
if (THING_TYPE_DEVICE.equals(thingTypeUID)) {
|
||||
return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID);
|
||||
}
|
||||
throw new IllegalArgumentException("The thing type " + thingTypeUID + " is not supported by the KNX binding.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -116,13 +118,4 @@ public class KNXHandlerFactory extends BaseThingHandlerFactory {
|
||||
String serialPort = (String) configuration.get(SERIAL_PORT);
|
||||
return new ThingUID(thingTypeUID, serialPort);
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setNetworkAddressService(NetworkAddressService networkAddressService) {
|
||||
this.networkAddressService = networkAddressService;
|
||||
}
|
||||
|
||||
protected void unsetNetworkAddressService(NetworkAddressService networkAddressService) {
|
||||
this.networkAddressService = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ public abstract class AbstractKNXThingHandler extends BaseThingHandler implement
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractKNXThingHandler.class);
|
||||
|
||||
protected @Nullable IndividualAddress address;
|
||||
private @Nullable Future<?> descriptionJob;
|
||||
private @Nullable ScheduledFuture<?> descriptionJob;
|
||||
private boolean filledDescription = false;
|
||||
private final Random random = new Random();
|
||||
|
||||
|
||||
@ -15,13 +15,12 @@ package org.openhab.binding.knx.internal.handler;
|
||||
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -69,11 +68,11 @@ public class DeviceThingHandler extends AbstractKNXThingHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(DeviceThingHandler.class);
|
||||
|
||||
private final KNXTypeMapper typeHelper = new KNXCoreTypeMapper();
|
||||
private final Set<GroupAddress> groupAddresses = new HashSet<>();
|
||||
private final Set<GroupAddress> groupAddressesWriteBlockedOnce = new HashSet<>();
|
||||
private final Set<OutboundSpec> groupAddressesRespondingSpec = new HashSet<>();
|
||||
private final Map<GroupAddress, ScheduledFuture<?>> readFutures = new HashMap<>();
|
||||
private final Map<ChannelUID, ScheduledFuture<?>> channelFutures = new HashMap<>();
|
||||
private final Set<GroupAddress> groupAddresses = ConcurrentHashMap.newKeySet();
|
||||
private final Set<GroupAddress> groupAddressesWriteBlockedOnce = ConcurrentHashMap.newKeySet();
|
||||
private final Set<OutboundSpec> groupAddressesRespondingSpec = ConcurrentHashMap.newKeySet();
|
||||
private final Map<GroupAddress, ScheduledFuture<?>> readFutures = new ConcurrentHashMap<>();
|
||||
private final Map<ChannelUID, ScheduledFuture<?>> channelFutures = new ConcurrentHashMap<>();
|
||||
private int readInterval;
|
||||
|
||||
public DeviceThingHandler(Thing thing) {
|
||||
@ -99,20 +98,20 @@ public class DeviceThingHandler extends AbstractKNXThingHandler {
|
||||
@Override
|
||||
public void dispose() {
|
||||
cancelChannelFutures();
|
||||
freeGroupAdresses();
|
||||
freeGroupAddresses();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private void cancelChannelFutures() {
|
||||
for (ScheduledFuture<?> future : channelFutures.values()) {
|
||||
if (!future.isDone()) {
|
||||
future.cancel(true);
|
||||
}
|
||||
for (ChannelUID channelUID : channelFutures.keySet()) {
|
||||
channelFutures.computeIfPresent(channelUID, (k, v) -> {
|
||||
v.cancel(true);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
channelFutures.clear();
|
||||
}
|
||||
|
||||
private void freeGroupAdresses() {
|
||||
private void freeGroupAddresses() {
|
||||
groupAddresses.clear();
|
||||
groupAddressesWriteBlockedOnce.clear();
|
||||
groupAddressesRespondingSpec.clear();
|
||||
@ -120,12 +119,12 @@ public class DeviceThingHandler extends AbstractKNXThingHandler {
|
||||
|
||||
@Override
|
||||
protected void cancelReadFutures() {
|
||||
for (ScheduledFuture<?> future : readFutures.values()) {
|
||||
if (!future.isDone()) {
|
||||
future.cancel(true);
|
||||
}
|
||||
for (GroupAddress groupAddress : readFutures.keySet()) {
|
||||
readFutures.computeIfPresent(groupAddress, (k, v) -> {
|
||||
v.cancel(true);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
readFutures.clear();
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
@ -220,7 +219,9 @@ public class DeviceThingHandler extends AbstractKNXThingHandler {
|
||||
@SuppressWarnings("null")
|
||||
private void rememberRespondingSpec(OutboundSpec commandSpec, boolean add) {
|
||||
GroupAddress ga = commandSpec.getGroupAddress();
|
||||
groupAddressesRespondingSpec.removeIf(spec -> spec.getGroupAddress().equals(ga));
|
||||
if (ga != null) {
|
||||
groupAddressesRespondingSpec.removeIf(spec -> spec.getGroupAddress().equals(ga));
|
||||
}
|
||||
if (add) {
|
||||
groupAddressesRespondingSpec.add(commandSpec);
|
||||
}
|
||||
@ -393,18 +394,18 @@ public class DeviceThingHandler extends AbstractKNXThingHandler {
|
||||
&& (type instanceof UnDefType || type instanceof IncreaseDecreaseType) && frequency > 0) {
|
||||
// continuous dimming by the binding
|
||||
if (UnDefType.UNDEF.equals(type)) {
|
||||
ScheduledFuture<?> future = channelFutures.remove(channelUID);
|
||||
if (future != null) {
|
||||
future.cancel(false);
|
||||
}
|
||||
channelFutures.computeIfPresent(channelUID, (k, v) -> {
|
||||
v.cancel(false);
|
||||
return null;
|
||||
});
|
||||
} else if (type instanceof IncreaseDecreaseType) {
|
||||
ScheduledFuture<?> future = scheduler.scheduleWithFixedDelay(() -> {
|
||||
postCommand(channelUID, (Command) type);
|
||||
}, 0, frequency, TimeUnit.MILLISECONDS);
|
||||
ScheduledFuture<?> previousFuture = channelFutures.put(channelUID, future);
|
||||
if (previousFuture != null) {
|
||||
previousFuture.cancel(true);
|
||||
}
|
||||
channelFutures.compute(channelUID, (k, v) -> {
|
||||
if (v != null) {
|
||||
v.cancel(true);
|
||||
}
|
||||
return scheduler.scheduleWithFixedDelay(() -> postCommand(channelUID, (Command) type), 0,
|
||||
frequency, TimeUnit.MILLISECONDS);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (type instanceof Command) {
|
||||
|
||||
@ -175,8 +175,9 @@ public class IPBridgeThingHandler extends KNXBridgeBaseThingHandler {
|
||||
logger.debug("NetworkAddressService not available, cannot create bridge {}", thing.getUID());
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
return;
|
||||
} else {
|
||||
localEndPoint = new InetSocketAddress(networkAddressService.getPrimaryIpv4HostAddress(), 0);
|
||||
}
|
||||
localEndPoint = new InetSocketAddress(networkAddressService.getPrimaryIpv4HostAddress(), 0);
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
@ -36,11 +36,15 @@ class KNXChannelTypeTest {
|
||||
ct = new MyKNXChannelType("");
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
void testParseWithDptMultipleWithRead() {
|
||||
ChannelConfiguration res = ct.parse("5.001:<1/3/22+0/3/22+<0/8/15");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals("5.001", res.getDPT());
|
||||
assertEquals("1/3/22", res.getMainGA().getGA());
|
||||
assertTrue(res.getMainGA().isRead());
|
||||
@ -48,11 +52,15 @@ class KNXChannelTypeTest {
|
||||
assertEquals(2, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
void testParseWithDptMultipleWithoutRead() {
|
||||
ChannelConfiguration res = ct.parse("5.001:1/3/22+0/3/22+0/8/15");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals("5.001", res.getDPT());
|
||||
assertEquals("1/3/22", res.getMainGA().getGA());
|
||||
assertFalse(res.getMainGA().isRead());
|
||||
@ -60,11 +68,15 @@ class KNXChannelTypeTest {
|
||||
assertEquals(0, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
void testParseWithoutDptSingleWithoutRead() {
|
||||
ChannelConfiguration res = ct.parse("1/3/22");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertNull(res.getDPT());
|
||||
assertEquals("1/3/22", res.getMainGA().getGA());
|
||||
assertFalse(res.getMainGA().isRead());
|
||||
@ -72,11 +84,15 @@ class KNXChannelTypeTest {
|
||||
assertEquals(0, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
void testParseWithoutDptSingleWitRead() {
|
||||
ChannelConfiguration res = ct.parse("<1/3/22");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertNull(res.getDPT());
|
||||
assertEquals("1/3/22", res.getMainGA().getGA());
|
||||
assertTrue(res.getMainGA().isRead());
|
||||
@ -84,19 +100,29 @@ class KNXChannelTypeTest {
|
||||
assertEquals(1, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
void testParseTwoLevel() {
|
||||
ChannelConfiguration res = ct.parse("5.001:<3/1024+<4/1025");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals("3/1024", res.getMainGA().getGA());
|
||||
assertEquals(2, res.getListenGAs().size());
|
||||
assertEquals(2, res.getReadGAs().size());
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
void testParseFreeLevel() {
|
||||
ChannelConfiguration res = ct.parse("5.001:<4610+<4611");
|
||||
|
||||
if (res == null) {
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals("4610", res.getMainGA().getGA());
|
||||
assertEquals(2, res.getListenGAs().size());
|
||||
assertEquals(2, res.getReadGAs().size());
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user