/** * Copyright (c) 2014-2017 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.eclipse.smarthome.core.library.types; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.smarthome.core.items.GroupFunction; import org.eclipse.smarthome.core.items.Item; import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.core.types.UnDefType; /** * This interface is only a container for functions that require the core type library * for its calculations. * * @author Kai Kreuzer - Initial contribution and API * @author Thomas Eichstädt-Engelen - Added "N" functions * @author Gaël L'hopital - Added count function * */ public interface ArithmeticGroupFunction extends GroupFunction { /** * This does a logical 'and' operation. Only if all items are of 'activeState' this * is returned, otherwise the 'passiveState' is returned. * * Through the getStateAs() method, it can be determined, how many * items actually are not in the 'activeState'. */ static class And implements GroupFunction { protected final State activeState; protected final State passiveState; public And(State activeValue, State passiveValue) { if (activeValue == null || passiveValue == null) { throw new IllegalArgumentException("Parameters must not be null!"); } this.activeState = activeValue; this.passiveState = passiveValue; } @Override public State calculate(Set<Item> items) { if (items != null && items.size() > 0) { for (Item item : items) { if (!activeState.equals(item.getStateAs(activeState.getClass()))) { return passiveState; } } return activeState; } else { // if we do not have any items, we return the passive state return passiveState; } } @Override public State getStateAs(Set<Item> items, Class<? extends State> stateClass) { State state = calculate(items); if (stateClass.isInstance(state)) { return state; } else { if (stateClass == DecimalType.class) { if (items != null) { return new DecimalType(items.size() - count(items, activeState)); } else { return DecimalType.ZERO; } } else { return null; } } } private int count(Set<Item> items, State state) { int count = 0; if (items != null && state != null) { for (Item item : items) { if (state.equals(item.getStateAs(state.getClass()))) { count++; } } } return count; } @Override public State[] getParameters() { return new State[] { activeState, passiveState }; } } /** * This does a logical 'or' operation. If at least one item is of 'activeState' this * is returned, otherwise the 'passiveState' is returned. * * Through the getStateAs() method, it can be determined, how many * items actually are in the 'activeState'. */ static class Or implements GroupFunction { protected final State activeState; protected final State passiveState; public Or(State activeValue, State passiveValue) { if (activeValue == null || passiveValue == null) { throw new IllegalArgumentException("Parameters must not be null!"); } this.activeState = activeValue; this.passiveState = passiveValue; } @Override public State calculate(Set<Item> items) { if (items != null) { for (Item item : items) { if (activeState.equals(item.getStateAs(activeState.getClass()))) { return activeState; } } } return passiveState; } @Override public State getStateAs(Set<Item> items, Class<? extends State> stateClass) { State state = calculate(items); if (stateClass.isInstance(state)) { return state; } else { if (stateClass == DecimalType.class) { return new DecimalType(count(items, activeState)); } else { return null; } } } private int count(Set<Item> items, State state) { int count = 0; if (items != null && state != null) { for (Item item : items) { if (state.equals(item.getStateAs(state.getClass()))) { count++; } } } return count; } @Override public State[] getParameters() { return new State[] { activeState, passiveState }; } } /** * This does a logical 'nand' operation. The state is 'calculated' by * the normal 'and' operation and than negated by returning the opposite * value. E.g. when the 'and' operation calculates the activeValue the * passiveValue will be returned and vice versa. */ static class NAnd extends And { public NAnd(State activeValue, State passiveValue) { super(activeValue, passiveValue); } @Override public State calculate(Set<Item> items) { State result = super.calculate(items); State notResult = result.equals(activeState) ? passiveState : activeState; return notResult; } } /** * This does a logical 'nor' operation. The state is 'calculated' by * the normal 'or' operation and than negated by returning the opposite * value. E.g. when the 'or' operation calculates the activeValue the * passiveValue will be returned and vice versa. */ static class NOr extends Or { public NOr(State activeValue, State passiveValue) { super(activeValue, passiveValue); } @Override public State calculate(Set<Item> items) { State result = super.calculate(items); State notResult = result.equals(activeState) ? passiveState : activeState; return notResult; } } /** * This calculates the numeric average over all item states of decimal type. */ static class Avg implements GroupFunction { public Avg() { } /** * @{inheritDoc */ @Override public State calculate(Set<Item> items) { BigDecimal sum = BigDecimal.ZERO; int count = 0; if (items != null) { for (Item item : items) { DecimalType itemState = (DecimalType) item.getStateAs(DecimalType.class); if (itemState != null) { sum = sum.add(itemState.toBigDecimal()); count++; } } } if (count > 0) { return new DecimalType(sum.divide(BigDecimal.valueOf(count), RoundingMode.HALF_UP)); } else { return UnDefType.UNDEF; } } /** * @{inheritDoc */ @Override public State getStateAs(Set<Item> items, Class<? extends State> stateClass) { State state = calculate(items); if (stateClass.isInstance(state)) { return state; } else { return null; } } @Override public State[] getParameters() { return new State[0]; } } /** * This calculates the numeric sum over all item states of decimal type. */ static class Sum implements GroupFunction { public Sum() { } @Override public State calculate(Set<Item> items) { BigDecimal sum = BigDecimal.ZERO; if (items != null) { for (Item item : items) { DecimalType itemState = (DecimalType) item.getStateAs(DecimalType.class); if (itemState != null) { sum = sum.add(itemState.toBigDecimal()); } } } return new DecimalType(sum); } @Override public State getStateAs(Set<Item> items, Class<? extends State> stateClass) { State state = calculate(items); if (stateClass.isInstance(state)) { return state; } else { return null; } } @Override public State[] getParameters() { return new State[0]; } } /** * This calculates the minimum value of all item states of decimal type. */ static class Min implements GroupFunction { public Min() { } @Override public State calculate(Set<Item> items) { if (items != null && items.size() > 0) { BigDecimal min = null; for (Item item : items) { DecimalType itemState = (DecimalType) item.getStateAs(DecimalType.class); if (itemState != null) { if (min == null || min.compareTo(itemState.toBigDecimal()) > 0) { min = itemState.toBigDecimal(); } } } if (min != null) { return new DecimalType(min); } } return UnDefType.UNDEF; } @Override public State getStateAs(Set<Item> items, Class<? extends State> stateClass) { State state = calculate(items); if (stateClass.isInstance(state)) { return state; } else { return null; } } @Override public State[] getParameters() { return new State[0]; } } /** * This calculates the maximum value of all item states of decimal type. */ static class Max implements GroupFunction { public Max() { } @Override public State calculate(Set<Item> items) { if (items != null && items.size() > 0) { BigDecimal max = null; for (Item item : items) { DecimalType itemState = (DecimalType) item.getStateAs(DecimalType.class); if (itemState != null) { if (max == null || max.compareTo(itemState.toBigDecimal()) < 0) { max = itemState.toBigDecimal(); } } } if (max != null) { return new DecimalType(max); } } return UnDefType.UNDEF; } @Override public State getStateAs(Set<Item> items, Class<? extends State> stateClass) { State state = calculate(items); if (stateClass.isInstance(state)) { return state; } else { return null; } } @Override public State[] getParameters() { return new State[0]; } } /** * This calculates the number of items in the group matching the * regular expression passed in parameter * Group:Number:COUNT(".") will count all items having a string state of one character * Group:Number:COUNT("[5-9]") will count all items having a string state between 5 and 9 * ... */ static class Count implements GroupFunction { protected final Pattern pattern; public Count(State regExpr) { if (regExpr == null) { throw new IllegalArgumentException("Parameter must not be null!"); } this.pattern = Pattern.compile(regExpr.toString()); } @Override public State calculate(Set<Item> items) { int count = 0; if (items != null) { for (Item item : items) { Matcher matcher = pattern.matcher(item.getState().toString()); if (matcher.matches()) { count++; } } } return new DecimalType(count); } @Override public State getStateAs(Set<Item> items, Class<? extends State> stateClass) { State state = calculate(items); if (stateClass.isInstance(state)) { return state; } else { return null; } } @Override public State[] getParameters() { return new State[] { new StringType(pattern.pattern()) }; } } }