diff --git a/dmn-core/src/main/java/com/gs/dmn/feel/analysis/AbstractAnalysisVisitor.java b/dmn-core/src/main/java/com/gs/dmn/feel/analysis/AbstractAnalysisVisitor.java index 2f2414e98..2544eb144 100644 --- a/dmn-core/src/main/java/com/gs/dmn/feel/analysis/AbstractAnalysisVisitor.java +++ b/dmn-core/src/main/java/com/gs/dmn/feel/analysis/AbstractAnalysisVisitor.java @@ -13,8 +13,12 @@ package com.gs.dmn.feel.analysis; import com.gs.dmn.DMNModelRepository; +import com.gs.dmn.context.DMNContext; import com.gs.dmn.context.environment.EnvironmentFactory; +import com.gs.dmn.el.analysis.semantics.type.Type; import com.gs.dmn.error.ErrorHandler; +import com.gs.dmn.feel.analysis.semantics.SemanticError; +import com.gs.dmn.feel.analysis.syntax.ast.expression.Expression; import com.gs.dmn.feel.analysis.syntax.ast.visitor.AbstractVisitor; import com.gs.dmn.feel.synthesis.type.NativeTypeFactory; import com.gs.dmn.transformation.basic.BasicDMNToNativeTransformer; @@ -54,4 +58,16 @@ protected AbstractAnalysisVisitor(BasicDMNToNativeTransformer dmnTransform public BasicDMNToNativeTransformer getDmnTransformer() { return dmnTransformer; } + + protected void handleError(String message) { + throw new SemanticError(message); + } + + protected void handleError(DMNContext context, Expression element, String message) { + throw new SemanticError(context, element, message); + } + + protected void handleError(DMNContext context, Expression element, String message, Exception e) { + throw new SemanticError(context, element, message, e); + } } diff --git a/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/FEELSemanticVisitor.java b/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/FEELSemanticVisitor.java index 1ecb9d280..9a7a99b91 100644 --- a/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/FEELSemanticVisitor.java +++ b/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/FEELSemanticVisitor.java @@ -44,7 +44,6 @@ import com.gs.dmn.feel.analysis.syntax.ast.expression.textual.*; import com.gs.dmn.feel.analysis.syntax.ast.expression.type.*; import com.gs.dmn.feel.analysis.syntax.ast.test.*; -import com.gs.dmn.runtime.DMNRuntimeException; import com.gs.dmn.runtime.Pair; import com.gs.dmn.transformation.basic.BasicDMNToNativeTransformer; @@ -91,7 +90,8 @@ public Element visit(NegatedPositiveUnaryTests element, DMNContext c for (Type child : ((TupleType) type).getTypes()) { if (child == BooleanType.BOOLEAN || child instanceof RangeType) { } else { - throw new SemanticError(element, String.format("Operator '%s' cannot be applied to '%s'", "not", child)); + handleError(context, element, String.format("Operator '%s' cannot be applied to '%s'", "not", child)); + return null; } } } @@ -155,7 +155,8 @@ public Element visit(EndpointsRange element, DMNContext context) { Expression start = element.getStart(); Expression end = element.getEnd(); if (start == null && end == null) { - throw new DMNRuntimeException(String.format("Illegal range, both endpoints are null in context of element '%s'", context.getElementName())); + handleError(String.format("Illegal range, both endpoints are null in context of element '%s'", context.getElementName())); + return null; } // Visit children @@ -213,7 +214,8 @@ public Element visit(ListTest element, DMNContext context) { } else if (com.gs.dmn.el.analysis.semantics.type.Type.conformsTo(inputExpressionType, optimizedListElementType)) { // input conforms to element in the list } else { - throw new SemanticError(element, String.format("Cannot compare '%s', '%s'", inputExpressionType, optimizedListType)); + handleError(context, element, String.format("Cannot compare '%s', '%s'", inputExpressionType, optimizedListType)); + return null; } } else { // test is list of ranges compatible with input @@ -373,10 +375,11 @@ public Element visit(IfExpression element, DMNContext context) { Type thenType = thenExpression.getType(); Type elseType = elseExpression.getType(); if (conditionType != BOOLEAN) { - throw new SemanticError(element, String.format("Condition type must be boolean. Found '%s' instead.", conditionType)); - } - if (com.gs.dmn.el.analysis.semantics.type.Type.isNullType(thenType) && com.gs.dmn.el.analysis.semantics.type.Type.isNullType(elseType)) { - throw new SemanticError(element, String.format("Types of then and else branches are incompatible. Found '%s' and '%s'.", thenType, elseType)); + handleError(context, element, String.format("Condition type must be boolean. Found '%s' instead.", conditionType)); + return null; + } else if (com.gs.dmn.el.analysis.semantics.type.Type.isNullType(thenType) && com.gs.dmn.el.analysis.semantics.type.Type.isNullType(elseType)) { + handleError(context, element, String.format("Types of then and else branches are incompatible. Found '%s' and '%s'.", thenType, elseType)); + return null; } else if (com.gs.dmn.el.analysis.semantics.type.Type.isNullType(thenType)) { element.setType(elseType); } else if (com.gs.dmn.el.analysis.semantics.type.Type.isNullType(elseType)) { @@ -387,7 +390,8 @@ public Element visit(IfExpression element, DMNContext context) { } else if (com.gs.dmn.el.analysis.semantics.type.Type.conformsTo(elseType, thenType)) { element.setType(thenType); } else { - throw new SemanticError(element, String.format("Types of then and else branches are incompatible. Found '%s' and '%s'.", thenType, elseType)); + handleError(context, element, String.format("Types of then and else branches are incompatible. Found '%s' and '%s'.", thenType, elseType)); + return null; } } @@ -431,7 +435,7 @@ public Element visit(FilterExpression element, DMNContext context) { } else if (filterType == BOOLEAN) { element.setType(sourceType); } else { - throw new SemanticError(element, String.format("Cannot resolve type for '%s'", element)); + handleError(context, element, String.format("Cannot resolve type for '%s'", element)); } } else { if (filterType == NUMBER) { @@ -439,7 +443,7 @@ public Element visit(FilterExpression element, DMNContext context) { } else if (filterType == BOOLEAN) { element.setType(new ListType(sourceType)); } else { - throw new SemanticError(element, String.format("Cannot resolve type for '%s'", element)); + handleError(context, element, String.format("Cannot resolve type for '%s'", element)); } } @@ -633,7 +637,7 @@ public Element visit(ArithmeticNegation element, DMNContext context) Type type = element.getLeftOperand().getType(); element.setType(NUMBER); if (type != NUMBER) { - throw new SemanticError(element, String.format("Operator '%s' cannot be applied to '%s'", element.getOperator(), type)); + handleError(context, element, String.format("Operator '%s' cannot be applied to '%s'", element.getOperator(), type)); } // Derive type @@ -727,7 +731,7 @@ private void visitSortParameters(FunctionInvocation element, DMNContext co lambdaExpression.accept(this, context); } if (!success) { - throw new SemanticError(element, String.format("Cannot infer parameter type for lambda in sort call '%s'", element)); + handleError(context, element, String.format("Cannot infer parameter type for lambda in sort call '%s'", element)); } } @@ -821,10 +825,12 @@ public Element visit(DateTimeLiteral element, DMNContext context) { } else if (element.isDaysAndTimeDuration(element.getLexeme())) { element.setType(DurationType.DAYS_AND_TIME_DURATION); } else { - throw new SemanticError(element, String.format("Date time literal '%s(%s) is not supported", conversionFunction, element.getLexeme())); + handleError(context, element, String.format("Date time literal '%s(%s) is not supported", conversionFunction, element.getLexeme())); + return null; } } else { - throw new SemanticError(element, String.format("Date time literal '%s(%s)' is not supported", conversionFunction, element.getLexeme())); + handleError(context, element, String.format("Date time literal '%s(%s)' is not supported", conversionFunction, element.getLexeme())); + return null; } return element; @@ -905,7 +911,8 @@ public Element visit(QualifiedName element, DMNContext context) { // Derive type List names = element.getNames(); if (names == null || names.isEmpty()) { - throw new SemanticError(element, "Illegal qualified name."); + handleError(context, element, "Illegal qualified name."); + return null; } else if (names.size() == 1) { deriveType(element, context); return element; @@ -1022,13 +1029,13 @@ private DMNContext visitIterators(final Expression element, DMNContext con it.accept(visitor, qContext); String itName = it.getName(); Type domainType = it.getDomain().getType(); - Type itType; + Type itType = null; if (domainType instanceof ListType) { itType = ((ListType) domainType).getElementType(); } else if (domainType instanceof RangeType) { itType = ((RangeType) domainType).getRangeType(); } else { - throw new SemanticError(element, String.format("Cannot resolve iterator type for '%s'", domainType)); + handleError(context, element, String.format("Cannot resolve iterator type for '%s'", domainType)); } qContext.addDeclaration(this.environmentFactory.makeVariableDeclaration(itName, itType)); }); @@ -1041,10 +1048,10 @@ protected void checkType(Expression element, String operator, Type leftOpe if (resultType != null) { element.setType(resultType); } else { - throw new SemanticError(element, String.format("Operator '%s' cannot be applied to '%s', '%s'", operator, leftOperandType, rightOperandType)); + handleError(context, element, String.format("Operator '%s' cannot be applied to '%s', '%s'", operator, leftOperandType, rightOperandType)); } } catch (Exception e) { - throw new SemanticError(element, String.format("Operator '%s' cannot be applied to '%s', '%s' in element '%s'", operator, leftOperandType, rightOperandType, context.getElementName()), e); + handleError(context, element, String.format("Operator '%s' cannot be applied to '%s', '%s'", operator, leftOperandType, rightOperandType), e); } } diff --git a/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/SemanticError.java b/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/SemanticError.java index 0ca0ba9fd..7a918cb16 100644 --- a/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/SemanticError.java +++ b/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/SemanticError.java @@ -12,6 +12,7 @@ */ package com.gs.dmn.feel.analysis.semantics; +import com.gs.dmn.context.DMNContext; import com.gs.dmn.el.analysis.semantics.type.Type; import com.gs.dmn.feel.analysis.syntax.ast.expression.Expression; @@ -20,11 +21,24 @@ public SemanticError(String errorMessage) { super(errorMessage); } - public SemanticError(Expression expression, String errorMessage) { - super(String.format("'%s': %s", expression.getClass().getSimpleName(), errorMessage)); + public SemanticError(DMNContext context, Expression expression, String errorMessage) { + super(String.format("'%s': %s", makeLocation(context, expression), errorMessage)); } - public SemanticError(Expression expression, String errorMessage, Exception e) { - super(String.format("'%s': %s", expression.getClass().getSimpleName(), errorMessage), e); + public SemanticError(DMNContext context, Expression expression, String errorMessage, Exception e) { + super(String.format("'%s': %s", makeLocation(context, expression), errorMessage), e); + } + + private static String makeLocation(DMNContext context, Expression expression) { + String location = null; + if (context != null && context.getElement() != null) { + location = context.getElementName(); + } + if (location == null) { + location = expression.getClass().getSimpleName(); + } else { + location += ":" + expression.getClass().getSimpleName(); + } + return location; } } diff --git a/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/type/BuiltinFunctionType.java b/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/type/BuiltinFunctionType.java index 0a62f2e5e..b9d108964 100644 --- a/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/type/BuiltinFunctionType.java +++ b/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/type/BuiltinFunctionType.java @@ -164,7 +164,8 @@ public boolean equivalentTo(Type other) { @Override public boolean conformsTo(Type other) { // “contravariant function argument type” and “covariant function return type” - return other instanceof FunctionType + return other == FunctionType.ANY_FUNCTION || + other instanceof FunctionType && com.gs.dmn.el.analysis.semantics.type.Type.conformsTo(this.returnType, ((FunctionType) other).returnType) && com.gs.dmn.el.analysis.semantics.type.Type.conformsTo(((FunctionType) other).parameterTypes, this.parameterTypes); } diff --git a/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/type/DMNFunctionType.java b/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/type/DMNFunctionType.java index 9f270c3c6..b15aad7c5 100644 --- a/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/type/DMNFunctionType.java +++ b/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/type/DMNFunctionType.java @@ -69,7 +69,8 @@ public boolean equivalentTo(Type other) { @Override public boolean conformsTo(Type other) { // “contravariant function argument type” and “covariant function return type” - return other instanceof FunctionType + return other == FunctionType.ANY_FUNCTION || + other instanceof FunctionType && com.gs.dmn.el.analysis.semantics.type.Type.conformsTo(this.returnType, ((FunctionType) other).returnType) && com.gs.dmn.el.analysis.semantics.type.Type.conformsTo(((FunctionType) other).parameterTypes, this.parameterTypes); } diff --git a/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/type/FEELFunctionType.java b/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/type/FEELFunctionType.java index aada79554..ce76735f6 100644 --- a/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/type/FEELFunctionType.java +++ b/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/type/FEELFunctionType.java @@ -55,7 +55,8 @@ public boolean equivalentTo(Type other) { @Override public boolean conformsTo(Type other) { // “contravariant function argument type” and “covariant function return type” - return other instanceof FunctionType + return other == FunctionType.ANY_FUNCTION || + other instanceof FunctionType && com.gs.dmn.el.analysis.semantics.type.Type.conformsTo(this.returnType, ((FunctionType) other).returnType) && com.gs.dmn.el.analysis.semantics.type.Type.conformsTo(((FunctionType) other).parameterTypes, this.parameterTypes); } diff --git a/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/type/FunctionType.java b/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/type/FunctionType.java index 2942d236c..64be669cc 100644 --- a/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/type/FunctionType.java +++ b/dmn-core/src/main/java/com/gs/dmn/feel/analysis/semantics/type/FunctionType.java @@ -13,8 +13,8 @@ package com.gs.dmn.feel.analysis.semantics.type; import com.gs.dmn.el.analysis.semantics.type.Type; +import com.gs.dmn.feel.analysis.semantics.SemanticError; import com.gs.dmn.feel.analysis.syntax.ast.expression.function.*; -import com.gs.dmn.runtime.DMNRuntimeException; import com.gs.dmn.runtime.Pair; import java.util.*; @@ -29,12 +29,12 @@ public abstract class FunctionType implements com.gs.dmn.el.analysis.semantics.t public static final FunctionType ANY_FUNCTION = new FunctionType(Arrays.asList(), ANY) { @Override public boolean equivalentTo(Type other) { - return false; + return this == other; } @Override public boolean conformsTo(Type other) { - return false; + return equivalentTo(other); } @Override @@ -114,66 +114,72 @@ protected ArrayList, ParameterConversions>> calc ConversionKind[] candidateConversions = FUNCTION_RESOLUTION_CANDIDATES; int conversionSize = candidateConversions.length; - // For every sequence of conversions, not including the exact match + // For every sequence of conversions int[] conversionMap = init(argumentSize); + // Skip the exact match conversionMap = next(conversionMap, argumentSize, conversionSize); while (conversionMap != null) { // Calculate new types and conversions for every argument List newTypes = new ArrayList<>(); PositionalParameterConversions conversions = new PositionalParameterConversions<>(); boolean different = false; + boolean succsefulCandidate = true; for (int i = 0; i < argumentSize; i++) { // Compute new type and conversion ConversionKind kind = candidateConversions[conversionMap[i]]; Type argumentType = argumentTypes.get(i); - Type newType = argumentType; - Conversion conversion = new Conversion<>(NONE, newType); + Type newArgumentType = argumentType; + Conversion conversion = new Conversion<>(NONE, newArgumentType); if (i < parameterTypes.size()) { Type parameterType = parameterTypes.get(i); - if (!com.gs.dmn.el.analysis.semantics.type.Type.conformsTo(argumentType, parameterType)) { - if (kind == NONE) { - // No conversion - } else if (kind == ELEMENT_TO_SINGLETON_LIST) { - // When the type of the expression is T and the target type is List the expression is converted to a singleton list. - if (parameterType instanceof ListType) { - if (com.gs.dmn.el.analysis.semantics.type.Type.conformsTo(argumentType, ((ListType) parameterType).getElementType())) { - newType = new ListType(argumentType); - conversion = new Conversion<>(kind, newType); - - different = true; - } - } - } else if (kind == SINGLETON_LIST_TO_ELEMENT) { - // When the type of the expression is List, the value of the expression is a singleton list and the target type is T, - // the expression is converted by unwraping the first element. - if (argumentType instanceof ListType) { - if (com.gs.dmn.el.analysis.semantics.type.Type.conformsTo(parameterType, ((ListType) argumentType).getElementType())) { - newType = ((ListType) argumentType).getElementType(); - conversion = new Conversion<>(kind, newType); - - different = true; - } + if (kind == NONE) { + // No conversion + } else if (kind == ELEMENT_TO_SINGLETON_LIST) { + // When the type of the expression is T and the target type is List the expression is converted to a singleton list. + if (parameterType instanceof ListType) { + if (com.gs.dmn.el.analysis.semantics.type.Type.conformsTo(argumentType, ((ListType) parameterType).getElementType())) { + newArgumentType = new ListType(argumentType); + conversion = new Conversion<>(kind, newArgumentType); + different = true; } - } else if (kind == DATE_TO_UTC_MIDNIGHT) { - // When the type of the expression is date, the value of the expression is a date and the target type is date and time, - // the expression is converted to UTC midnight data and time. - if (com.gs.dmn.el.analysis.semantics.type.Type.equivalentTo(argumentType, DATE) && com.gs.dmn.el.analysis.semantics.type.Type.equivalentTo(parameterType, DATE_AND_TIME)) { - newType = DATE_AND_TIME; - conversion = new Conversion<>(kind, newType); + } + } else if (kind == SINGLETON_LIST_TO_ELEMENT) { + // When the type of the expression is List, the value of the expression is a singleton list and the target type is T, + // the expression is converted by unwraping the first element. + if (argumentType instanceof ListType) { + if (com.gs.dmn.el.analysis.semantics.type.Type.conformsTo(parameterType, ((ListType) argumentType).getElementType())) { + newArgumentType = ((ListType) argumentType).getElementType(); + conversion = new Conversion<>(kind, newArgumentType); different = true; } - } else { - throw new DMNRuntimeException(String.format("Conversion '%s' is not supported yet", kind)); } + } else if (kind == DATE_TO_UTC_MIDNIGHT) { + // When the type of the expression is date, the value of the expression is a date and the target type is date and time, + // the expression is converted to UTC midnight data and time. + if (com.gs.dmn.el.analysis.semantics.type.Type.equivalentTo(argumentType, DATE) && com.gs.dmn.el.analysis.semantics.type.Type.equivalentTo(parameterType, DATE_AND_TIME)) { + newArgumentType = DATE_AND_TIME; + conversion = new Conversion<>(kind, newArgumentType); + + different = true; + } + } else { + throw new SemanticError(String.format("Conversion '%s' is not supported yet", kind)); + } + // Check if new argument type matches + boolean newArgumentTypeOk = Type.conformsTo(newArgumentType, parameterType); + if (!newArgumentTypeOk) { + succsefulCandidate = false; + break; + } else { + newTypes.add(newArgumentType); + conversions.add(conversion); } - newTypes.add(newType); - conversions.add(conversion); } } // Add new candidate - if (different) { + if (different && succsefulCandidate) { PositionalParameterTypes newSignature = new PositionalParameterTypes<>(newTypes); candidates.add(new Pair<>(newSignature, conversions)); } diff --git a/dmn-core/src/main/java/com/gs/dmn/feel/interpreter/AbstractFEELInterpreterVisitor.java b/dmn-core/src/main/java/com/gs/dmn/feel/interpreter/AbstractFEELInterpreterVisitor.java index 0cf015ab0..0e6d8aa48 100644 --- a/dmn-core/src/main/java/com/gs/dmn/feel/interpreter/AbstractFEELInterpreterVisitor.java +++ b/dmn-core/src/main/java/com/gs/dmn/feel/interpreter/AbstractFEELInterpreterVisitor.java @@ -23,7 +23,6 @@ import com.gs.dmn.el.analysis.semantics.type.Type; import com.gs.dmn.el.synthesis.ELTranslator; import com.gs.dmn.feel.OperatorDecisionTable; -import com.gs.dmn.feel.analysis.semantics.SemanticError; import com.gs.dmn.feel.analysis.semantics.type.*; import com.gs.dmn.feel.analysis.syntax.ast.expression.Iterator; import com.gs.dmn.feel.analysis.syntax.ast.expression.*; @@ -321,7 +320,8 @@ public Object visit(ListTest element, DMNContext context) { List list = (List) optimizedListLiteral.accept(this, context); result = this.lib.listContains(list, self); } else { - throw new SemanticError(element, String.format("Cannot compare '%s', '%s'", inputExpressionType, optimizedListType)); + handleError(context, element, String.format("Cannot compare '%s', '%s'", inputExpressionType, optimizedListType)); + return null; } } else { // test is list of ranges compatible with input @@ -636,7 +636,8 @@ public Object visit(BetweenExpression element, DMNContext context) { } else if (leftEndpoint.getType() == DurationType.DAYS_AND_TIME_DURATION) { return this.lib.booleanAnd(this.lib.durationLessEqualThan((DURATION) leftOpd, (DURATION) value), this.lib.durationLessEqualThan((DURATION) value, (DURATION) rightOpd)); } else{ - throw new DMNRuntimeException(String.format("Type '%s' is not supported yet", leftEndpoint.getType())); + handleError(context, element, String.format("Type '%s' is not supported yet", leftEndpoint.getType())); + return null; } } catch (Exception e) { this.errorHandler.reportError(String.format("Cannot evaluate '%s'", element), e); @@ -742,7 +743,8 @@ private Object evaluateFunction(Expression function, FunctionType function Object evaluateFunctionInvocation(Object functionDefinition, FunctionType functionType, List argList) { if (functionType instanceof DMNFunctionType || functionType instanceof FEELFunctionType) { if (functionDefinition == null) { - throw new DMNRuntimeException(String.format("Missing function definition, expecting value of type for '%s'", functionType)); + handleError(String.format("Missing function definition, expecting value of type for '%s'", functionType)); + return null; } if (functionDefinition instanceof DMNInvocable) { return evaluateInvocableDefinition((DMNInvocable) functionDefinition, argList); } else if (functionDefinition instanceof DMNFunction) { @@ -752,7 +754,8 @@ Object evaluateFunctionInvocation(Object functionDefinition, FunctionType functi } else if (functionDefinition instanceof BuiltinFunction) { return evaluateBuiltInFunction((BuiltinFunction) functionDefinition, argList); } else { - throw new DMNRuntimeException(String.format("Not supported yet %s", functionDefinition.getClass().getSimpleName())); + handleError(String.format("Not supported yet %s", functionDefinition.getClass().getSimpleName())); + return null; } } else if (functionType instanceof BuiltinFunctionType) { String functionName = functionName(functionDefinition); @@ -761,20 +764,22 @@ Object evaluateFunctionInvocation(Object functionDefinition, FunctionType functi Object secondArg = argList.get(1); if (secondArg instanceof Function) { Function sortFunction = (Function) secondArg; - List result = ((StandardFEELLib) lib).sort((List) argList.get(0), makeComparator(sortFunction)); + List result = ((StandardFEELLib) lib).sort((List) argList.get(0), makeLambdaExpression(sortFunction)); return result; } else { - throw new DMNRuntimeException(String.format("'%s' is not supported yet", secondArg.getClass())); + handleError(String.format("'%s' is not supported yet", secondArg.getClass())); + return null; } } else { return evaluateBuiltInFunction(this.lib, javaFunctionName, argList); } } else { - throw new DMNRuntimeException(String.format("Not supported yet %s", functionDefinition.getClass().getSimpleName())); + handleError(String.format("Not supported yet %s", functionDefinition.getClass().getSimpleName())); + return null; } } - private LambdaExpression makeComparator(Function sortFunction) { + private LambdaExpression makeLambdaExpression(Function sortFunction) { return new LambdaExpression() { @Override public Boolean apply(Object... args) { @@ -788,7 +793,8 @@ public Boolean apply(Object... args) { } else if (sortFunction instanceof DMNInvocable) { return (Boolean) evaluateInvocableDefinition((DMNInvocable) sortFunction, argList); } else { - throw new DMNRuntimeException(String.format("Not supported yet '%s'", sortFunction.getClass())); + handleError(String.format("Not supported yet '%s'", sortFunction.getClass())); + return null; } } }; diff --git a/dmn-core/src/main/java/com/gs/dmn/feel/interpreter/StandardFEELInterpreterVisitor.java b/dmn-core/src/main/java/com/gs/dmn/feel/interpreter/StandardFEELInterpreterVisitor.java index 3cf51be73..d0c09106a 100644 --- a/dmn-core/src/main/java/com/gs/dmn/feel/interpreter/StandardFEELInterpreterVisitor.java +++ b/dmn-core/src/main/java/com/gs/dmn/feel/interpreter/StandardFEELInterpreterVisitor.java @@ -13,7 +13,6 @@ package com.gs.dmn.feel.interpreter; import com.gs.dmn.feel.lib.StandardFEELLib; -import com.gs.dmn.runtime.DMNRuntimeException; import com.gs.dmn.runtime.interpreter.DMNInterpreter; class StandardFEELInterpreterVisitor extends AbstractFEELInterpreterVisitor { @@ -56,7 +55,8 @@ protected Object evaluateDateTimeMember(Object source, String member) { } else if ("seconds".equals(member)) { return lib.seconds((DURATION) source); } else { - throw new DMNRuntimeException(String.format("Cannot resolve method '%s' for date time", member)); + handleError(String.format("Cannot resolve method '%s' for date time", member)); + return null; } } } diff --git a/dmn-core/src/main/java/com/gs/dmn/feel/synthesis/AbstractFEELToJavaVisitor.java b/dmn-core/src/main/java/com/gs/dmn/feel/synthesis/AbstractFEELToJavaVisitor.java index 303a2f13b..705e22a79 100644 --- a/dmn-core/src/main/java/com/gs/dmn/feel/synthesis/AbstractFEELToJavaVisitor.java +++ b/dmn-core/src/main/java/com/gs/dmn/feel/synthesis/AbstractFEELToJavaVisitor.java @@ -20,7 +20,6 @@ import com.gs.dmn.feel.analysis.AbstractAnalysisVisitor; import com.gs.dmn.feel.analysis.syntax.ast.expression.Expression; import com.gs.dmn.feel.analysis.syntax.ast.expression.Name; -import com.gs.dmn.runtime.DMNRuntimeException; import com.gs.dmn.runtime.function.BuiltinFunction; import com.gs.dmn.transformation.basic.BasicDMNToNativeTransformer; import org.apache.commons.lang3.StringUtils; @@ -113,7 +112,8 @@ protected String functionName(Object function) { List declarations = ((BuiltinFunction) function).getDeclarations(); return declarations.get(0).getName(); } catch (Exception e) { - throw new DMNRuntimeException(String.format("Cannot find name of builtin function '%s'", function)); + handleError(String.format("Cannot find name of builtin function '%s'", function)); + return null; } } } diff --git a/dmn-core/src/main/java/com/gs/dmn/feel/synthesis/FEELToNativeForInterpreterVisitor.java b/dmn-core/src/main/java/com/gs/dmn/feel/synthesis/FEELToNativeForInterpreterVisitor.java deleted file mode 100644 index 38e944abd..000000000 --- a/dmn-core/src/main/java/com/gs/dmn/feel/synthesis/FEELToNativeForInterpreterVisitor.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2016 Goldman Sachs. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. - * - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package com.gs.dmn.feel.synthesis; - -import com.gs.dmn.context.DMNContext; -import com.gs.dmn.el.analysis.semantics.type.Type; -import com.gs.dmn.feel.analysis.semantics.type.ItemDefinitionType; -import com.gs.dmn.feel.analysis.syntax.ast.expression.Expression; -import com.gs.dmn.transformation.basic.BasicDMNToNativeTransformer; - -public class FEELToNativeForInterpreterVisitor extends FEELToNativeVisitor { - public FEELToNativeForInterpreterVisitor(BasicDMNToNativeTransformer dmnTransformer) { - super(dmnTransformer); - } - - @Override - protected String makeNavigation(Expression element, Type sourceType, String source, String memberName, String memberVariableName) { - if (sourceType instanceof ItemDefinitionType) { - String javaType = dmnTransformer.toNativeType(((ItemDefinitionType) sourceType).getMemberType(memberName)); - return this.nativeFactory.makeItemDefinitionSelectExpression(source, memberName, javaType); - } else { - return super.makeNavigation(element, sourceType, source, memberName, memberVariableName); - } - } -} diff --git a/dmn-core/src/main/java/com/gs/dmn/feel/synthesis/FEELToNativeVisitor.java b/dmn-core/src/main/java/com/gs/dmn/feel/synthesis/FEELToNativeVisitor.java index ab1f1b418..d0b65076a 100644 --- a/dmn-core/src/main/java/com/gs/dmn/feel/synthesis/FEELToNativeVisitor.java +++ b/dmn-core/src/main/java/com/gs/dmn/feel/synthesis/FEELToNativeVisitor.java @@ -23,7 +23,6 @@ import com.gs.dmn.el.analysis.semantics.type.AnyType; import com.gs.dmn.el.analysis.semantics.type.Type; import com.gs.dmn.feel.OperatorDecisionTable; -import com.gs.dmn.feel.analysis.semantics.SemanticError; import com.gs.dmn.feel.analysis.semantics.type.*; import com.gs.dmn.feel.analysis.syntax.ast.Element; import com.gs.dmn.feel.analysis.syntax.ast.expression.*; @@ -46,7 +45,6 @@ import com.gs.dmn.feel.analysis.syntax.ast.expression.type.*; import com.gs.dmn.feel.analysis.syntax.ast.test.*; import com.gs.dmn.feel.lib.StringEscapeUtil; -import com.gs.dmn.runtime.DMNRuntimeException; import com.gs.dmn.runtime.Pair; import com.gs.dmn.transformation.basic.BasicDMNToNativeTransformer; import com.gs.dmn.transformation.basic.ImportContextType; @@ -166,7 +164,8 @@ public String visit(ListTest element, DMNContext context) { String args = String.format("%s, %s", javaList, inputExpressionToJava(context)); return this.nativeFactory.makeBuiltinFunctionInvocation("listContains", args); } else { - throw new SemanticError(element, String.format("Cannot compare '%s', '%s'", inputExpressionType, optimizedListType)); + handleError(context, element, String.format("Cannot compare '%s', '%s'", inputExpressionType, optimizedListType)); + return null; } } else { // test is list of ranges compatible with input @@ -186,7 +185,8 @@ public String visit(FunctionDefinition element, DMNContext context) { String body = (String)element.getBody().accept(this, context); return this.dmnTransformer.functionDefinitionToNative((TDRGElement) context.getElement(), element, false, body); } else { - throw new DMNRuntimeException("Dynamic typing for FEEL functions not supported yet"); + handleError(context, element, "Dynamic typing for FEEL functions not supported yet"); + return null; } } @@ -497,7 +497,7 @@ public String visit(PathExpression element, DMNContext context) { String source = (String) sourceExpression.accept(this, context); String member = element.getMember(); - return makeNavigation(element, sourceType, source, member, nativeFriendlyVariableName(member)); + return makeNavigation(element, sourceType, source, member, nativeFriendlyVariableName(member), context); } @Override @@ -544,7 +544,8 @@ public String visit(FunctionInvocation element, DMNContext context) { String argumentsText = argList.stream().map(Object::toString).collect(Collectors.joining(", ")); return this.nativeFactory.makeApplyInvocation(javaFunctionCode, argumentsText); } else { - throw new DMNRuntimeException(String.format("Not supported function type '%s' in '%s'", functionType, context.getElementName())); + handleError(context, element, String.format("Not supported function type '%s' in '%s'", functionType, context.getElementName())); + return null; } } @@ -672,7 +673,8 @@ protected String nameToJava(String name, DMNContext context) { private String inputExpressionToJava(DMNContext context) { if (context.isExpressionContext()) { - throw new DMNRuntimeException(String.format("Missing inputExpression in context of element '%s'", context.getElementName())); + handleError(context, null, String.format("Missing inputExpression in context of element '%s'", context.getElementName())); + return null; } else { // Evaluate as test Expression inputExpression = (Expression) context.getInputExpression(); @@ -705,12 +707,13 @@ protected String handleNotSupportedElement(Element element) { throw new UnsupportedOperationException("FEEL '" + (element == null ? null : element.getClass().getSimpleName()) + "' is not supported in this context"); } - protected String makeNavigation(Expression element, Type sourceType, String source, String memberName, String memberVariableName) { + protected String makeNavigation(Expression element, Type sourceType, String source, String memberName, String memberVariableName, DMNContext context) { if (sourceType instanceof ImportContextType) { ImportContextType importContextType = (ImportContextType) sourceType; DRGElementReference memberReference = importContextType.getMemberReference(memberName); if (memberReference == null) { - throw new DMNRuntimeException(String.format("Cannot find reference for '%s'", memberName)); + handleError(context, element, String.format("Cannot find reference for '%s'", memberName)); + return null; } TDRGElement drgElement = memberReference.getElement(); if (drgElement instanceof TBusinessKnowledgeModel) { @@ -728,7 +731,7 @@ protected String makeNavigation(Expression element, Type sourceType, Strin String javaType = this.dmnTransformer.toNativeType(memberType); return this.nativeFactory.makeContextAccessor(javaType, source, memberName); } else if (sourceType instanceof ListType) { - String filter = makeNavigation(element, ((ListType) sourceType).getElementType(), "x", memberName, memberVariableName); + String filter = makeNavigation(element, ((ListType) sourceType).getElementType(), "x", memberName, memberVariableName, context); return this.nativeFactory.makeCollectionMap(source, filter); } else if (sourceType instanceof DateType) { return this.nativeFactory.makeBuiltinFunctionInvocation(propertyFunctionName(memberName), source); @@ -744,7 +747,8 @@ protected String makeNavigation(Expression element, Type sourceType, Strin // source is Context return this.nativeFactory.makeContextSelectExpression(this.dmnTransformer.contextClassName(), source, memberName); } else { - throw new SemanticError(element, String.format("Cannot generate navigation path '%s'", element)); + handleError(context, element, String.format("Cannot generate navigation path '%s'", element)); + return null; } } @@ -757,7 +761,8 @@ protected String makeCondition(String feelOperator, Expression leftOperand protected String makeCondition(String feelOperator, String leftOpd, String rightOpd, NativeOperator javaOperator) { if (javaOperator == null) { - throw new DMNRuntimeException(String.format("Operator '%s' cannot be applied to '%s' and '%s'", feelOperator, leftOpd, rightOpd)); + handleError(String.format("Operator '%s' cannot be applied to '%s' and '%s'", feelOperator, leftOpd, rightOpd)); + return null; } else { if (javaOperator.getCardinality() == 2) { if (javaOperator.getNotation() == NativeOperator.Notation.FUNCTIONAL) { @@ -774,7 +779,8 @@ protected String makeCondition(String feelOperator, String leftOpd, String right } } } else { - throw new DMNRuntimeException(String.format("Operator '%s' cannot be applied to '%s' and '%s'", feelOperator, leftOpd, rightOpd)); + handleError(String.format("Operator '%s' cannot be applied to '%s' and '%s'", feelOperator, leftOpd, rightOpd)); + return null; } } } @@ -784,7 +790,8 @@ protected String listTestOperator(String feelOperatorName, Expression left if (javaOperator != null) { return javaOperator.getName(); } else { - throw new DMNRuntimeException(String.format("Operator '%s' cannot be applied to '%s' and '%s'", feelOperatorName, leftOperand, rightOperand)); + handleError(String.format("Operator '%s' cannot be applied to '%s' and '%s'", feelOperatorName, leftOperand, rightOperand)); + return null; } } diff --git a/dmn-core/src/main/java/com/gs/dmn/feel/synthesis/FEELToTripleNativeVisitor.java b/dmn-core/src/main/java/com/gs/dmn/feel/synthesis/FEELToTripleNativeVisitor.java index 20f9c2f87..24ff5fa5e 100644 --- a/dmn-core/src/main/java/com/gs/dmn/feel/synthesis/FEELToTripleNativeVisitor.java +++ b/dmn-core/src/main/java/com/gs/dmn/feel/synthesis/FEELToTripleNativeVisitor.java @@ -25,7 +25,6 @@ import com.gs.dmn.el.synthesis.triple.Triple; import com.gs.dmn.el.synthesis.triple.Triples; import com.gs.dmn.feel.OperatorDecisionTable; -import com.gs.dmn.feel.analysis.semantics.SemanticError; import com.gs.dmn.feel.analysis.semantics.type.*; import com.gs.dmn.feel.analysis.syntax.ast.Element; import com.gs.dmn.feel.analysis.syntax.ast.expression.Iterator; @@ -48,7 +47,6 @@ import com.gs.dmn.feel.analysis.syntax.ast.expression.textual.*; import com.gs.dmn.feel.analysis.syntax.ast.expression.type.*; import com.gs.dmn.feel.analysis.syntax.ast.test.*; -import com.gs.dmn.runtime.DMNRuntimeException; import com.gs.dmn.runtime.Pair; import com.gs.dmn.transformation.basic.BasicDMNToNativeTransformer; import com.gs.dmn.transformation.basic.ImportContextType; @@ -169,7 +167,8 @@ public Triple visit(ListTest element, DMNContext context) { Triple javaList = (Triple) optimizedListLiteral.accept(this, context); return this.triples.makeBuiltinFunctionInvocation("listContains", javaList, inputExpressionToJava(context)); } else { - throw new SemanticError(element, String.format("Cannot compare '%s', '%s'", inputExpressionType, optimizedListType)); + handleError(context, element, String.format("Cannot compare '%s', '%s'", inputExpressionType, optimizedListType)); + return null; } } else { // test is list of ranges compatible with input @@ -188,7 +187,8 @@ public Triple visit(FunctionDefinition element, DMNContext context) { Triple body = (Triple) element.getBody().accept(this, context); return this.triples.makeFunctionDefinition((TDRGElement) context.getElement(), element, false, body); } else { - throw new DMNRuntimeException("Dynamic typing for FEEL functions not supported yet"); + handleError("Dynamic typing for FEEL functions not supported yet"); + return null; } } @@ -495,7 +495,7 @@ public Triple visit(PathExpression element, DMNContext context) { Triple source = (Triple) sourceExpression.accept(this, context); String member = element.getMember(); - return makeNavigation(element, sourceType, source, member, nativeFriendlyVariableName(member)); + return makeNavigation(element, sourceType, source, member, nativeFriendlyVariableName(member), context); } @Override @@ -542,17 +542,19 @@ public Triple visit(FunctionInvocation element, DMNContext context) { List operands = visitArgList(argList); return this.triples.makeApplyInvocation(javaFunctionCode, operands); } else { - throw new DMNRuntimeException(String.format("Not supported function type '%s' in '%s'", functionType, context.getElementName())); + handleError(String.format("Not supported function type '%s' in '%s'", functionType, context.getElementName())); + return null; } } - private static List visitArgList(List argList) { + private List visitArgList(List argList) { List operands = new ArrayList<>(); for (Object arg : argList) { if (arg instanceof Triple) { operands.add((Triple) arg); } else { - throw new DMNRuntimeException("Illegal arg, should be Operand"); + handleError("Illegal arg, should be Operand"); + return null; } } return operands; @@ -566,7 +568,8 @@ protected Triple convertArgument(Object param, Conversion conversion) { if (param instanceof Triple) { return this.triples.makeConvertArgument((Triple) param, conversion); } else { - throw new DMNRuntimeException(String.format("Expected operand, found '%s'", param)); + handleError(String.format("Expected operand, found '%s'", param)); + return null; } } @@ -679,7 +682,8 @@ protected Triple nameToJava(String name, DMNContext context) { private Triple inputExpressionToJava(DMNContext context) { if (context.isExpressionContext()) { - throw new DMNRuntimeException(String.format("Missing inputExpression in context of element '%s'", context.getElementName())); + handleError(String.format("Missing inputExpression in context of element '%s'", context.getElementName())); + return null; } else { // Evaluate as test Expression inputExpression = (Expression) context.getInputExpression(); @@ -712,12 +716,13 @@ protected Triple handleNotSupportedElement(Element element) { throw new UnsupportedOperationException("FEEL '" + (element == null ? null : element.getClass().getSimpleName()) + "' is not supported in this context"); } - protected Triple makeNavigation(Expression element, Type sourceType, Triple source, String memberName, String memberVariableName) { + protected Triple makeNavigation(Expression element, Type sourceType, Triple source, String memberName, String memberVariableName, DMNContext context) { if (sourceType instanceof ImportContextType) { ImportContextType importContextType = (ImportContextType) sourceType; DRGElementReference memberReference = importContextType.getMemberReference(memberName); if (memberReference == null) { - throw new DMNRuntimeException(String.format("Cannot find reference for '%s'", memberName)); + handleError(context, element, String.format("Cannot find reference for '%s'", memberName)); + return null; } TDRGElement drgElement = memberReference.getElement(); if (drgElement instanceof TInvocable) { @@ -735,7 +740,7 @@ protected Triple makeNavigation(Expression element, Type sourceType, Tripl String javaType = this.dmnTransformer.toNativeType(memberType); return this.triples.makeContextAccessor(javaType, source, memberName); } else if (sourceType instanceof ListType) { - Triple filter = makeNavigation(element, ((ListType) sourceType).getElementType(), triples.name("x"), memberName, memberVariableName); + Triple filter = makeNavigation(element, ((ListType) sourceType).getElementType(), triples.name("x"), memberName, memberVariableName, context); return this.triples.makeCollectionMap(source, filter); } else if (sourceType instanceof DateType) { return this.triples.makeBuiltinFunctionInvocation(propertyFunctionName(memberName), source); @@ -751,7 +756,8 @@ protected Triple makeNavigation(Expression element, Type sourceType, Tripl // source is Context return this.triples.makeContextSelectExpression(this.dmnTransformer.contextClassName(), source, memberName); } else { - throw new SemanticError(element, String.format("Cannot generate navigation path '%s'", element)); + handleError(context, element, String.format("Cannot generate navigation path '%s'", element)); + return null; } } @@ -764,7 +770,8 @@ protected Triple makeCondition(String feelOperator, Expression leftOperand protected Triple makeCondition(String feelOperator, Triple leftOpd, Triple rightOpd, NativeOperator javaOperator) { if (javaOperator == null) { - throw new DMNRuntimeException(String.format("Operator '%s' cannot be applied to '%s' and '%s'", feelOperator, leftOpd, rightOpd)); + handleError(String.format("Operator '%s' cannot be applied to '%s' and '%s'", feelOperator, leftOpd, rightOpd)); + return null; } else { if (javaOperator.getCardinality() == 2) { if (javaOperator.getNotation() == NativeOperator.Notation.FUNCTIONAL) { @@ -781,7 +788,8 @@ protected Triple makeCondition(String feelOperator, Triple leftOpd, Triple right } } } else { - throw new DMNRuntimeException(String.format("Operator '%s' cannot be applied to '%s' and '%s'", feelOperator, leftOpd, rightOpd)); + handleError(String.format("Operator '%s' cannot be applied to '%s' and '%s'", feelOperator, leftOpd, rightOpd)); + return null; } } } @@ -791,7 +799,8 @@ protected String listTestOperator(String feelOperatorName, Expression left if (javaOperator != null) { return javaOperator.getName(); } else { - throw new DMNRuntimeException(String.format("Operator '%s' cannot be applied to '%s' and '%s'", feelOperatorName, leftOperand, rightOperand)); + handleError(String.format("Operator '%s' cannot be applied to '%s' and '%s'", feelOperatorName, leftOperand, rightOperand)); + return null; } } diff --git a/dmn-runtime/src/main/java/com/gs/dmn/runtime/annotation/AnnotationSet.java b/dmn-runtime/src/main/java/com/gs/dmn/runtime/annotation/AnnotationSet.java index 523a3882f..b723fd6ae 100644 --- a/dmn-runtime/src/main/java/com/gs/dmn/runtime/annotation/AnnotationSet.java +++ b/dmn-runtime/src/main/java/com/gs/dmn/runtime/annotation/AnnotationSet.java @@ -20,6 +20,16 @@ import java.util.Set; public class AnnotationSet extends LinkedList { + private final int maxAnnotations; + + public AnnotationSet() { + this(200); + } + + public AnnotationSet(int maxAnnotations) { + this.maxAnnotations = maxAnnotations; + } + public void addAnnotation(String decisionName, int ruleIndex, List annotationList) { if (annotationList != null && !annotationList.isEmpty()) { String annotation = String.join(" ", annotationList); @@ -30,8 +40,13 @@ public void addAnnotation(String decisionName, int ruleIndex, List annot public void addAnnotation(String decisionName, int ruleIndex, String annotation) { if (!StringUtils.isBlank(annotation)) { // Rules index starts from 0 - Annotation element = new Annotation(decisionName, ruleIndex + 1, annotation); - this.add(element); + if (size() < maxAnnotations) { + Annotation element = new Annotation(decisionName, ruleIndex + 1, annotation); + this.add(element); + } else if (size() == maxAnnotations) { + Annotation element = new Annotation(decisionName, ruleIndex + 1, String.format("Too many annotations, maximum number is %s", maxAnnotations)); + this.add(element); + } } } diff --git a/dmn-runtime/src/test/java/com/gs/dmn/runtime/annotation/AnnotationSetTest.java b/dmn-runtime/src/test/java/com/gs/dmn/runtime/annotation/AnnotationSetTest.java index 550260e1d..23ff4cfe3 100644 --- a/dmn-runtime/src/test/java/com/gs/dmn/runtime/annotation/AnnotationSetTest.java +++ b/dmn-runtime/src/test/java/com/gs/dmn/runtime/annotation/AnnotationSetTest.java @@ -17,6 +17,22 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class AnnotationSetTest { + @Test + public void testThreshold() { + AnnotationSet set = new AnnotationSet(2); + set.addAnnotation("Name", 0, "text1"); + set.addAnnotation("Name", 0, "text2"); + + assertEquals(2, set.size()); + + set.addAnnotation("Name", 0, "text3"); + + assertEquals(3, set.size()); + assertEquals("text1", set.get(0).getAnnotation()); + assertEquals("text2", set.get(1).getAnnotation()); + assertEquals("Too many annotations, maximum number is 2", set.get(2).getAnnotation()); + } + @Test public void testIsSet() { AnnotationSet set = new AnnotationSet();