2 * Copyright (c) 2010-2020 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.modbus.internal;
15 import static org.apache.commons.lang.StringUtils.isEmpty;
17 import java.util.ArrayList;
18 import java.util.List;
19 import java.util.Optional;
20 import java.util.regex.Matcher;
21 import java.util.regex.Pattern;
23 import org.apache.commons.lang.builder.EqualsBuilder;
24 import org.apache.commons.lang.builder.StandardToStringStyle;
25 import org.apache.commons.lang.builder.ToStringBuilder;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.core.library.types.DecimalType;
29 import org.openhab.core.library.types.OnOffType;
30 import org.openhab.core.library.types.OpenClosedType;
31 import org.openhab.core.transform.TransformationException;
32 import org.openhab.core.transform.TransformationHelper;
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.TypeParser;
37 import org.osgi.framework.BundleContext;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * Class describing transformation of a command or state.
44 * Inspired from other openHAB binding "Transformation" classes.
46 * @author Sami Salonen - Initial contribution
50 public class Transformation {
52 public static final String TRANSFORM_DEFAULT = "default";
53 public static final Transformation IDENTITY_TRANSFORMATION = new Transformation(TRANSFORM_DEFAULT, null, null);
55 /** RegEx to extract and parse a function String <code>'(.*?)\((.*)\)'</code> */
56 private static final Pattern EXTRACT_FUNCTION_PATTERN = Pattern.compile("(?<service>.*?)\\((?<arg>.*)\\)");
59 * Ordered list of types that are tried out first when trying to parse transformed command
61 private static final List<Class<? extends Command>> DEFAULT_TYPES = new ArrayList<>();
63 DEFAULT_TYPES.add(DecimalType.class);
64 DEFAULT_TYPES.add(OpenClosedType.class);
65 DEFAULT_TYPES.add(OnOffType.class);
68 private final Logger logger = LoggerFactory.getLogger(Transformation.class);
70 private static StandardToStringStyle toStringStyle = new StandardToStringStyle();
73 toStringStyle.setUseShortClassName(true);
76 private final @Nullable String transformation;
77 private final @Nullable String transformationServiceName;
78 private final @Nullable String transformationServiceParam;
82 * @param transformation either FUN(VAL) (standard transformation syntax), default (identity transformation
83 * (output equals input)) or some other value (output is a constant). Futhermore, empty string is
84 * considered the same way as "default".
86 public Transformation(@Nullable String transformation) {
87 this.transformation = transformation;
89 // Parse transformation configuration here on construction, but delay the
90 // construction of TransformationService to call-time
91 if (isEmpty(transformation) || transformation.equalsIgnoreCase(TRANSFORM_DEFAULT)) {
92 // no-op (identity) transformation
93 transformationServiceName = null;
94 transformationServiceParam = null;
96 Matcher matcher = EXTRACT_FUNCTION_PATTERN.matcher(transformation);
97 if (matcher.matches()) {
100 transformationServiceName = matcher.group("service");
101 transformationServiceParam = matcher.group("arg");
104 "Given transformation configuration '{}' did not match the FUN(VAL) pattern. Transformation output will be constant '{}'",
105 transformation, transformation);
106 transformationServiceName = null;
107 transformationServiceParam = null;
113 * For testing, thus package visibility by design
115 * @param transformation
116 * @param transformationServiceName
117 * @param transformationServiceParam
119 Transformation(String transformation, @Nullable String transformationServiceName,
120 @Nullable String transformationServiceParam) {
121 this.transformation = transformation;
122 this.transformationServiceName = transformationServiceName;
123 this.transformationServiceParam = transformationServiceParam;
126 public String transform(BundleContext context, String value) {
127 String transformedResponse;
128 String transformationServiceName = this.transformationServiceName;
129 String transformationServiceParam = this.transformationServiceParam;
131 if (transformationServiceName != null) {
133 if (transformationServiceParam == null) {
134 throw new TransformationException(
135 "transformation service parameter is missing! Invalid transform?");
138 TransformationService transformationService = TransformationHelper.getTransformationService(context,
139 transformationServiceName);
140 if (transformationService != null) {
141 transformedResponse = transformationService.transform(transformationServiceParam, value);
143 transformedResponse = value;
144 logger.warn("couldn't transform response because transformationService of type '{}' is unavailable",
145 transformationServiceName);
147 } catch (TransformationException te) {
148 logger.error("transformation throws exception [transformation={}, response={}]", transformation, value,
151 // in case of an error we return the response without any
153 transformedResponse = value;
155 } else if (isIdentityTransform()) {
156 // identity transformation
157 transformedResponse = value;
160 transformedResponse = this.transformation;
163 return transformedResponse == null ? "" : transformedResponse;
166 public boolean isIdentityTransform() {
167 return TRANSFORM_DEFAULT.equalsIgnoreCase(this.transformation);
170 public static Optional<Command> tryConvertToCommand(String transformed) {
171 Optional<Command> transformedCommand = Optional.ofNullable(TypeParser.parseCommand(DEFAULT_TYPES, transformed));
172 return transformedCommand;
176 * Transform state to another state using this transformation
179 * @param types types to used to parse the transformation result
181 * @return Transformed command, or null if no transformation was possible
183 public @Nullable State transformState(BundleContext context, List<Class<? extends State>> types, State state) {
184 // Note that even identity transformations go through the State -> String -> State steps. This does add some
185 // overhead but takes care of DecimalType -> PercentType conversions, for example.
186 final String stateAsString = state.toString();
187 final String transformed = transform(context, stateAsString);
188 return TypeParser.parseState(types, transformed);
191 public boolean hasTransformationService() {
192 return transformationServiceName != null;
196 public boolean equals(@Nullable Object obj) {
203 if (!(obj instanceof Transformation)) {
206 Transformation that = (Transformation) obj;
207 EqualsBuilder eb = new EqualsBuilder();
208 if (hasTransformationService()) {
209 eb.append(this.transformationServiceName, that.transformationServiceName);
210 eb.append(this.transformationServiceParam, that.transformationServiceParam);
212 eb.append(this.transformation, that.transformation);
214 return eb.isEquals();
218 public String toString() {
219 return new ToStringBuilder(this, toStringStyle).append("tranformation", transformation)
220 .append("transformationServiceName", transformationServiceName)
221 .append("transformationServiceParam", transformationServiceParam).toString();