/** * Copyright (c) 1997, 2015 by ProSyst Software GmbH and others. * 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.eclipse.smarthome.automation.core.util; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.smarthome.automation.Action; import org.eclipse.smarthome.automation.Condition; import org.eclipse.smarthome.automation.Trigger; import org.eclipse.smarthome.automation.core.internal.Connection; import org.eclipse.smarthome.automation.core.internal.RuntimeAction; import org.eclipse.smarthome.automation.core.internal.RuntimeCondition; import org.eclipse.smarthome.automation.core.internal.RuntimeRule; import org.eclipse.smarthome.automation.type.ActionType; import org.eclipse.smarthome.automation.type.ConditionType; import org.eclipse.smarthome.automation.type.Input; import org.eclipse.smarthome.automation.type.ModuleTypeRegistry; import org.eclipse.smarthome.automation.type.Output; import org.eclipse.smarthome.automation.type.TriggerType; /** * This class contains utility methods for comparison of data types between connected inputs and outputs of modules * participating in a rule. * * @author Ana Dimova - Initial contribution and API * @author Kai Kreuzer - refactored (managed) provider and registry implementation * @author Benedikt Niehues - validation of connection-types respects inheriting types * */ public class ConnectionValidator { private static ModuleTypeRegistry mtRegistry; public static void setRegistry(ModuleTypeRegistry mtRegistry) { ConnectionValidator.mtRegistry = mtRegistry; } /** * The method validates connections between inputs and outputs of modules participated in rule. It compares data * types of connected inputs and outputs and throws exception when there is a lack of coincidence. * * @param r rule which must be checked * @throws IllegalArgumentException when validation fails. */ public static void validateConnections(RuntimeRule r) { if (r == null) { throw new IllegalArgumentException("Validation of rule is failed! Rule must not be null!"); } ConnectionValidator.validateConnections(r.getTriggers(), r.getConditions(), r.getActions()); } /** * The method validates connections between inputs and outputs of the modules participated in a rule. It checks is * there unconnected required inputs and compatibility of data types of connected inputs and outputs. Throws * exception if they are incompatible. * * @param triggers is a list with triggers of the rule whose connections have to be validated * @param conditions is a list with conditions of the rule whose connections have to be validated * @param actions is a list with actions of the rule whose connections have to be validated * @throws IllegalArgumentException when validation fails. */ public static void validateConnections(List<Trigger> triggers, List<Condition> conditions, List<Action> actions) { if (!conditions.isEmpty()) { for (Condition condition : conditions) { validateConditionConnections((RuntimeCondition) condition, triggers); } } if (!actions.isEmpty()) { for (Action action : actions) { validateActionConnections((RuntimeAction) action, triggers, actions); } } } /** * The method validates connections between outputs of triggers and actions and action's inputs. It checks is there * unconnected required inputs and compatibility of data types of connected inputs and outputs. Throws exception if * they are incompatible. * * @param action is an Action module whose connections have to be validated * @param triggers is a list with triggers of the rule on which the action belongs * @param actions is a list with actions of the rule on which the action belongs * @throws IllegalArgumentException when validation fails. */ private static void validateActionConnections(RuntimeAction action, List<Trigger> triggers, List<Action> actions) { ActionType type = (ActionType) mtRegistry.get(action.getTypeUID()); // get module type of the condition if (type == null) { // if module type not exists in the system - throws exception throw new IllegalArgumentException("Action Type \"" + action.getTypeUID() + "\" does not exist!"); } List<Input> inputs = type.getInputs(); // get inputs of the condition according to module type definition // gets connected inputs from the condition module and put them into map Set<Connection> cons = action.getConnections(); Map<String, Connection> connectionsMap = new HashMap<String, Connection>(); Iterator<Connection> connectionsI = cons.iterator(); while (connectionsI.hasNext()) { Connection connection = connectionsI.next(); String inputName = connection.getInputName(); connectionsMap.put(inputName, connection); } // checks is there unconnected required inputs if (inputs != null && !inputs.isEmpty()) { for (Input input : inputs) { String name = input.getName(); Connection connection = connectionsMap.get(name); if (connection == null && input.isRequired()) { throw new IllegalArgumentException("Required input \"" + name + "\" of the condition \"" + action.getId() + "\" not connected"); } else if (connection != null) { checkConnection(connection, input, triggers, actions); } } } } /** * The method validates the connection between outputs of list of triggers and actions to the action's input. It * checks if the input is unconnected and compatibility of data types of the input and connected output. Throws * exception if they are incompatible. * * @param connection that should be validated * @param input that should be validated * @param triggers is a list with triggers of the rule on which the action belongs * @param actions is a list with actions of the rule on which the action belongs * @throws IllegalArgumentException when validation fails. */ private static void checkConnection(Connection connection, Input input, List<Trigger> triggers, List<Action> actions) { Map<String, Action> actionsMap = new HashMap<String, Action>(); for (Action a : actions) { actionsMap.put(a.getId(), a); } String moduleId = connection.getOuputModuleId(); Action action = actionsMap.get(moduleId); String msg = " Invalid Connection \"" + connection.getInputName() + "\" : "; if (moduleId != null && action != null) { String typeUID = action.getTypeUID(); ActionType actionType = (ActionType) mtRegistry.get(typeUID); if (actionType == null) { throw new IllegalArgumentException(msg + " Action Type with UID \"" + typeUID + "\" does not exist!"); } checkCompatibility(msg, connection, input, actionType.getOutputs()); } else { checkConnection(connection, input, triggers); } } /** * The method validates connections between trigger's outputs and condition's inputs. It checks is there unconnected * required inputs and compatibility of data types of connected inputs and outputs. Throws exception if they are * incompatible. * * @param condition is a Condition module whose connections have to be validated * @param triggers is a list with triggers of the rule on which the condition belongs * @throws IllegalArgumentException when validation fails. */ private static void validateConditionConnections(RuntimeCondition condition, List<Trigger> triggers) { ConditionType type = (ConditionType) mtRegistry.get(condition.getTypeUID()); // get module type of the condition if (type == null) { // if module type not exists in the system - throws exception throw new IllegalArgumentException("Condition Type \"" + condition.getTypeUID() + "\" does not exist!"); } List<Input> inputs = type.getInputs(); // get inputs of the condition according to module type definition // gets connected inputs from the condition module and put them into map Set<Connection> cons = condition.getConnections(); Map<String, Connection> connectionsMap = new HashMap<String, Connection>(); Iterator<Connection> connectionsI = cons.iterator(); while (connectionsI.hasNext()) { Connection connection = connectionsI.next(); String inputName = connection.getInputName(); connectionsMap.put(inputName, connection); } // checks is there unconnected required inputs if (inputs != null && !inputs.isEmpty()) { for (Input input : inputs) { String name = input.getName(); Connection connection = connectionsMap.get(name); if (connection != null) { checkConnection(connection, input, triggers); } else if (input.isRequired()) { throw new IllegalArgumentException("Required input \"" + name + "\" of the condition \"" + condition.getId() + "\" not connected"); } } } } /** * The method validates the connection between outputs of list of triggers to the action's or condition's input. It * checks if the input is unconnected and compatibility of data types of the input and connected output. Throws * exception if they are incompatible. * * @param connection that should be validated * @param input that should be validated * @param triggers is a list with triggers of the rule on which the action belongs * @throws IllegalArgumentException when validation fails. */ private static void checkConnection(Connection connection, Input input, List<Trigger> triggers) { Map<String, Trigger> triggersMap = new HashMap<String, Trigger>(); for (Trigger trigger : triggers) { triggersMap.put(trigger.getId(), trigger); } String moduleId = connection.getOuputModuleId(); String msg = " Invalid Connection \"" + connection.getInputName() + "\" : "; if (moduleId != null) { Trigger trigger = triggersMap.get(moduleId); if (trigger == null) { throw new IllegalArgumentException(msg + " Trigger with ID \"" + moduleId + "\" does not exist!"); } String triggerTypeUID = trigger.getTypeUID(); TriggerType triggerType = (TriggerType) mtRegistry.get(triggerTypeUID); if (triggerType == null) { throw new IllegalArgumentException( msg + " Trigger Type with UID \"" + triggerTypeUID + "\" does not exist!"); } checkCompatibility(msg, connection, input, triggerType.getOutputs()); } } /** * This method checks the compatibility of data types of the input and connected output. Throws * exception if they are incompatible. * * @param msg message should be extended with an information and thrown as exception when validation fails. * @param connection that should be validated * @param input that should be validated * @param outputs list with outputs of the module connected to the given input * @throws IllegalArgumentException when validation fails. */ private static void checkCompatibility(String msg, Connection connection, Input input, List<Output> outputs) { String outputName = connection.getOutputName(); if (outputs != null && !outputs.isEmpty()) { for (Output output : outputs) { if (output.getName().equals(outputName)) { if (input.getType().equals("*")) { return; } else { try { Class<?> outputType = Class.forName(output.getType()); Class<?> inputType = Class.forName(input.getType()); if (inputType.isAssignableFrom(outputType)) { return; } else { throw new IllegalArgumentException(msg + " Incompatible types : \"" + output.getType() + "\" and \"" + input.getType() + "\"."); } } catch (ClassNotFoundException e) { if (output.getType().equals(input.getType())) { return; } else { throw new IllegalArgumentException(msg + " Incompatible types : \"" + output.getType() + "\" and \"" + input.getType() + "\"."); } } } } } throw new IllegalArgumentException(msg + " Output with name \"" + outputName + "\" not exists in the Module with ID \"" + connection.getOuputModuleId() + "\""); } } }