]> git.basschouten.com Git - openhab-addons.git/blob
68948366d6713909118f158165fb97a225f8e70b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.modbus.internal;
14
15 import java.util.ArrayList;
16 import java.util.List;
17 import java.util.Optional;
18 import java.util.regex.Matcher;
19 import java.util.regex.Pattern;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.core.library.types.DecimalType;
24 import org.openhab.core.library.types.OnOffType;
25 import org.openhab.core.library.types.OpenClosedType;
26 import org.openhab.core.transform.TransformationException;
27 import org.openhab.core.transform.TransformationHelper;
28 import org.openhab.core.transform.TransformationService;
29 import org.openhab.core.types.Command;
30 import org.openhab.core.types.TypeParser;
31 import org.osgi.framework.BundleContext;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 /**
36  * Class describing transformation of a command or state.
37  *
38  * Inspired from other openHAB binding "Transformation" classes.
39  *
40  * @author Sami Salonen - Initial contribution
41  *
42  */
43 @NonNullByDefault
44 public class SingleValueTransformation implements ValueTransformation {
45
46     public static final String TRANSFORM_DEFAULT = "default";
47     public static final ValueTransformation IDENTITY_TRANSFORMATION = new SingleValueTransformation(TRANSFORM_DEFAULT,
48             null, null);
49
50     /** RegEx to extract and parse a function String <code>'(.*?)\((.*)\)'</code> */
51     private static final Pattern EXTRACT_FUNCTION_PATTERN_OLD = Pattern.compile("(?<service>.*?)\\((?<arg>.*)\\)");
52     private static final Pattern EXTRACT_FUNCTION_PATTERN_NEW = Pattern.compile("(?<service>.*?):(?<arg>.*)");
53
54     /**
55      * Ordered list of types that are tried out first when trying to parse transformed command
56      */
57     private static final List<Class<? extends Command>> DEFAULT_TYPES = new ArrayList<>();
58     static {
59         DEFAULT_TYPES.add(DecimalType.class);
60         DEFAULT_TYPES.add(OpenClosedType.class);
61         DEFAULT_TYPES.add(OnOffType.class);
62     }
63
64     private final Logger logger = LoggerFactory.getLogger(SingleValueTransformation.class);
65
66     private final @Nullable String transformation;
67     final @Nullable String transformationServiceName;
68     final @Nullable String transformationServiceParam;
69
70     /**
71      *
72      * @param transformation either FUN(VAL) (standard transformation syntax), default (identity transformation
73      *            (output equals input)) or some other value (output is a constant). Futhermore, empty string is
74      *            considered the same way as "default".
75      */
76     public SingleValueTransformation(@Nullable String transformation) {
77         this.transformation = transformation;
78         //
79         // Parse transformation configuration here on construction, but delay the
80         // construction of TransformationService to call-time
81         if (transformation == null || transformation.isEmpty() || transformation.equalsIgnoreCase(TRANSFORM_DEFAULT)) {
82             // no-op (identity) transformation
83             transformationServiceName = null;
84             transformationServiceParam = null;
85         } else {
86             int colonIndex = transformation.indexOf(":");
87             int parenthesisOpenIndex = transformation.indexOf("(");
88
89             final Matcher matcher;
90             if (parenthesisOpenIndex != -1 && (colonIndex == -1 || parenthesisOpenIndex < colonIndex)) {
91                 matcher = EXTRACT_FUNCTION_PATTERN_OLD.matcher(transformation);
92             } else {
93                 matcher = EXTRACT_FUNCTION_PATTERN_NEW.matcher(transformation);
94             }
95             if (matcher.matches()) {
96                 matcher.reset();
97                 matcher.find();
98                 transformationServiceName = matcher.group("service");
99                 transformationServiceParam = matcher.group("arg");
100             } else {
101                 logger.debug(
102                         "Given transformation configuration '{}' did not match the FUN(VAL) pattern. Transformation output will be constant '{}'",
103                         transformation, transformation);
104                 transformationServiceName = null;
105                 transformationServiceParam = null;
106             }
107         }
108     }
109
110     /**
111      * For testing, thus package visibility by design
112      *
113      * @param transformation
114      * @param transformationServiceName
115      * @param transformationServiceParam
116      */
117     SingleValueTransformation(String transformation, @Nullable String transformationServiceName,
118             @Nullable String transformationServiceParam) {
119         this.transformation = transformation;
120         this.transformationServiceName = transformationServiceName;
121         this.transformationServiceParam = transformationServiceParam;
122     }
123
124     @Override
125     public String transform(BundleContext context, String value) {
126         String transformedResponse;
127         String transformationServiceName = this.transformationServiceName;
128         String transformationServiceParam = this.transformationServiceParam;
129
130         if (transformationServiceName != null) {
131             try {
132                 if (transformationServiceParam == null) {
133                     throw new TransformationException(
134                             "transformation service parameter is missing! Invalid transform?");
135                 }
136                 @Nullable
137                 TransformationService transformationService = TransformationHelper.getTransformationService(context,
138                         transformationServiceName);
139                 if (transformationService != null) {
140                     transformedResponse = transformationService.transform(transformationServiceParam, value);
141                 } else {
142                     transformedResponse = value;
143                     logger.warn("couldn't transform response because transformationService of type '{}' is unavailable",
144                             transformationServiceName);
145                 }
146             } catch (TransformationException te) {
147                 logger.error("transformation throws exception [transformation={}, response={}]", transformation, value,
148                         te);
149
150                 // in case of an error we return the response without any
151                 // transformation
152                 transformedResponse = value;
153             }
154         } else if (isIdentityTransform()) {
155             // identity transformation
156             transformedResponse = value;
157         } else {
158             // pass value as is
159             transformedResponse = this.transformation;
160         }
161
162         return transformedResponse == null ? "" : transformedResponse;
163     }
164
165     @Override
166     public boolean isIdentityTransform() {
167         return TRANSFORM_DEFAULT.equalsIgnoreCase(this.transformation);
168     }
169
170     public static Optional<Command> tryConvertToCommand(String transformed) {
171         return Optional.ofNullable(TypeParser.parseCommand(DEFAULT_TYPES, transformed));
172     }
173
174     @Override
175     public String toString() {
176         return "SingleValueTransformation [transformation=" + transformation + ", transformationServiceName="
177                 + transformationServiceName + ", transformationServiceParam=" + transformationServiceParam + "]";
178     }
179 }