Skip to content
This repository has been archived by the owner on May 7, 2020. It is now read-only.

Enhance rule interpretation errors #4462

Merged
merged 7 commits into from Nov 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -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);
Expand Down
Expand Up @@ -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(
Expand Down
Expand Up @@ -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
*
Expand All @@ -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
Expand All @@ -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() {
Expand All @@ -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() {
Expand All @@ -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() {
Expand All @@ -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() {
Expand Down
Expand Up @@ -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<ScriptError>(1);
errors.add(scriptError);
}

/**
* @param message
* @param cause
Expand Down Expand Up @@ -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) {
Expand All @@ -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<ScriptError> getErrors() {
Expand All @@ -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
Expand Down
Expand Up @@ -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);
Expand Down
Expand Up @@ -8,114 +8,150 @@
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
* to the standard Xbase interpreter.
*
* @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
}
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;
}
}
}

}