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

Commit

Permalink
Enhance rule interpretation errors (#4462)
Browse files Browse the repository at this point in the history
* enhanced error message for ClassCastExceptions

Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
  • Loading branch information
sjsf authored and kaikreuzer committed Nov 1, 2017
1 parent 3c9d030 commit ebd2a3a
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 90 deletions.
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;
}
}
}

}

0 comments on commit ebd2a3a

Please sign in to comment.