package org.openhab.domain.rule; import org.openhab.domain.rule.operators.RuleOperator; import org.openhab.domain.user.AccessModifier; import org.openhab.domain.util.StringHandler; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; /** * Created by Tony Alpskog in 2014. */ public class RuleOperation extends EntityDataType<LogicBoolean> implements IRuleChild, OnOperandValueChangedListener { private List<IEntityDataType> mOperands; private RuleOperator mRuleOperator; private String mDescription; private boolean mIsActive; private AccessModifier mAccessModifier; public RuleOperation() { mOperands = new ArrayList<IEntityDataType>(); mOperands.add(null); mDataSourceId = UUID.randomUUID().toString(); mAccessModifier = mAccessModifier.ReadOnly; } public RuleOperation(String name) { this(); setName(name); } public RuleOperation(RuleOperator ruleOperator, List<IEntityDataType> operands) { setActive(true); mRuleOperator = ruleOperator; mOperands = operands; mDataSourceId = UUID.randomUUID().toString(); for (IEntityDataType operand : operands) { if(operand != null) operand.setOnOperandValueChangedListener(this); } runCalculation(); } public int getNumberOfOperands() { return mOperands.size(); } public void setOperand(int index, IEntityDataType operand) { //TODO - TA: Investigate if OnOperandValueChangedListener should be unregistered. // if(mOperands.get(index) != null) //Unregister OnOperandValueChangedListener ??? if(mOperands.size() > index) mOperands.set(index, operand); else mOperands.add(index, operand); if(operand != null) operand.setOnOperandValueChangedListener(this); runCalculation(); } public IEntityDataType getOperand(int index) { if(mOperands.size() > index) return mOperands.get(index); return null; } public IEntityDataType getOperandBySourceId(String operandSourceId) { for(IEntityDataType operand : mOperands) if(operand!= null && !StringHandler.isNullOrEmpty(operand.getDataSourceId()) && operand.getDataSourceId().equalsIgnoreCase(operandSourceId)) return operand; return null; } public int getOperandIndexBySourceId(String operandSourceId) { IEntityDataType operand = getOperandBySourceId(operandSourceId); return operand != null? mOperands.indexOf(operand) : -1; } public boolean isActive() { return mIsActive; } public void setActive(boolean isActive) { mIsActive = isActive; } @Override public String toString() { return toString(true); } public boolean isIncomplete() { if(getRuleOperator() == null) return true; for(IEntityDataType operand : mOperands.toArray(new IEntityDataType[0])) if(operand == null) return true; return false; } private String toString(boolean addResultAsPostfixToNonGeneratedStrings) { if(!StringHandler.isNullOrEmpty(getName())) { return getName() + (isIncomplete()? " <Incomplete>" : "") + (addResultAsPostfixToNonGeneratedStrings? " [" + getFormattedString() + "]" : ""); } String[] operandsAsStringArray = new String[mOperands.size() - 1]; int index = 0; Iterator<IEntityDataType> iterator = mOperands.iterator(); IEntityDataType mLeftOperand; if(iterator.hasNext()) mLeftOperand = iterator.next();//Save the the leftmost operand for later. else return "Oooops!";//TODO - TA: Throw exception or change description if(mLeftOperand == null && getRuleOperator() == null) return RuleOperatorType.MISSING_OPERAND; while(iterator.hasNext()) { IEntityDataType operand = iterator.next(); boolean isRuleAndUseGeneratedString = getIsRuleAndUseGeneratedString(operand); String format = isRuleAndUseGeneratedString? "(%s)" : "%s"; operandsAsStringArray[index++] = String.format(format, operand == null? RuleOperatorType.MISSING_OPERAND : operand.toString()); } boolean isRuleAndUseGeneratedString = getIsRuleAndUseGeneratedString(mLeftOperand); String format = isRuleAndUseGeneratedString? "(%s)%s" : "%s%s"; String rightOperandString; if(getRuleOperator() == null) rightOperandString = " " + RuleOperator.MISSING_OPERATOR; else { try { rightOperandString = getRuleOperator().getType().getFormattedString(operandsAsStringArray); } catch(IllegalArgumentException ex) { rightOperandString = " " + getRuleOperator().getName() + " <Missing operand(s)>";//TODO - TA: Language independent } } return String.format(format, mLeftOperand == null? RuleOperatorType.MISSING_OPERAND : mLeftOperand.toString() , rightOperandString); } public String toString(boolean addResultAsPrefix, boolean addResultAsPostfix) { StringBuilder result = new StringBuilder(); if(addResultAsPrefix) result.append("[" + getFormattedString() + "] "); result.append(toString(false)); if(addResultAsPostfix) result.append(" [" + getFormattedString() + "]"); return result.toString(); } private boolean getIsRuleAndUseGeneratedString(IEntityDataType operand) { return operand == null? false : operand.getSourceType() == EntityDataTypeSource.OPERATION && StringHandler.isNullOrEmpty(operand.getName()); } public boolean isValid() { if(mRuleOperator == null) return false; if(mOperands.size() < mRuleOperator.getType().getMinimumNumberOfSupportedOperationArgs() || mOperands.size() > mRuleOperator.getType().getMaximumNumberOfSupportedOperationArgs()) return false; for(IEntityDataType operand : mOperands) { if(operand == null || operand.getValue() == null) return false; } return true; } public void runCalculation() { if(!isActive()) return; if(!isValid()) { if(mValue == null) mValue = new LogicBoolean(Boolean.FALSE); else mValue.setValue(false);//Missing operator shall result as FALSE. return; } LogicBoolean oldValue = mValue; if(getRuleOperator() == null) { if(mValue == null) mValue = new LogicBoolean(Boolean.FALSE); else mValue.setValue(false);//Missing operator shall result as FALSE. } else mValue = new LogicBoolean(getRuleOperator().getOperationResult(mOperands)); if(!mValue.equals(oldValue) && mOnOperandValueChangedListener != null) mOnOperandValueChangedListener.onOperandValueChanged(this); } @Override public String getName() { return mName; } @Override public void setName(String name) { mName = name; } @Override public String getDescription() { return mDescription; } @Override public void setDescription(String description) { mDescription = description; } @Override public RuleTreeItem getRuleTreeItem(int treeIndex) { HashMap<Integer, RuleTreeItem> hm = new HashMap<Integer, RuleTreeItem>(); Integer treeItemIndex = 0; for(IEntityDataType operand : mOperands.toArray(new IEntityDataType[0])) { RuleTreeItem rti = operand == null? null : operand.getRuleTreeItem(treeItemIndex); if(rti != null) { if(!hm.isEmpty()) { RuleTreeItem operatorTreeItem = new RuleTreeItem(treeItemIndex , getRuleOperator() != null? getRuleOperator().getType().getName() : RuleOperator.MISSING_OPERATOR , RuleTreeItem.ItemType.OPERATOR); hm.put(treeItemIndex++, operatorTreeItem); rti.setPosition(treeItemIndex); } hm.put(treeItemIndex++, rti); } } RuleTreeItem result = hm.isEmpty()? new RuleTreeItem(treeIndex, toString(true, false), RuleTreeItem.ItemType.OPERAND) : new RuleTreeItem(treeIndex, toString(true, false), RuleTreeItem.ItemType.OPERAND, hm); result.setItemId(getDataSourceId()); return result; } @Override public String getFormattedString(){ if(getRuleOperator() == null) return "Falskt"; for(IEntityDataType operand : mOperands) if(operand == null) return "Falskt"; return getValue().getValue()? "Sant": "Falskt";//TODO - TA: Language independent } @Override public EntityDataTypeSource getSourceType() { return EntityDataTypeSource.OPERATION; } @Override public Class<LogicBoolean> getDataType() { return LogicBoolean.class; } @Override public LogicBoolean valueOf(String input) { return new LogicBoolean(new Boolean(input)); } @Override /** * The latest resulting value OR null. */ public LogicBoolean getValue() { //Returns mValue instead of calling runCalculation() to prevent from multiple calls to getResult(). return mValue == null? new LogicBoolean(false): mValue; } public RuleOperator getRuleOperator() { return mRuleOperator; } public void setRuleOperator(RuleOperator ruleOperator) { mRuleOperator = ruleOperator; if(!isValid()) runCalculation(); } @Override public void onOperandValueChanged(IEntityDataType operand) { runCalculation(); } public HashMap<String, RuleOperation> getRuleOperationHash() { HashMap<String, RuleOperation> operationIdHash = new HashMap<String, RuleOperation>(); operationIdHash.put(getDataSourceId(), this); for(IEntityDataType operand : mOperands) { if(operand == null) continue; if(operand.getSourceType() == EntityDataTypeSource.OPERATION) operationIdHash.putAll(((RuleOperation) operand).getRuleOperationHash()); else operationIdHash.put(operand.getDataSourceId(), this); } return operationIdHash; } public Map<String, LogicBoolean> getStaticValues() { Map<String, LogicBoolean> nameValueMap = new HashMap<String, LogicBoolean>(2); nameValueMap.put("TRUE", new LogicBoolean(Boolean.TRUE)); nameValueMap.put("FALSE", new LogicBoolean(Boolean.FALSE)); return nameValueMap; } public static RuleOperation getStaticEntityDataType(String staticValue) { RuleOperation staticEntityDataType = null; //TODO - TA: replace static values with constants (TRUE, FALSE, Undefined) LogicBoolean aLogicBoolean; if(staticValue == null || staticValue.equalsIgnoreCase("Undefined")) aLogicBoolean = null; else aLogicBoolean = new LogicBoolean(staticValue.equalsIgnoreCase("TRUE")); staticEntityDataType = new RuleOperation() { @Override public String getFormattedString(){ return mValue.getValue()? "TRUE": "FALSE"; } @Override public LogicBoolean valueOf(String input) { if(input.equalsIgnoreCase("TRUE")) return new LogicBoolean(Boolean.valueOf(true)); if(input.equalsIgnoreCase("FALSE")) return new LogicBoolean(Boolean.valueOf(false)); return null; } @Override public String toString() { return getFormattedString(); } @Override public String toString(boolean addResultAsPrefix, boolean addResultAsPostfix) { return toString(); } @Override public LogicBoolean getValue() { return mValue; } }; staticEntityDataType.mValue = aLogicBoolean; return staticEntityDataType; } public AccessModifier getAccessModifier() { return mAccessModifier; } public void setAccessModifier(AccessModifier accessModifier) { mAccessModifier = accessModifier; } }