--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.tr064.internal;
+
+import javax.net.ssl.X509ExtendedTrustManager;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.io.net.http.TlsTrustManagerProvider;
+import org.openhab.core.io.net.http.TrustAllTrustManager;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * Provides a TrustManager to allow secure connections to any FRITZ!Box
+ *
+ * @author Christoph Weitkamp - Initial Contribution
+ */
+@Component
+@NonNullByDefault
+public class AVMFritzTlsTrustManagerProvider implements TlsTrustManagerProvider {
+
+ @Override
+ public String getHostName() {
+ return "fritz.box";
+ }
+
+ @Override
+ public X509ExtendedTrustManager getTrustManager() {
+ return TrustAllTrustManager.getInstance();
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2020 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.tr064.internal;
-
-import javax.net.ssl.X509ExtendedTrustManager;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.core.io.net.http.TlsTrustManagerProvider;
-import org.openhab.core.io.net.http.TrustAllTrustManager;
-import org.osgi.service.component.annotations.Component;
-
-/**
- * Provides a TrustManager to allow secure connections to any FRITZ!Box
- *
- * @author Christoph Weitkamp - Initial Contribution
- */
-@Component
-@NonNullByDefault
-public class AvmFritzTlsTrustManagerProvider implements TlsTrustManagerProvider {
-
- @Override
- public String getHostName() {
- return "fritz.box";
- }
-
- @Override
- public X509ExtendedTrustManager getTrustManager() {
- return TrustAllTrustManager.getInstance();
- }
-}
*/
package org.openhab.binding.tr064.internal.phonebook;
+import java.math.BigDecimal;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.CoreItemFactory;
+import org.openhab.core.library.types.StringListType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ThingUID;
-import org.openhab.core.thing.profiles.*;
+import org.openhab.core.thing.profiles.ProfileCallback;
+import org.openhab.core.thing.profiles.ProfileContext;
+import org.openhab.core.thing.profiles.ProfileType;
+import org.openhab.core.thing.profiles.ProfileTypeBuilder;
+import org.openhab.core.thing.profiles.ProfileTypeUID;
+import org.openhab.core.thing.profiles.StateProfile;
import org.openhab.core.transform.TransformationService;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
import org.openhab.core.util.UIDUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PhonebookProfile implements StateProfile {
public static final ProfileTypeUID PHONEBOOK_PROFILE_TYPE_UID = new ProfileTypeUID(
TransformationService.TRANSFORM_PROFILE_SCOPE, "PHONEBOOK");
- public static final ProfileType PHONEBOOK_PROFILE_TYPE = ProfileTypeBuilder
- .newState(PhonebookProfile.PHONEBOOK_PROFILE_TYPE_UID, "Phonebook").build();
+ public static final ProfileType PHONEBOOK_PROFILE_TYPE = ProfileTypeBuilder //
+ .newState(PHONEBOOK_PROFILE_TYPE_UID, "Phonebook") //
+ .withSupportedItemTypesOfChannel(CoreItemFactory.CALL, CoreItemFactory.STRING) //
+ .withSupportedItemTypes(CoreItemFactory.STRING) //
+ .build();
public static final String PHONEBOOK_PARAM = "phonebook";
- private static final String MATCH_COUNT_PARAM = "matchCount";
+ public static final String MATCH_COUNT_PARAM = "matchCount";
+ public static final String PHONE_NUMBER_INDEX_PARAM = "phoneNumberIndex";
private final Logger logger = LoggerFactory.getLogger(PhonebookProfile.class);
private final @Nullable ThingUID thingUID;
private final Map<ThingUID, PhonebookProvider> phonebookProviders;
private final int matchCount;
+ private final int phoneNumberIndex;
public PhonebookProfile(ProfileCallback callback, ProfileContext context,
Map<ThingUID, PhonebookProvider> phonebookProviders) {
Configuration configuration = context.getConfiguration();
Object phonebookParam = configuration.get(PHONEBOOK_PARAM);
Object matchCountParam = configuration.get(MATCH_COUNT_PARAM);
+ Object phoneNumberIndexParam = configuration.get(PHONE_NUMBER_INDEX_PARAM);
- logger.debug("Profile configured with '{}'='{}', '{}'='{}'", PHONEBOOK_PARAM, phonebookParam, MATCH_COUNT_PARAM,
- matchCountParam);
+ logger.debug("Profile configured with '{}'='{}', '{}'='{}', '{}'='{}'", PHONEBOOK_PARAM, phonebookParam,
+ MATCH_COUNT_PARAM, matchCountParam, PHONE_NUMBER_INDEX_PARAM, phoneNumberIndexParam);
ThingUID thingUID;
String phonebookName = null;
int matchCount = 0;
+ int phoneNumberIndex = 0;
try {
- if (!(phonebookParam instanceof String)
- || ((matchCountParam != null) && !(matchCountParam instanceof String))) {
- throw new IllegalArgumentException("Parameters need to be Strings");
+ if (!(phonebookParam instanceof String)) {
+ throw new IllegalArgumentException("Parameter 'phonebook' need to be a String");
}
String[] phonebookParams = ((String) phonebookParam).split(":");
if (phonebookParams.length > 2) {
- throw new IllegalArgumentException("Could not split 'phonebook' parameter");
+ throw new IllegalArgumentException("Cannot split 'phonebook' parameter");
}
thingUID = new ThingUID(UIDUtils.decode(phonebookParams[0]));
if (phonebookParams.length == 2) {
phonebookName = UIDUtils.decode(phonebookParams[1]);
}
if (matchCountParam != null) {
- matchCount = Integer.parseInt((String) matchCountParam);
+ if (matchCountParam instanceof BigDecimal) {
+ matchCount = ((BigDecimal) matchCountParam).intValue();
+ } else if (matchCountParam instanceof String) {
+ matchCount = Integer.parseInt((String) matchCountParam);
+ }
+ }
+ if (phoneNumberIndexParam != null) {
+ if (phoneNumberIndexParam instanceof BigDecimal) {
+ phoneNumberIndex = ((BigDecimal) phoneNumberIndexParam).intValue();
+ } else if (phoneNumberIndexParam instanceof String) {
+ phoneNumberIndex = Integer.parseInt((String) phoneNumberIndexParam);
+ }
}
} catch (IllegalArgumentException e) {
- logger.warn("Could not initialize PHONEBOOK transformation profile: {}. Profile will be inactive.",
+ logger.warn("Cannot initialize PHONEBOOK transformation profile: {}. Profile will be inactive.",
e.getMessage());
thingUID = null;
}
this.thingUID = thingUID;
this.phonebookName = phonebookName;
this.matchCount = matchCount;
+ this.phoneNumberIndex = phoneNumberIndex;
}
@Override
@Override
public void onStateUpdateFromHandler(State state) {
+ if (state instanceof UnDefType) {
+ // we cannot adjust UNDEF or NULL values, thus we simply apply them without reporting an error or warning
+ callback.sendUpdate(state);
+ }
if (state instanceof StringType) {
- PhonebookProvider provider = phonebookProviders.get(thingUID);
- if (provider == null) {
- logger.warn("Could not get phonebook provider with thing UID '{}'.", thingUID);
- return;
- }
- final String phonebookName = this.phonebookName;
- Optional<String> match;
- if (phonebookName != null) {
- match = provider.getPhonebookByName(phonebookName).or(() -> {
- logger.warn("Could not get phonebook '{}' from provider '{}'", phonebookName, thingUID);
- return Optional.empty();
- }).flatMap(phonebook -> phonebook.lookupNumber(state.toString(), matchCount));
- } else {
- match = provider.getPhonebooks().stream().map(p -> p.lookupNumber(state.toString(), matchCount))
- .filter(Optional::isPresent).map(Optional::get).findAny();
- }
+ Optional<String> match = resolveNumber(state.toString());
State newState = match.map(name -> (State) new StringType(name)).orElse(state);
if (newState == state) {
logger.debug("Number '{}' not found in phonebook '{}' from provider '{}'", state, phonebookName,
thingUID);
}
callback.sendUpdate(newState);
+ } else if (state instanceof StringListType) {
+ StringListType stringList = (StringListType) state;
+ try {
+ String phoneNumber = stringList.getValue(phoneNumberIndex);
+ Optional<String> match = resolveNumber(phoneNumber);
+ final State newState;
+ if (match.isPresent()) {
+ newState = new StringType(match.get());
+ } else {
+ logger.debug("Number '{}' not found in phonebook '{}' from provider '{}'", phoneNumber,
+ phonebookName, thingUID);
+ newState = new StringType(phoneNumber);
+ }
+ callback.sendUpdate(newState);
+ } catch (IllegalArgumentException e) {
+ logger.debug("StringListType does not contain a number at index {}", phoneNumberIndex);
+ }
+ }
+ }
+
+ private Optional<String> resolveNumber(String phoneNumber) {
+ PhonebookProvider provider = phonebookProviders.get(thingUID);
+ if (provider == null) {
+ logger.warn("Could not get phonebook provider with thing UID '{}'.", thingUID);
+ return Optional.empty();
+ }
+ final String phonebookName = this.phonebookName;
+ if (phonebookName != null) {
+ return provider.getPhonebookByName(phonebookName).or(() -> {
+ logger.warn("Could not get phonebook '{}' from provider '{}'", phonebookName, thingUID);
+ return Optional.empty();
+ }).flatMap(phonebook -> phonebook.lookupNumber(phoneNumber, matchCount));
+ } else {
+ return provider.getPhonebooks().stream().map(p -> p.lookupNumber(phoneNumber, matchCount))
+ .filter(Optional::isPresent).map(Optional::get).findAny();
}
}
*/
package org.openhab.binding.tr064.internal.phonebook;
+import static java.util.Comparator.comparing;
+
import java.net.URI;
+import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.core.ConfigOptionProvider;
import org.openhab.core.config.core.ParameterOption;
+import org.openhab.core.i18n.LocalizedKey;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.profiles.Profile;
import org.openhab.core.thing.profiles.ProfileCallback;
import org.openhab.core.thing.profiles.ProfileType;
import org.openhab.core.thing.profiles.ProfileTypeProvider;
import org.openhab.core.thing.profiles.ProfileTypeUID;
+import org.openhab.core.thing.profiles.i18n.ProfileTypeI18nLocalizationService;
+import org.openhab.core.util.BundleResolver;
import org.openhab.core.util.UIDUtils;
+import org.osgi.framework.Bundle;
+import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private final Logger logger = LoggerFactory.getLogger(PhonebookProfileFactory.class);
private final Map<ThingUID, PhonebookProvider> phonebookProviders = new ConcurrentHashMap<>();
+ private final Map<LocalizedKey, ProfileType> localizedProfileTypeCache = new ConcurrentHashMap<>();
+
+ private final ProfileTypeI18nLocalizationService profileTypeI18nLocalizationService;
+ private final Bundle bundle;
+
+ @Activate
+ public PhonebookProfileFactory(
+ final @Reference ProfileTypeI18nLocalizationService profileTypeI18nLocalizationService,
+ final @Reference BundleResolver bundleResolver) {
+ this.profileTypeI18nLocalizationService = profileTypeI18nLocalizationService;
+ this.bundle = bundleResolver.resolveBundle(PhonebookProfileFactory.class);
+ }
+
@Override
public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback,
ProfileContext profileContext) {
@Override
public Collection<ProfileTypeUID> getSupportedProfileTypeUIDs() {
- return Collections.singleton(PhonebookProfile.PHONEBOOK_PROFILE_TYPE_UID);
+ return Set.of(PhonebookProfile.PHONEBOOK_PROFILE_TYPE_UID);
}
@Override
public Collection<ProfileType> getProfileTypes(@Nullable Locale locale) {
- return Collections.singleton(PhonebookProfile.PHONEBOOK_PROFILE_TYPE);
+ return Set.of(createLocalizedProfileType(PhonebookProfile.PHONEBOOK_PROFILE_TYPE, locale));
+ }
+
+ private ProfileType createLocalizedProfileType(ProfileType profileType, @Nullable Locale locale) {
+ final LocalizedKey localizedKey = new LocalizedKey(profileType.getUID(),
+ locale != null ? locale.toLanguageTag() : null);
+
+ final ProfileType cachedlocalizedProfileType = localizedProfileTypeCache.get(localizedKey);
+ if (cachedlocalizedProfileType != null) {
+ return cachedlocalizedProfileType;
+ }
+
+ final ProfileType localizedProfileType = profileTypeI18nLocalizationService.createLocalizedProfileType(bundle,
+ profileType, locale);
+ if (localizedProfileType != null) {
+ localizedProfileTypeCache.put(localizedKey, localizedProfileType);
+ return localizedProfileType;
+ } else {
+ return profileType;
+ }
}
/**
}
}
- private Stream<ParameterOption> createPhonebookList(Map.Entry<ThingUID, PhonebookProvider> entry) {
+ private List<ParameterOption> createPhonebookList(Map.Entry<ThingUID, PhonebookProvider> entry) {
String thingUid = UIDUtils.encode(entry.getKey().toString());
String thingName = entry.getValue().getFriendlyName();
- Stream<ParameterOption> parameterOptions = entry.getValue().getPhonebooks().stream()
+ List<ParameterOption> parameterOptions = entry.getValue().getPhonebooks().stream()
.map(phonebook -> new ParameterOption(thingUid + ":" + UIDUtils.encode(phonebook.getName()),
- thingName + " " + phonebook.getName()));
+ thingName + " - " + phonebook.getName()))
+ .collect(Collectors.toList());
- if (parameterOptions.count() > 0) {
- return Stream.concat(Stream.of(new ParameterOption(thingUid, thingName)), parameterOptions);
+ if (parameterOptions.size() > 0) {
+ parameterOptions.add(new ParameterOption(thingUid, thingName));
}
return parameterOptions;
@Nullable Locale locale) {
if (uri.getSchemeSpecificPart().equals(PhonebookProfile.PHONEBOOK_PROFILE_TYPE_UID.toString())
&& s.equals(PhonebookProfile.PHONEBOOK_PARAM)) {
- return phonebookProviders.entrySet().stream().flatMap(this::createPhonebookList)
- .collect(Collectors.toSet());
+ List<ParameterOption> parameterOptions = new ArrayList<>();
+ for (Map.Entry<ThingUID, PhonebookProvider> entry : phonebookProviders.entrySet()) {
+ parameterOptions.addAll(createPhonebookList(entry));
+ }
+ parameterOptions.sort(comparing(o -> o.getLabel()));
+ return parameterOptions;
}
return null;
}
@Override
public Optional<String> lookupNumber(String number, int matchCount) {
- String matchString = matchCount < number.length() ? number.substring(number.length() - matchCount) : number;
- logger.trace("matchString for {} is {}", number, matchString);
- return phonebook.keySet().stream().filter(n -> n.endsWith(matchString)).findAny().map(phonebook::get);
+ String matchString = matchCount > 0 && matchCount < number.length()
+ ? number.substring(number.length() - matchCount)
+ : number;
+ logger.trace("matchString for '{}' is '{}'", number, matchString);
+ return matchString.isBlank() ? Optional.empty()
+ : phonebook.keySet().stream().filter(n -> n.endsWith(matchString)).findFirst().map(phonebook::get);
}
@Override
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
+
<config-description uri="profile:transform:PHONEBOOK">
<parameter name="phonebook" type="text" required="true">
- <label>Phonebook</label>
- <description>The name of the the phonebook</description>
+ <label>Phone book</label>
+ <description>The name of the the phone book</description>
</parameter>
- <parameter name="matchCount" type="text" required="false">
+ <parameter name="matchCount" type="integer" min="0" step="1">
<label>Match Count</label>
<description>The number of digits matching the incoming value, counted from far right (default is 0 = all matching)</description>
+ <default>0</default>
+ </parameter>
+ <parameter name="phoneNumberIndex" type="integer" min="0" max="1" step="1">
+ <label>Phone Number Index</label>
+ <description>The index of the phone number to be resolved from a CallItem state (StringListType), 0 or 1 (default is
+ 0)</description>
+ <default>0</default>
</parameter>
</config-description>
</config-description:config-descriptions>
--- /dev/null
+profile-type.transform.PHONEBOOK.label = Telefonbuch
+profile.config.transform.PHONEBOOK.phonebook.label = Telefonbuch
+profile.config.transform.PHONEBOOK.phonebook.description = Der Name des Telefonbuches.
+profile.config.transform.PHONEBOOK.matchCount.label = Übereinstimmungen
+profile.config.transform.PHONEBOOK.matchCount.description = Die Anzahl der Ziffern, die mit dem eingehenden Wert übereinstimmen, von rechts gezählt (Vorgabe ist 0 = alle müssen übereinstimmen).
+profile.config.transform.PHONEBOOK.phoneNumberIndex.label = Telefonnummern-Index
+profile.config.transform.PHONEBOOK.phoneNumberIndex.description = Der Index der Telefonnummer, die aus einem CallItem-State (StringListType) aufgelöst werden soll, 0 oder 1 (Vorgabe ist 0).
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.tr064.internal.phonebook;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.mockito.MockitoAnnotations.openMocks;
+import static org.openhab.binding.tr064.internal.Tr064BindingConstants.*;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mock;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.StringListType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.profiles.ProfileCallback;
+import org.openhab.core.thing.profiles.ProfileContext;
+import org.openhab.core.thing.profiles.StateProfile;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.openhab.core.util.UIDUtils;
+
+/**
+ *
+ * @author Christoph Weitkamp - Initial contribution
+ */
+class PhonebookProfileTest {
+
+ private static final String INTERNAL_PHONE_NUMBER = "999";
+ private static final String OTHER_PHONE_NUMBER = "555-456";
+ private static final String JOHN_DOES_PHONE_NUMBER = "12345";
+ private static final String JOHN_DOES_NAME = "John Doe";
+ private static final ThingUID THING_UID = new ThingUID(BINDING_ID, THING_TYPE_FRITZBOX.getId(), "test");
+ private static final String MY_PHONEBOOK = UIDUtils.encode(THING_UID.getAsString()) + ":MyPhonebook";
+
+ @NonNullByDefault
+ public static class ParameterSet {
+ public final State state;
+ public final State resultingState;
+ public final @Nullable Object matchCount;
+ public final @Nullable Object phoneNumberIndex;
+
+ public ParameterSet(State state, State resultingState, @Nullable Object matchCount,
+ @Nullable Object phoneNumberIndex) {
+ this.state = state;
+ this.resultingState = resultingState;
+ this.matchCount = matchCount;
+ this.phoneNumberIndex = phoneNumberIndex;
+ }
+ }
+
+ public static Collection<Object[]> parameters() {
+ return Arrays.asList(new Object[][] { //
+ { new ParameterSet(UnDefType.UNDEF, UnDefType.UNDEF, null, null) }, //
+ { new ParameterSet(new StringType(JOHN_DOES_PHONE_NUMBER), new StringType(JOHN_DOES_NAME), null,
+ null) }, //
+ { new ParameterSet(new StringType(JOHN_DOES_PHONE_NUMBER), new StringType(JOHN_DOES_NAME),
+ BigDecimal.ONE, null) }, //
+ { new ParameterSet(new StringType(JOHN_DOES_PHONE_NUMBER), new StringType(JOHN_DOES_NAME), "3", null) }, //
+ { new ParameterSet(new StringListType(JOHN_DOES_PHONE_NUMBER, INTERNAL_PHONE_NUMBER),
+ new StringType(JOHN_DOES_NAME), null, null) }, //
+ { new ParameterSet(new StringListType(JOHN_DOES_PHONE_NUMBER, INTERNAL_PHONE_NUMBER),
+ new StringType(JOHN_DOES_NAME), null, BigDecimal.ZERO) }, //
+ { new ParameterSet(new StringListType(INTERNAL_PHONE_NUMBER, JOHN_DOES_PHONE_NUMBER),
+ new StringType(JOHN_DOES_NAME), null, BigDecimal.ONE) }, //
+ { new ParameterSet(new StringType(OTHER_PHONE_NUMBER), new StringType(OTHER_PHONE_NUMBER), null,
+ null) }, //
+ { new ParameterSet(new StringListType(OTHER_PHONE_NUMBER, INTERNAL_PHONE_NUMBER),
+ new StringType(OTHER_PHONE_NUMBER), null, null) }, //
+ { new ParameterSet(new StringListType(OTHER_PHONE_NUMBER, INTERNAL_PHONE_NUMBER),
+ new StringType(OTHER_PHONE_NUMBER), null, BigDecimal.ZERO) }, //
+ { new ParameterSet(new StringListType(INTERNAL_PHONE_NUMBER, OTHER_PHONE_NUMBER),
+ new StringType(OTHER_PHONE_NUMBER), null, BigDecimal.ONE) }, //
+ });
+ }
+
+ private AutoCloseable mocksCloseable;
+
+ private @Mock ProfileCallback mockCallback;
+ private @Mock ProfileContext mockContext;
+ private @Mock PhonebookProvider mockPhonebookProvider;
+
+ @NonNullByDefault
+ private final Phonebook phonebook = new Phonebook() {
+ @Override
+ public Optional<String> lookupNumber(String number, int matchCount) {
+ switch (number) {
+ case JOHN_DOES_PHONE_NUMBER:
+ return Optional.of(JOHN_DOES_NAME);
+ default:
+ return Optional.empty();
+ }
+ }
+
+ @Override
+ public String getName() {
+ return MY_PHONEBOOK;
+ }
+ };
+
+ @BeforeEach
+ public void setup() {
+ mocksCloseable = openMocks(this);
+
+ when(mockPhonebookProvider.getPhonebookByName(any(String.class))).thenReturn(Optional.of(phonebook));
+ when(mockPhonebookProvider.getPhonebooks()).thenReturn(Set.of(phonebook));
+ }
+
+ @AfterEach
+ public void afterEach() throws Exception {
+ mocksCloseable.close();
+ }
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testPhonebookProfileResolvesPhoneNumber(ParameterSet parameterSet) {
+ StateProfile profile = initProfile(MY_PHONEBOOK, parameterSet.matchCount, parameterSet.phoneNumberIndex);
+ verifySendUpdate(profile, parameterSet.state, parameterSet.resultingState);
+ }
+
+ private StateProfile initProfile(Object phonebookName, @Nullable Object matchCount,
+ @Nullable Object phoneNumberIndex) {
+ Map<String, Object> properties = new HashMap<>();
+ properties.put(PhonebookProfile.PHONEBOOK_PARAM, phonebookName);
+ if (matchCount != null) {
+ properties.put(PhonebookProfile.MATCH_COUNT_PARAM, matchCount);
+ }
+ if (phoneNumberIndex != null) {
+ properties.put(PhonebookProfile.PHONE_NUMBER_INDEX_PARAM, phoneNumberIndex);
+ }
+ when(mockContext.getConfiguration()).thenReturn(new Configuration(properties));
+ return new PhonebookProfile(mockCallback, mockContext, Map.of(THING_UID, mockPhonebookProvider));
+ }
+
+ private void verifySendUpdate(StateProfile profile, State state, State expectedState) {
+ reset(mockCallback);
+ profile.onStateUpdateFromHandler(state);
+ verify(mockCallback, times(1)).sendUpdate(eq(expectedState));
+ }
+}