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.Optional;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.core.config.core.Configuration;
22 import org.openhab.core.library.CoreItemFactory;
23 import org.openhab.core.library.types.StringListType;
24 import org.openhab.core.library.types.StringType;
25 import org.openhab.core.thing.ThingUID;
26 import org.openhab.core.thing.profiles.ProfileCallback;
27 import org.openhab.core.thing.profiles.ProfileContext;
28 import org.openhab.core.thing.profiles.ProfileType;
29 import org.openhab.core.thing.profiles.ProfileTypeBuilder;
30 import org.openhab.core.thing.profiles.ProfileTypeUID;
31 import org.openhab.core.thing.profiles.StateProfile;
32 import org.openhab.core.transform.TransformationService;
33 import org.openhab.core.types.Command;
34 import org.openhab.core.types.State;
35 import org.openhab.core.types.UnDefType;
36 import org.openhab.core.util.UIDUtils;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
41 * The {@link PhonebookProfile} class provides a profile for resolving phone number strings to names
43 * @author Jan N. Klug - Initial contribution
46 public class PhonebookProfile implements StateProfile {
47 public static final ProfileTypeUID PHONEBOOK_PROFILE_TYPE_UID = new ProfileTypeUID(
48 TransformationService.TRANSFORM_PROFILE_SCOPE, "PHONEBOOK");
49 public static final ProfileType PHONEBOOK_PROFILE_TYPE = ProfileTypeBuilder //
50 .newState(PHONEBOOK_PROFILE_TYPE_UID, "Phonebook") //
51 .withSupportedItemTypesOfChannel(CoreItemFactory.CALL, CoreItemFactory.STRING) //
52 .withSupportedItemTypes(CoreItemFactory.STRING) //
55 public static final String PHONEBOOK_PARAM = "phonebook";
56 public static final String MATCH_COUNT_PARAM = "matchCount";
57 public static final String PHONE_NUMBER_INDEX_PARAM = "phoneNumberIndex";
59 private final Logger logger = LoggerFactory.getLogger(PhonebookProfile.class);
61 private final ProfileCallback callback;
63 private final @Nullable String phonebookName;
64 private final @Nullable ThingUID thingUID;
65 private final Map<ThingUID, PhonebookProvider> phonebookProviders;
66 private final int matchCount;
67 private final int phoneNumberIndex;
69 public PhonebookProfile(ProfileCallback callback, ProfileContext context,
70 Map<ThingUID, PhonebookProvider> phonebookProviders) {
71 this.callback = callback;
72 this.phonebookProviders = phonebookProviders;
74 Configuration configuration = context.getConfiguration();
75 Object phonebookParam = configuration.get(PHONEBOOK_PARAM);
76 Object matchCountParam = configuration.get(MATCH_COUNT_PARAM);
77 Object phoneNumberIndexParam = configuration.get(PHONE_NUMBER_INDEX_PARAM);
79 logger.debug("Profile configured with '{}'='{}', '{}'='{}', '{}'='{}'", PHONEBOOK_PARAM, phonebookParam,
80 MATCH_COUNT_PARAM, matchCountParam, PHONE_NUMBER_INDEX_PARAM, phoneNumberIndexParam);
83 String phonebookName = null;
85 int phoneNumberIndex = 0;
88 if (!(phonebookParam instanceof String)) {
89 throw new IllegalArgumentException("Parameter 'phonebook' need to be a String");
91 String[] phonebookParams = ((String) phonebookParam).split(":");
92 if (phonebookParams.length > 2) {
93 throw new IllegalArgumentException("Cannot split 'phonebook' parameter");
95 thingUID = new ThingUID(UIDUtils.decode(phonebookParams[0]));
96 if (phonebookParams.length == 2) {
97 phonebookName = UIDUtils.decode(phonebookParams[1]);
99 if (matchCountParam != null) {
100 if (matchCountParam instanceof BigDecimal) {
101 matchCount = ((BigDecimal) matchCountParam).intValue();
102 } else if (matchCountParam instanceof String) {
103 matchCount = Integer.parseInt((String) matchCountParam);
106 if (phoneNumberIndexParam != null) {
107 if (phoneNumberIndexParam instanceof BigDecimal) {
108 phoneNumberIndex = ((BigDecimal) phoneNumberIndexParam).intValue();
109 } else if (phoneNumberIndexParam instanceof String) {
110 phoneNumberIndex = Integer.parseInt((String) phoneNumberIndexParam);
113 } catch (IllegalArgumentException e) {
114 logger.warn("Cannot initialize PHONEBOOK transformation profile: {}. Profile will be inactive.",
119 this.thingUID = thingUID;
120 this.phonebookName = phonebookName;
121 this.matchCount = matchCount;
122 this.phoneNumberIndex = phoneNumberIndex;
126 public void onCommandFromItem(Command command) {
130 public void onCommandFromHandler(Command command) {
134 @SuppressWarnings("PMD.CompareObjectsWithEquals")
135 public void onStateUpdateFromHandler(State state) {
136 if (state instanceof UnDefType) {
137 // we cannot adjust UNDEF or NULL values, thus we simply apply them without reporting an error or warning
138 callback.sendUpdate(state);
140 if (state instanceof StringType) {
141 Optional<String> match = resolveNumber(state.toString());
142 State newState = match.map(name -> (State) new StringType(name)).orElse(state);
143 // Compare by reference to check if the name is mapped to the same state
144 if (newState == state) {
145 logger.debug("Number '{}' not found in phonebook '{}' from provider '{}'", state, phonebookName,
148 callback.sendUpdate(newState);
149 } else if (state instanceof StringListType) {
150 StringListType stringList = (StringListType) state;
152 String phoneNumber = stringList.getValue(phoneNumberIndex);
153 Optional<String> match = resolveNumber(phoneNumber);
154 final State newState;
155 if (match.isPresent()) {
156 newState = new StringType(match.get());
158 logger.debug("Number '{}' not found in phonebook '{}' from provider '{}'", phoneNumber,
159 phonebookName, thingUID);
160 newState = new StringType(phoneNumber);
162 callback.sendUpdate(newState);
163 } catch (IllegalArgumentException e) {
164 logger.debug("StringListType does not contain a number at index {}", phoneNumberIndex);
169 private Optional<String> resolveNumber(String phoneNumber) {
170 PhonebookProvider provider = phonebookProviders.get(thingUID);
171 if (provider == null) {
172 logger.warn("Could not get phonebook provider with thing UID '{}'.", thingUID);
173 return Optional.empty();
175 final String phonebookName = this.phonebookName;
176 if (phonebookName != null) {
177 return provider.getPhonebookByName(phonebookName).or(() -> {
178 logger.warn("Could not get phonebook '{}' from provider '{}'", phonebookName, thingUID);
179 return Optional.empty();
180 }).flatMap(phonebook -> phonebook.lookupNumber(phoneNumber, matchCount));
182 return provider.getPhonebooks().stream().map(p -> p.lookupNumber(phoneNumber, matchCount))
183 .filter(Optional::isPresent).map(Optional::get).findAny();
188 public ProfileTypeUID getProfileTypeUID() {
189 return PHONEBOOK_PROFILE_TYPE_UID;
193 public void onStateUpdateFromItem(State state) {