2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.tr064.internal.phonebook;
15 import java.math.BigDecimal;
17 import java.util.Objects;
18 import java.util.Optional;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.core.config.core.Configuration;
23 import org.openhab.core.library.CoreItemFactory;
24 import org.openhab.core.library.types.StringListType;
25 import org.openhab.core.library.types.StringType;
26 import org.openhab.core.thing.ThingUID;
27 import org.openhab.core.thing.profiles.ProfileCallback;
28 import org.openhab.core.thing.profiles.ProfileContext;
29 import org.openhab.core.thing.profiles.ProfileType;
30 import org.openhab.core.thing.profiles.ProfileTypeBuilder;
31 import org.openhab.core.thing.profiles.ProfileTypeUID;
32 import org.openhab.core.thing.profiles.StateProfile;
33 import org.openhab.core.transform.TransformationService;
34 import org.openhab.core.types.Command;
35 import org.openhab.core.types.State;
36 import org.openhab.core.types.UnDefType;
37 import org.openhab.core.util.UIDUtils;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * The {@link PhonebookProfile} class provides a profile for resolving phone number strings to names
44 * @author Jan N. Klug - Initial contribution
47 public class PhonebookProfile implements StateProfile {
48 public static final ProfileTypeUID PHONEBOOK_PROFILE_TYPE_UID = new ProfileTypeUID(
49 TransformationService.TRANSFORM_PROFILE_SCOPE, "PHONEBOOK");
50 public static final ProfileType PHONEBOOK_PROFILE_TYPE = ProfileTypeBuilder //
51 .newState(PHONEBOOK_PROFILE_TYPE_UID, "Phonebook") //
52 .withSupportedItemTypesOfChannel(CoreItemFactory.CALL, CoreItemFactory.STRING) //
53 .withSupportedItemTypes(CoreItemFactory.STRING) //
56 public static final String PHONEBOOK_PARAM = "phonebook";
57 public static final String MATCH_COUNT_PARAM = "matchCount";
58 public static final String PHONE_NUMBER_INDEX_PARAM = "phoneNumberIndex";
60 private final Logger logger = LoggerFactory.getLogger(PhonebookProfile.class);
62 private final ProfileCallback callback;
64 private final @Nullable String phonebookName;
65 private final @Nullable ThingUID thingUID;
66 private final Map<ThingUID, PhonebookProvider> phonebookProviders;
67 private final int matchCount;
68 private final int phoneNumberIndex;
70 public PhonebookProfile(ProfileCallback callback, ProfileContext context,
71 Map<ThingUID, PhonebookProvider> phonebookProviders) {
72 this.callback = callback;
73 this.phonebookProviders = phonebookProviders;
75 Configuration configuration = context.getConfiguration();
76 Object phonebookParam = configuration.get(PHONEBOOK_PARAM);
77 Object matchCountParam = configuration.get(MATCH_COUNT_PARAM);
78 Object phoneNumberIndexParam = configuration.get(PHONE_NUMBER_INDEX_PARAM);
80 logger.debug("Profile configured with '{}'='{}', '{}'='{}', '{}'='{}'", PHONEBOOK_PARAM, phonebookParam,
81 MATCH_COUNT_PARAM, matchCountParam, PHONE_NUMBER_INDEX_PARAM, phoneNumberIndexParam);
84 String phonebookName = null;
86 int phoneNumberIndex = 0;
89 if (!(phonebookParam instanceof String)) {
90 throw new IllegalArgumentException("Parameter 'phonebook' need to be a String");
92 String[] phonebookParams = ((String) phonebookParam).split(":");
93 if (phonebookParams.length > 2) {
94 throw new IllegalArgumentException("Cannot split 'phonebook' parameter");
96 thingUID = new ThingUID(UIDUtils.decode(phonebookParams[0]));
97 if (phonebookParams.length == 2) {
98 phonebookName = UIDUtils.decode(phonebookParams[1]);
100 if (matchCountParam != null) {
101 if (matchCountParam instanceof BigDecimal) {
102 matchCount = ((BigDecimal) matchCountParam).intValue();
103 } else if (matchCountParam instanceof String) {
104 matchCount = Integer.parseInt((String) matchCountParam);
107 if (phoneNumberIndexParam != null) {
108 if (phoneNumberIndexParam instanceof BigDecimal) {
109 phoneNumberIndex = ((BigDecimal) phoneNumberIndexParam).intValue();
110 } else if (phoneNumberIndexParam instanceof String) {
111 phoneNumberIndex = Integer.parseInt((String) phoneNumberIndexParam);
114 } catch (IllegalArgumentException e) {
115 logger.warn("Cannot initialize PHONEBOOK transformation profile: {}. Profile will be inactive.",
120 this.thingUID = thingUID;
121 this.phonebookName = phonebookName;
122 this.matchCount = matchCount;
123 this.phoneNumberIndex = phoneNumberIndex;
127 public void onCommandFromItem(Command command) {
131 public void onCommandFromHandler(Command command) {
135 @SuppressWarnings("PMD.CompareObjectsWithEquals")
136 public void onStateUpdateFromHandler(State state) {
137 if (state instanceof UnDefType) {
138 // we cannot adjust UNDEF or NULL values, thus we simply apply them without reporting an error or warning
139 callback.sendUpdate(state);
141 if (state instanceof StringType) {
142 Optional<String> match = resolveNumber(state.toString());
143 State newState = Objects.requireNonNull(match.map(name -> (State) new StringType(name)).orElse(state));
144 if (newState.equals(state)) {
145 logger.debug("Number '{}' not found in phonebook '{}' from provider '{}'", state, phonebookName,
148 callback.sendUpdate(newState);
149 } else if (state instanceof StringListType stringList) {
151 String phoneNumber = stringList.getValue(phoneNumberIndex);
152 Optional<String> match = resolveNumber(phoneNumber);
153 final State newState;
154 if (match.isPresent()) {
155 newState = new StringType(match.get());
157 logger.debug("Number '{}' not found in phonebook '{}' from provider '{}'", phoneNumber,
158 phonebookName, thingUID);
159 newState = new StringType(phoneNumber);
161 callback.sendUpdate(newState);
162 } catch (IllegalArgumentException e) {
163 logger.debug("StringListType does not contain a number at index {}", phoneNumberIndex);
168 private Optional<String> resolveNumber(String phoneNumber) {
169 PhonebookProvider provider = phonebookProviders.get(thingUID);
170 if (provider == null) {
171 logger.warn("Could not get phonebook provider with thing UID '{}'.", thingUID);
172 return Optional.empty();
174 final String phonebookName = this.phonebookName;
175 if (phonebookName != null) {
176 return provider.getPhonebookByName(phonebookName).or(() -> {
177 logger.warn("Could not get phonebook '{}' from provider '{}'", phonebookName, thingUID);
178 return Optional.empty();
179 }).flatMap(phonebook -> phonebook.lookupNumber(phoneNumber, matchCount));
181 return provider.getPhonebooks().stream().map(p -> p.lookupNumber(phoneNumber, matchCount))
182 .filter(Optional::isPresent).map(Optional::get).findAny();
187 public ProfileTypeUID getProfileTypeUID() {
188 return PHONEBOOK_PROFILE_TYPE_UID;
192 public void onStateUpdateFromItem(State state) {