org.openhab.binding.modbus.internal.Transformation.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.modbus.internal.Transformation.java

Source

/**
 * Copyright (c) 2010-2019 by the respective copyright holders.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.openhab.binding.modbus.internal;

import static org.apache.commons.lang.StringUtils.isEmpty;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.StandardToStringStyle;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.transform.TransformationHelper;
import org.openhab.core.transform.TransformationService;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.TypeParser;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Class describing transformation of a command or state.
 *
 * Inspired from other openHAB binding "Transformation" classes.
 *
 * @author Sami Salonen
 * @since 1.10.0
 *
 */
public class Transformation {

    interface TransformationHelperWrapper {
        public TransformationService getTransformationService(BundleContext context,
                String transformationServiceName);
    }

    private static final class TransformationHelperWrapperImpl implements TransformationHelperWrapper {
        @Override
        public TransformationService getTransformationService(BundleContext context,
                String transformationServiceName) {
            return TransformationHelper.getTransformationService(context, transformationServiceName);
        }
    }

    public static final String TRANSFORM_DEFAULT = "default";
    public static final Transformation IDENTITY_TRANSFORMATION = new Transformation(TRANSFORM_DEFAULT, null, null);
    private static final TransformationHelperWrapper DEFAULT_TRANSFORMATION_HELPER = new TransformationHelperWrapperImpl();

    /** RegEx to extract and parse a function String <code>'(.*?)\((.*)\)'</code> */
    private static final Pattern EXTRACT_FUNCTION_PATTERN = Pattern.compile("(?<service>.*?)\\((?<arg>.*)\\)");

    /**
     * Ordered list of types that are tried out first when trying to parse transformed command
     */
    private final static List<Class<? extends Command>> DEFAULT_TYPES = new ArrayList<>();
    static {
        DEFAULT_TYPES.add(DecimalType.class);
        DEFAULT_TYPES.add(OpenClosedType.class);
        DEFAULT_TYPES.add(OnOffType.class);
    }

    static private final Logger logger = LoggerFactory.getLogger(Transformation.class);

    private static StandardToStringStyle toStringStyle = new StandardToStringStyle();

    static {
        toStringStyle.setUseShortClassName(true);
    }

    private final String transformation;
    private final String transformationServiceName;
    private final String transformationServiceParam;

    private TransformationHelperWrapper transformationHelper = DEFAULT_TRANSFORMATION_HELPER;

    /**
     *
     * @param transformation either FUN(VAL) (standard transformation syntax), default (identity transformation
     *            (output equals input)) or some other value (output is a constant). Futhermore, empty string is
     *            considered the same way as "default".
     */
    public Transformation(String transformation) {
        this.transformation = transformation;
        //
        // Parse transformation configuration here on construction, but delay the
        // construction of TransformationService to call-time
        if (isEmpty(transformation) || transformation.equalsIgnoreCase(TRANSFORM_DEFAULT)) {
            // no-op (identity) transformation
            transformationServiceName = null;
            transformationServiceParam = null;
        } else {
            Matcher matcher = EXTRACT_FUNCTION_PATTERN.matcher(transformation);
            if (matcher.matches()) {
                matcher.reset();
                matcher.find();
                transformationServiceName = matcher.group("service");
                transformationServiceParam = matcher.group("arg");
            } else {
                logger.debug(
                        "Given transformation configuration '{}' did not match the FUN(VAL) pattern. Transformation output will be constant '{}'",
                        transformation, transformation);
                transformationServiceName = null;
                transformationServiceParam = null;
            }
        }
    }

    /**
     * For testing. Package level visibility on purpose
     *
     * @param transformation
     * @param transformationHelper
     */
    Transformation(String transformation, TransformationHelperWrapper transformationHelper) {
        this(transformation);
        this.transformationHelper = transformationHelper;
    }

    /**
     * For testing, thus package visibility by design
     *
     * @param transformation
     * @param transformationServiceName
     * @param transformationServiceParam
     */
    Transformation(String transformation, String transformationServiceName, String transformationServiceParam) {
        this.transformation = transformation;
        this.transformationServiceName = transformationServiceName;
        this.transformationServiceParam = transformationServiceParam;
    }

    private String transform(String value) {
        String transformedResponse;

        if (hasTransformationService()) {
            try {
                TransformationService transformationService = this.transformationHelper
                        .getTransformationService(ModbusActivator.getContext(), transformationServiceName);
                if (transformationService != null) {
                    transformedResponse = transformationService.transform(transformationServiceParam, value);
                } else {
                    transformedResponse = value;
                    logger.warn(
                            "couldn't transform response because transformationService of type '{}' is unavailable",
                            transformationServiceName);
                }
            } catch (Exception te) {
                logger.error("transformation throws exception [transformation={}, response={}]", transformation,
                        value, te);

                // in case of an error we return the response without any
                // transformation
                transformedResponse = value;
            }
        } else if (isIdentityTransform()) {
            // identity transformation
            transformedResponse = value;
        } else {
            // pass value as is
            transformedResponse = this.transformation;
        }

        logger.debug("transformed response is '{}'", transformedResponse);

        return transformedResponse;
    }

    private boolean isIdentityTransform() {
        return TRANSFORM_DEFAULT.equalsIgnoreCase(this.transformation);
    }

    /**
     * Transform command to another command using this transformation
     *
     *
     *
     * @param types types types to used to parse the transformation result. First one to match is used. Some types are
     *            always tried first even if they are not list in the types (see DEFAULT_TYPES constant)
     * @param command
     * @return Transformed command, or null if no transformation was possible
     */
    public Command transformCommand(List<Class<? extends Command>> types, Command command) {
        if (isIdentityTransform()) {
            // optimization, do not convert command->string->command if the transformation is identity transform
            return command;
        }
        final String commandAsString = command.toString();
        final String transformed = transform(commandAsString);

        Command transformedCommand = null;
        transformedCommand = TypeParser.parseCommand(DEFAULT_TYPES, transformed);
        if (transformedCommand == null) {
            transformedCommand = TypeParser.parseCommand(types, transformed);
        }
        if (transformedCommand == null) {
            logger.warn(
                    "Could not transform item  command '{}' to a Command. Command as string '{}', "
                            + "transformed string '{}', transformation '{}'",
                    command, commandAsString, transformed, transformation);
        } else {
            logger.debug(
                    "Transformed item command '{}' to a command {}. Command as string '{}', "
                            + "transformed string '{}', transformation '{}'",
                    command, transformedCommand, commandAsString, transformed, transformation);
        }
        return transformedCommand;
    }

    /**
     * Transform state to another state using this transformation
     *
     * @param types types to used to parse the transformation result
     * @param command
     * @return Transformed command, or null if no transformation was possible
     */
    public State transformState(List<Class<? extends State>> types, State state) {
        if (isIdentityTransform()) {
            // optimization, do not convert state->string->state if the transformation is identity transform
            return state;
        }
        final String stateAsString = state.toString();
        final String transformed = transform(stateAsString);
        State transformedState = TypeParser.parseState(types, transformed);
        if (transformedState == null) {
            logger.warn(
                    "Could not transform item state '{}' (of type {}) to a State! Command as string '{}', "
                            + "transformed string '{}', transformation '{}'",
                    state, state.getClass().getSimpleName(), stateAsString, transformed, transformation);
        } else {
            logger.debug(
                    "Transformed item state '{}' (of type {}) to a state {} (of type {}). Input state as string '{}', "
                            + "transformed string '{}', transformation '{}'",
                    state, state.getClass().getSimpleName(), transformedState,
                    transformedState.getClass().getSimpleName(), stateAsString, transformed, transformation);
        }
        return transformedState;
    }

    public boolean hasTransformationService() {
        return transformationServiceName != null;
    }

    /**
     * For testing only. Package level visibility on purpose
     *
     * @param transformationHelper
     */
    void setTransformationHelper(TransformationHelperWrapper transformationHelper) {
        this.transformationHelper = transformationHelper;
    }

    @Override
    public boolean equals(Object obj) {
        if (null == obj) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Transformation)) {
            return false;
        }
        Transformation that = (Transformation) obj;
        EqualsBuilder eb = new EqualsBuilder();
        if (hasTransformationService()) {
            eb.append(this.transformationServiceName, that.transformationServiceName);
            eb.append(this.transformationServiceParam, that.transformationServiceParam);
        } else {
            eb.append(this.transformation, that.transformation);
        }
        return eb.isEquals();
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this, toStringStyle).append("tranformation", transformation)
                .append("transformationServiceName", transformationServiceName)
                .append("transformationServiceParam", transformationServiceParam).toString();
    }
}