[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:
Holger Friedrich 2023-03-06 22:34:16 +01:00 committed by GitHub
parent 539f49e4ec
commit 7ec7de55e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 83 additions and 67 deletions

View File

@ -38,7 +38,7 @@ public interface KNXTypeMapper {
* @return datapoint value as a string * @return datapoint value as a string
*/ */
@Nullable @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 * maps a datapoint value to an openHAB command or state
@ -49,8 +49,8 @@ public interface KNXTypeMapper {
* @return a command or state of openHAB * @return a command or state of openHAB
*/ */
@Nullable @Nullable
public Type toType(Datapoint datapoint, byte[] data); Type toType(Datapoint datapoint, byte[] data);
@Nullable @Nullable
public Class<? extends Type> toTypeClass(@Nullable String dpt); Class<? extends Type> toTypeClass(@Nullable String dpt);
} }

View File

@ -12,12 +12,10 @@
*/ */
package org.openhab.binding.knx.internal.channel; package org.openhab.binding.knx.internal.channel;
import static java.util.stream.Collectors.toSet;
import static org.openhab.binding.knx.internal.KNXBindingConstants.*; import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
@ -40,7 +38,7 @@ class TypeDimmer extends KNXChannelType {
@Override @Override
protected Set<String> getAllGAKeys() { 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 @Override

View File

@ -12,12 +12,10 @@
*/ */
package org.openhab.binding.knx.internal.channel; package org.openhab.binding.knx.internal.channel;
import static java.util.stream.Collectors.toSet;
import static org.openhab.binding.knx.internal.KNXBindingConstants.*; import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
@ -53,6 +51,6 @@ class TypeRollershutter extends KNXChannelType {
@Override @Override
protected Set<String> getAllGAKeys() { 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);
} }
} }

View File

@ -297,12 +297,11 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
} }
} }
@SuppressWarnings("null")
protected void releaseConnection() { protected void releaseConnection() {
logger.debug("Bridge {} is disconnecting from KNX bus", thingUID); logger.debug("Bridge {} is disconnecting from KNX bus", thingUID);
var tmplink = link; var tmplink = link;
if (tmplink != null) { if (tmplink != null) {
link.removeLinkListener(this); tmplink.removeLinkListener(this);
} }
busJob = nullify(busJob, j -> j.cancel(true)); busJob = nullify(busJob, j -> j.cancel(true));
readDatapoints.clear(); readDatapoints.clear();
@ -321,7 +320,7 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
logger.trace("Bridge {} disconnected from KNX bus", thingUID); 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) { if (target != null && lastWill != null) {
lastWill.accept(target); lastWill.accept(target);
} }

View File

@ -977,7 +977,7 @@ public class KNXCoreTypeMapper implements KNXTypeMapper {
if (typeClass.equals(DateTimeType.class)) { if (typeClass.equals(DateTimeType.class)) {
String date = formatDateTime(value, datapoint.getDPT()); String date = formatDateTime(value, datapoint.getDPT());
if (date.isEmpty()) { 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; return null;
} else { } else {
return DateTimeType.valueOf(date); 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 * @return a formatted String like </code>yyyy-MM-dd'T'HH:mm:ss</code> which
* is target format of the {@link DateTimeType} * 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; Date date = null;
try { try {

View File

@ -14,8 +14,8 @@ package org.openhab.binding.knx.internal.factory;
import static org.openhab.binding.knx.internal.KNXBindingConstants.*; import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -50,7 +50,7 @@ import org.osgi.service.component.annotations.Reference;
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.knx") @Component(service = ThingHandlerFactory.class, configurationPid = "binding.knx")
public class KNXHandlerFactory extends BaseThingHandlerFactory { 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); THING_TYPE_IP_BRIDGE, THING_TYPE_SERIAL_BRIDGE);
@Nullable @Nullable
@ -58,9 +58,11 @@ public class KNXHandlerFactory extends BaseThingHandlerFactory {
private final SerialPortManager serialPortManager; private final SerialPortManager serialPortManager;
@Activate @Activate
public KNXHandlerFactory(final @Reference TranslationProvider translationProvider, public KNXHandlerFactory(final @Reference NetworkAddressService networkAddressService,
final @Reference LocaleProvider localeProvider, final @Reference SerialPortManager serialPortManager) { final @Reference TranslationProvider translationProvider, final @Reference LocaleProvider localeProvider,
final @Reference SerialPortManager serialPortManager) {
KNXTranslationProvider.I18N.setProvider(localeProvider, translationProvider); KNXTranslationProvider.I18N.setProvider(localeProvider, translationProvider);
this.networkAddressService = networkAddressService;
this.serialPortManager = serialPortManager; this.serialPortManager = serialPortManager;
SerialTransportAdapter.setSerialPortManager(serialPortManager); SerialTransportAdapter.setSerialPortManager(serialPortManager);
} }
@ -84,7 +86,7 @@ public class KNXHandlerFactory extends BaseThingHandlerFactory {
if (THING_TYPE_DEVICE.equals(thingTypeUID)) { if (THING_TYPE_DEVICE.equals(thingTypeUID)) {
return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID);
} }
throw new IllegalArgumentException("The thing type " + thingTypeUID + " is not supported by the KNX binding."); return null;
} }
@Override @Override
@ -116,13 +118,4 @@ public class KNXHandlerFactory extends BaseThingHandlerFactory {
String serialPort = (String) configuration.get(SERIAL_PORT); String serialPort = (String) configuration.get(SERIAL_PORT);
return new ThingUID(thingTypeUID, serialPort); return new ThingUID(thingTypeUID, serialPort);
} }
@Reference
protected void setNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = networkAddressService;
}
protected void unsetNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = null;
}
} }

View File

@ -52,7 +52,7 @@ public abstract class AbstractKNXThingHandler extends BaseThingHandler implement
private final Logger logger = LoggerFactory.getLogger(AbstractKNXThingHandler.class); private final Logger logger = LoggerFactory.getLogger(AbstractKNXThingHandler.class);
protected @Nullable IndividualAddress address; protected @Nullable IndividualAddress address;
private @Nullable Future<?> descriptionJob; private @Nullable ScheduledFuture<?> descriptionJob;
private boolean filledDescription = false; private boolean filledDescription = false;
private final Random random = new Random(); private final Random random = new Random();

View File

@ -15,13 +15,12 @@ package org.openhab.binding.knx.internal.handler;
import static org.openhab.binding.knx.internal.KNXBindingConstants.*; import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -69,11 +68,11 @@ public class DeviceThingHandler extends AbstractKNXThingHandler {
private final Logger logger = LoggerFactory.getLogger(DeviceThingHandler.class); private final Logger logger = LoggerFactory.getLogger(DeviceThingHandler.class);
private final KNXTypeMapper typeHelper = new KNXCoreTypeMapper(); private final KNXTypeMapper typeHelper = new KNXCoreTypeMapper();
private final Set<GroupAddress> groupAddresses = new HashSet<>(); private final Set<GroupAddress> groupAddresses = ConcurrentHashMap.newKeySet();
private final Set<GroupAddress> groupAddressesWriteBlockedOnce = new HashSet<>(); private final Set<GroupAddress> groupAddressesWriteBlockedOnce = ConcurrentHashMap.newKeySet();
private final Set<OutboundSpec> groupAddressesRespondingSpec = new HashSet<>(); private final Set<OutboundSpec> groupAddressesRespondingSpec = ConcurrentHashMap.newKeySet();
private final Map<GroupAddress, ScheduledFuture<?>> readFutures = new HashMap<>(); private final Map<GroupAddress, ScheduledFuture<?>> readFutures = new ConcurrentHashMap<>();
private final Map<ChannelUID, ScheduledFuture<?>> channelFutures = new HashMap<>(); private final Map<ChannelUID, ScheduledFuture<?>> channelFutures = new ConcurrentHashMap<>();
private int readInterval; private int readInterval;
public DeviceThingHandler(Thing thing) { public DeviceThingHandler(Thing thing) {
@ -99,20 +98,20 @@ public class DeviceThingHandler extends AbstractKNXThingHandler {
@Override @Override
public void dispose() { public void dispose() {
cancelChannelFutures(); cancelChannelFutures();
freeGroupAdresses(); freeGroupAddresses();
super.dispose(); super.dispose();
} }
private void cancelChannelFutures() { private void cancelChannelFutures() {
for (ScheduledFuture<?> future : channelFutures.values()) { for (ChannelUID channelUID : channelFutures.keySet()) {
if (!future.isDone()) { channelFutures.computeIfPresent(channelUID, (k, v) -> {
future.cancel(true); v.cancel(true);
return null;
});
} }
} }
channelFutures.clear();
}
private void freeGroupAdresses() { private void freeGroupAddresses() {
groupAddresses.clear(); groupAddresses.clear();
groupAddressesWriteBlockedOnce.clear(); groupAddressesWriteBlockedOnce.clear();
groupAddressesRespondingSpec.clear(); groupAddressesRespondingSpec.clear();
@ -120,13 +119,13 @@ public class DeviceThingHandler extends AbstractKNXThingHandler {
@Override @Override
protected void cancelReadFutures() { protected void cancelReadFutures() {
for (ScheduledFuture<?> future : readFutures.values()) { for (GroupAddress groupAddress : readFutures.keySet()) {
if (!future.isDone()) { readFutures.computeIfPresent(groupAddress, (k, v) -> {
future.cancel(true); v.cancel(true);
return null;
});
} }
} }
readFutures.clear();
}
@FunctionalInterface @FunctionalInterface
private interface ChannelFunction { private interface ChannelFunction {
@ -220,7 +219,9 @@ public class DeviceThingHandler extends AbstractKNXThingHandler {
@SuppressWarnings("null") @SuppressWarnings("null")
private void rememberRespondingSpec(OutboundSpec commandSpec, boolean add) { private void rememberRespondingSpec(OutboundSpec commandSpec, boolean add) {
GroupAddress ga = commandSpec.getGroupAddress(); GroupAddress ga = commandSpec.getGroupAddress();
if (ga != null) {
groupAddressesRespondingSpec.removeIf(spec -> spec.getGroupAddress().equals(ga)); groupAddressesRespondingSpec.removeIf(spec -> spec.getGroupAddress().equals(ga));
}
if (add) { if (add) {
groupAddressesRespondingSpec.add(commandSpec); groupAddressesRespondingSpec.add(commandSpec);
} }
@ -393,18 +394,18 @@ public class DeviceThingHandler extends AbstractKNXThingHandler {
&& (type instanceof UnDefType || type instanceof IncreaseDecreaseType) && frequency > 0) { && (type instanceof UnDefType || type instanceof IncreaseDecreaseType) && frequency > 0) {
// continuous dimming by the binding // continuous dimming by the binding
if (UnDefType.UNDEF.equals(type)) { if (UnDefType.UNDEF.equals(type)) {
ScheduledFuture<?> future = channelFutures.remove(channelUID); channelFutures.computeIfPresent(channelUID, (k, v) -> {
if (future != null) { v.cancel(false);
future.cancel(false); return null;
} });
} else if (type instanceof IncreaseDecreaseType) { } else if (type instanceof IncreaseDecreaseType) {
ScheduledFuture<?> future = scheduler.scheduleWithFixedDelay(() -> { channelFutures.compute(channelUID, (k, v) -> {
postCommand(channelUID, (Command) type); if (v != null) {
}, 0, frequency, TimeUnit.MILLISECONDS); v.cancel(true);
ScheduledFuture<?> previousFuture = channelFutures.put(channelUID, future);
if (previousFuture != null) {
previousFuture.cancel(true);
} }
return scheduler.scheduleWithFixedDelay(() -> postCommand(channelUID, (Command) type), 0,
frequency, TimeUnit.MILLISECONDS);
});
} }
} else { } else {
if (type instanceof Command) { if (type instanceof Command) {

View File

@ -175,9 +175,10 @@ public class IPBridgeThingHandler extends KNXBridgeBaseThingHandler {
logger.debug("NetworkAddressService not available, cannot create bridge {}", thing.getUID()); logger.debug("NetworkAddressService not available, cannot create bridge {}", thing.getUID());
updateStatus(ThingStatus.OFFLINE); updateStatus(ThingStatus.OFFLINE);
return; return;
} } else {
localEndPoint = new InetSocketAddress(networkAddressService.getPrimaryIpv4HostAddress(), 0); localEndPoint = new InetSocketAddress(networkAddressService.getPrimaryIpv4HostAddress(), 0);
} }
}
updateStatus(ThingStatus.UNKNOWN); updateStatus(ThingStatus.UNKNOWN);
client = new IPClient(ipConnectionType, ip, localSource, port, localEndPoint, useNAT, autoReconnectPeriod, client = new IPClient(ipConnectionType, ip, localSource, port, localEndPoint, useNAT, autoReconnectPeriod,

View File

@ -36,11 +36,15 @@ class KNXChannelTypeTest {
ct = new MyKNXChannelType(""); ct = new MyKNXChannelType("");
} }
@SuppressWarnings("null")
@Test @Test
void testParseWithDptMultipleWithRead() { void testParseWithDptMultipleWithRead() {
ChannelConfiguration res = ct.parse("5.001:<1/3/22+0/3/22+<0/8/15"); 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("5.001", res.getDPT());
assertEquals("1/3/22", res.getMainGA().getGA()); assertEquals("1/3/22", res.getMainGA().getGA());
assertTrue(res.getMainGA().isRead()); assertTrue(res.getMainGA().isRead());
@ -48,11 +52,15 @@ class KNXChannelTypeTest {
assertEquals(2, res.getReadGAs().size()); assertEquals(2, res.getReadGAs().size());
} }
@SuppressWarnings("null")
@Test @Test
void testParseWithDptMultipleWithoutRead() { void testParseWithDptMultipleWithoutRead() {
ChannelConfiguration res = ct.parse("5.001:1/3/22+0/3/22+0/8/15"); 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("5.001", res.getDPT());
assertEquals("1/3/22", res.getMainGA().getGA()); assertEquals("1/3/22", res.getMainGA().getGA());
assertFalse(res.getMainGA().isRead()); assertFalse(res.getMainGA().isRead());
@ -60,11 +68,15 @@ class KNXChannelTypeTest {
assertEquals(0, res.getReadGAs().size()); assertEquals(0, res.getReadGAs().size());
} }
@SuppressWarnings("null")
@Test @Test
void testParseWithoutDptSingleWithoutRead() { void testParseWithoutDptSingleWithoutRead() {
ChannelConfiguration res = ct.parse("1/3/22"); ChannelConfiguration res = ct.parse("1/3/22");
if (res == null) {
fail();
return;
}
assertNull(res.getDPT()); assertNull(res.getDPT());
assertEquals("1/3/22", res.getMainGA().getGA()); assertEquals("1/3/22", res.getMainGA().getGA());
assertFalse(res.getMainGA().isRead()); assertFalse(res.getMainGA().isRead());
@ -72,11 +84,15 @@ class KNXChannelTypeTest {
assertEquals(0, res.getReadGAs().size()); assertEquals(0, res.getReadGAs().size());
} }
@SuppressWarnings("null")
@Test @Test
void testParseWithoutDptSingleWitRead() { void testParseWithoutDptSingleWitRead() {
ChannelConfiguration res = ct.parse("<1/3/22"); ChannelConfiguration res = ct.parse("<1/3/22");
if (res == null) {
fail();
return;
}
assertNull(res.getDPT()); assertNull(res.getDPT());
assertEquals("1/3/22", res.getMainGA().getGA()); assertEquals("1/3/22", res.getMainGA().getGA());
assertTrue(res.getMainGA().isRead()); assertTrue(res.getMainGA().isRead());
@ -84,19 +100,29 @@ class KNXChannelTypeTest {
assertEquals(1, res.getReadGAs().size()); assertEquals(1, res.getReadGAs().size());
} }
@SuppressWarnings("null")
@Test @Test
void testParseTwoLevel() { void testParseTwoLevel() {
ChannelConfiguration res = ct.parse("5.001:<3/1024+<4/1025"); ChannelConfiguration res = ct.parse("5.001:<3/1024+<4/1025");
if (res == null) {
fail();
return;
}
assertEquals("3/1024", res.getMainGA().getGA()); assertEquals("3/1024", res.getMainGA().getGA());
assertEquals(2, res.getListenGAs().size()); assertEquals(2, res.getListenGAs().size());
assertEquals(2, res.getReadGAs().size()); assertEquals(2, res.getReadGAs().size());
} }
@SuppressWarnings("null")
@Test @Test
void testParseFreeLevel() { void testParseFreeLevel() {
ChannelConfiguration res = ct.parse("5.001:<4610+<4611"); ChannelConfiguration res = ct.parse("5.001:<4610+<4611");
if (res == null) {
fail();
return;
}
assertEquals("4610", res.getMainGA().getGA()); assertEquals("4610", res.getMainGA().getGA());
assertEquals(2, res.getListenGAs().size()); assertEquals(2, res.getListenGAs().size());
assertEquals(2, res.getReadGAs().size()); assertEquals(2, res.getReadGAs().size());