diff --git a/bundles/model/org.eclipse.smarthome.model.rule.runtime/src/org/eclipse/smarthome/model/rule/runtime/internal/engine/ExecuteRuleJob.java b/bundles/model/org.eclipse.smarthome.model.rule.runtime/src/org/eclipse/smarthome/model/rule/runtime/internal/engine/ExecuteRuleJob.java index cc099414223..e530f65854d 100644 --- a/bundles/model/org.eclipse.smarthome.model.rule.runtime/src/org/eclipse/smarthome/model/rule/runtime/internal/engine/ExecuteRuleJob.java +++ b/bundles/model/org.eclipse.smarthome.model.rule.runtime/src/org/eclipse/smarthome/model/rule/runtime/internal/engine/ExecuteRuleJob.java @@ -61,7 +61,7 @@ public void execute(JobExecutionContext context) throws JobExecutionException { try { script.execute(RuleContextHelper.getContext(rule, injector)); } catch (ScriptExecutionException e) { - logger.error("Error during the execution of rule {}: {}", rule.getName(), e.getMessage()); + logger.error("Error during the execution of rule '{}': {}", rule.getName(), e.getMessage()); } } else { logger.debug("Scheduled rule '{}' does not exist", ruleName); diff --git a/bundles/model/org.eclipse.smarthome.model.rule.runtime/src/org/eclipse/smarthome/model/rule/runtime/internal/engine/RuleEngineImpl.java b/bundles/model/org.eclipse.smarthome.model.rule.runtime/src/org/eclipse/smarthome/model/rule/runtime/internal/engine/RuleEngineImpl.java index 13e3daca881..cdba49a100b 100644 --- a/bundles/model/org.eclipse.smarthome.model.rule.runtime/src/org/eclipse/smarthome/model/rule/runtime/internal/engine/RuleEngineImpl.java +++ b/bundles/model/org.eclipse.smarthome.model.rule.runtime/src/org/eclipse/smarthome/model/rule/runtime/internal/engine/RuleEngineImpl.java @@ -310,8 +310,8 @@ private void runStartupRules() { triggerManager.removeRule(STARTUP, rule); } catch (ScriptExecutionException e) { if (!e.getMessage().contains("cannot be resolved to an item or type")) { - logger.error("Error during the execution of startup rule '{}': {}", - new Object[] { rule.getName(), e.getCause().getMessage() }); + logger.error("Error during the execution of startup rule '{}': {}", rule.getName(), + e.getCause().getMessage()); triggerManager.removeRule(STARTUP, rule); } else { logger.debug( diff --git a/bundles/model/org.eclipse.smarthome.model.script/src/org/eclipse/smarthome/model/script/engine/ScriptError.java b/bundles/model/org.eclipse.smarthome.model.script/src/org/eclipse/smarthome/model/script/engine/ScriptError.java index b69d84a2391..c753c880d1e 100644 --- a/bundles/model/org.eclipse.smarthome.model.script/src/org/eclipse/smarthome/model/script/engine/ScriptError.java +++ b/bundles/model/org.eclipse.smarthome.model.script/src/org/eclipse/smarthome/model/script/engine/ScriptError.java @@ -7,6 +7,11 @@ */ package org.eclipse.smarthome.model.script.engine; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.nodemodel.INode; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; +import org.eclipse.xtext.util.LineAndColumn; + /** * A detailed error information for a script * @@ -28,7 +33,7 @@ public final class ScriptError { /** * Creates new ScriptError. - * + * * @param message Error Message * @param line Line number, or -1 if unknown * @param column Column number, or -1 if unknown @@ -41,10 +46,28 @@ public ScriptError(final String message, final int line, final int column, final this.length = length; } + /** + * Creates new ScriptError. + * + * This constructor uses the given EObject instance to calculate the exact position. + * + * @param message Error Message + * @param atEObject the EObject instance to use for calculating the position + * + */ + public ScriptError(final String message, final EObject atEObject) { + this.message = message; + INode node = NodeModelUtils.getNode(atEObject); + LineAndColumn lac = NodeModelUtils.getLineAndColumn(node, node.getOffset()); + this.line = lac.getLine(); + this.column = lac.getColumn(); + this.length = node.getEndOffset() - node.getOffset(); + } + /** * Returns a message containing the String passed to a constructor as well as line and column numbers if any of * these are known. - * + * * @return The error message. */ public String getMessage() { @@ -66,7 +89,7 @@ public String getMessage() { /** * Get the line number on which an error occurred. - * + * * @return The line number. Returns -1 if a line number is unavailable. */ public int getLineNumber() { @@ -75,7 +98,7 @@ public int getLineNumber() { /** * Get the column number on which an error occurred. - * + * * @return The column number. Returns -1 if a column number is unavailable. */ public int getColumnNumber() { @@ -84,7 +107,7 @@ public int getColumnNumber() { /** * Get the number of columns affected by the error. - * + * * @return The number of columns. Returns -1 if unavailable. */ public int getLength() { diff --git a/bundles/model/org.eclipse.smarthome.model.script/src/org/eclipse/smarthome/model/script/engine/ScriptException.java b/bundles/model/org.eclipse.smarthome.model.script/src/org/eclipse/smarthome/model/script/engine/ScriptException.java index 5a204f56899..d6222d377e0 100644 --- a/bundles/model/org.eclipse.smarthome.model.script/src/org/eclipse/smarthome/model/script/engine/ScriptException.java +++ b/bundles/model/org.eclipse.smarthome.model.script/src/org/eclipse/smarthome/model/script/engine/ScriptException.java @@ -29,6 +29,12 @@ protected ScriptException(String message) { errors.add(new ScriptError(message, 0, 0, -1)); } + protected ScriptException(ScriptError scriptError) { + super(scriptError.getMessage()); + this.errors = new ArrayList(1); + errors.add(scriptError); + } + /** * @param message * @param cause @@ -64,7 +70,7 @@ private ScriptException(final Throwable cause, final String scriptText, final Sc /** * Creates a ScriptException with one Error. - * + * * @param errors */ private ScriptException(final String scriptText, final ScriptError error) { @@ -81,7 +87,7 @@ public ScriptException(String message, Throwable cause) { /** * All Errors that lead to this Exception. - * + * * @return List of Error. Size >= 1, there is at last one ScriptError. */ public List getErrors() { @@ -95,9 +101,9 @@ public void setScriptText(final String scriptText) { /** * Returns a concatenation of all errors in contained ScriptError instances. * Separated by newline, except for last error; no \n if only one error. - * + * * @return The Message. - * + * * @see ScriptError#getMessage() */ @Override diff --git a/bundles/model/org.eclipse.smarthome.model.script/src/org/eclipse/smarthome/model/script/engine/ScriptExecutionException.java b/bundles/model/org.eclipse.smarthome.model.script/src/org/eclipse/smarthome/model/script/engine/ScriptExecutionException.java index 6ebd984bd1c..dcfd4aef127 100644 --- a/bundles/model/org.eclipse.smarthome.model.script/src/org/eclipse/smarthome/model/script/engine/ScriptExecutionException.java +++ b/bundles/model/org.eclipse.smarthome.model.script/src/org/eclipse/smarthome/model/script/engine/ScriptExecutionException.java @@ -21,6 +21,10 @@ public ScriptExecutionException(final String message, final int line, final int super(message, null, line, column, length); } + public ScriptExecutionException(final ScriptError scriptError) { + super(scriptError); + } + public ScriptExecutionException(final String message, final Throwable cause, final int line, final int column, final int length) { super(cause, message, null, line, column, length); diff --git a/bundles/model/org.eclipse.smarthome.model.script/src/org/eclipse/smarthome/model/script/interpreter/ScriptInterpreter.xtend b/bundles/model/org.eclipse.smarthome.model.script/src/org/eclipse/smarthome/model/script/interpreter/ScriptInterpreter.xtend index 31d3c810e70..b50b2a21798 100644 --- a/bundles/model/org.eclipse.smarthome.model.script/src/org/eclipse/smarthome/model/script/interpreter/ScriptInterpreter.xtend +++ b/bundles/model/org.eclipse.smarthome.model.script/src/org/eclipse/smarthome/model/script/interpreter/ScriptInterpreter.xtend @@ -8,21 +8,30 @@ package org.eclipse.smarthome.model.script.interpreter; import com.google.inject.Inject +import org.eclipse.emf.ecore.EObject import org.eclipse.smarthome.core.items.Item import org.eclipse.smarthome.core.items.ItemNotFoundException import org.eclipse.smarthome.core.items.ItemRegistry import org.eclipse.smarthome.core.types.Type +import org.eclipse.smarthome.model.script.engine.ScriptExecutionException import org.eclipse.smarthome.model.script.lib.NumberExtensions import org.eclipse.smarthome.model.script.scoping.StateAndCommandProvider import org.eclipse.xtext.common.types.JvmField import org.eclipse.xtext.common.types.JvmIdentifiableElement import org.eclipse.xtext.naming.QualifiedName +import org.eclipse.xtext.nodemodel.INode +import org.eclipse.xtext.nodemodel.util.NodeModelUtils import org.eclipse.xtext.util.CancelIndicator +import org.eclipse.xtext.util.LineAndColumn import org.eclipse.xtext.xbase.XAbstractFeatureCall +import org.eclipse.xtext.xbase.XCastedExpression import org.eclipse.xtext.xbase.XExpression +import org.eclipse.xtext.xbase.XFeatureCall +import org.eclipse.xtext.xbase.XMemberFeatureCall import org.eclipse.xtext.xbase.interpreter.IEvaluationContext import org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter import org.eclipse.xtext.xbase.jvmmodel.IJvmModelAssociations +import org.eclipse.smarthome.model.script.engine.ScriptError /** * The script interpreter handles ESH specific script components, which are not known @@ -30,87 +39,97 @@ import org.eclipse.xtext.xbase.jvmmodel.IJvmModelAssociations * * @author Kai Kreuzer - Initial contribution and API * @author Oliver Libutzki - Xtext 2.5.0 migration - * + * */ @SuppressWarnings("restriction") public class ScriptInterpreter extends XbaseInterpreter { - @Inject - ItemRegistry itemRegistry - - @Inject - StateAndCommandProvider stateAndCommandProvider - - @Inject - extension IJvmModelAssociations - - override protected _invokeFeature(JvmField jvmField, XAbstractFeatureCall featureCall, Object receiver, - IEvaluationContext context, CancelIndicator indicator) { - - // Check if the JvmField is inferred - val sourceElement = jvmField.sourceElements.head - if (sourceElement != null) { - val value = context.getValue(QualifiedName.create(jvmField.simpleName)) - value ?: { - - // Looks like we have an state, command or item field - val fieldName = jvmField.simpleName - fieldName.stateOrCommand ?: fieldName.item - } - } else { - super._invokeFeature(jvmField, featureCall, receiver, context, indicator) - } - - } - - override protected invokeFeature(JvmIdentifiableElement feature, XAbstractFeatureCall featureCall, - Object receiverObj, IEvaluationContext context, CancelIndicator indicator) { - if (feature != null && feature.eIsProxy) { - throw new RuntimeException( - "The name '" + featureCall.toString() + "' cannot be resolved to an item or type."); - } - super.invokeFeature(feature, featureCall, receiverObj, context, indicator) - } - - def protected Type getStateOrCommand(String name) { - for (Type type : stateAndCommandProvider.getAllTypes()) { - if (type.toString == name) { - return type - } - } - } - - def protected Item getItem(String name) { - try { - return itemRegistry.getItem(name); - } catch (ItemNotFoundException e) { - return null; - } - } - - override protected boolean eq(Object a, Object b) { - if (a instanceof Type && b instanceof Number) { - return NumberExtensions.operator_equals(a as Type, b as Number); - } else if (a instanceof Number && b instanceof Type) { - return NumberExtensions.operator_equals(b as Type, a as Number); - } else { - return super.eq(a, b); - } - } - - override _assignValueTo(JvmField jvmField, XAbstractFeatureCall assignment, Object value, - IEvaluationContext context, CancelIndicator indicator) { - - // Check if the JvmField is inferred - val sourceElement = jvmField.sourceElements.head - if (sourceElement != null) { - context.assignValue(QualifiedName.create(jvmField.simpleName), value) - value - } else { - super._assignValueTo(jvmField, assignment, value, context, indicator) - } - } - + @Inject + ItemRegistry itemRegistry + + @Inject + StateAndCommandProvider stateAndCommandProvider + + @Inject + extension IJvmModelAssociations + + override protected _invokeFeature(JvmField jvmField, XAbstractFeatureCall featureCall, Object receiver, + IEvaluationContext context, CancelIndicator indicator) { + + // Check if the JvmField is inferred + val sourceElement = jvmField.sourceElements.head + if (sourceElement != null) { + val value = context.getValue(QualifiedName.create(jvmField.simpleName)) + value ?: { + + // Looks like we have an state, command or item field + val fieldName = jvmField.simpleName + fieldName.stateOrCommand ?: fieldName.item + } + } else { + super._invokeFeature(jvmField, featureCall, receiver, context, indicator) + } + + } + + override protected invokeFeature(JvmIdentifiableElement feature, XAbstractFeatureCall featureCall, + Object receiverObj, IEvaluationContext context, CancelIndicator indicator) { + if (feature != null && feature.eIsProxy) { + if (featureCall instanceof XMemberFeatureCall) { + throw new ScriptExecutionException(new ScriptError( + "'" + featureCall.getConcreteSyntaxFeatureName() + "' is not a member of '" + + receiverObj?.getClass()?.getName() + "'", featureCall)); + } else if (featureCall instanceof XFeatureCall) { + throw new ScriptExecutionException(new ScriptError( + "The name '" + featureCall.getConcreteSyntaxFeatureName() + + "' cannot be resolved to an item or type", featureCall)); + } else { + throw new ScriptExecutionException(new ScriptError( + "Unknown variable or command '" + featureCall.getConcreteSyntaxFeatureName() + "'", featureCall)); + } + } + super.invokeFeature(feature, featureCall, receiverObj, context, indicator) + } + + def protected Type getStateOrCommand(String name) { + for (Type type : stateAndCommandProvider.getAllTypes()) { + if (type.toString == name) { + return type + } + } + } + + def protected Item getItem(String name) { + try { + return itemRegistry.getItem(name); + } catch (ItemNotFoundException e) { + return null; + } + } + + override protected boolean eq(Object a, Object b) { + if (a instanceof Type && b instanceof Number) { + return NumberExtensions.operator_equals(a as Type, b as Number); + } else if (a instanceof Number && b instanceof Type) { + return NumberExtensions.operator_equals(b as Type, a as Number); + } else { + return super.eq(a, b); + } + } + + override _assignValueTo(JvmField jvmField, XAbstractFeatureCall assignment, Object value, + IEvaluationContext context, CancelIndicator indicator) { + + // Check if the JvmField is inferred + val sourceElement = jvmField.sourceElements.head + if (sourceElement != null) { + context.assignValue(QualifiedName.create(jvmField.simpleName), value) + value + } else { + super._assignValueTo(jvmField, assignment, value, context, indicator) + } + } + override protected doEvaluate(XExpression expression, IEvaluationContext context, CancelIndicator indicator) { if (expression == null) { return null @@ -118,4 +137,21 @@ public class ScriptInterpreter extends XbaseInterpreter { return super.doEvaluate(expression, context, indicator) } -} + override Object _doEvaluate(XCastedExpression castedExpression, IEvaluationContext context, + CancelIndicator indicator) { + try { + return super._doEvaluate(castedExpression, context, indicator) + } catch (RuntimeException e) { + if (e.cause instanceof ClassCastException) { + val Object result = internalEvaluate(castedExpression.getTarget(), context, indicator); + throw new ScriptExecutionException(new ScriptError( + "Could not cast " + result + " to " + castedExpression.getType().getType().getQualifiedName(), + castedExpression)); + } else { + throw e; + } + } + } + + } + \ No newline at end of file