Skip to content

Commit

Permalink
[#655] DMN 1.5: Support for negative duration function (DMN15-86)
Browse files Browse the repository at this point in the history
  • Loading branch information
opatrascoiu committed Oct 29, 2024
1 parent 0f473da commit b24a625
Show file tree
Hide file tree
Showing 16 changed files with 736 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -648,12 +648,12 @@ public Element<Type> visit(ArithmeticNegation<Type> element, DMNContext context)

// Derive type
Type type = element.getLeftOperand().getType();
element.setType(NUMBER);
if (type != NUMBER) {
if (type != NUMBER && !(type instanceof DurationType)) {
handleError(context, element, String.format("Operator '%s' cannot be applied to '%s'", element.getOperator(), type));
}

// Derive type
element.setType(type);
return element;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,12 @@ public Object visit(ArithmeticNegation<Type> element, DMNContext context) {
LOGGER.debug("Visiting element '{}'", element);

Object leftOperand = element.getLeftOperand().accept(this, context);
return this.lib.numericUnaryMinus((NUMBER) leftOperand);
Type type = element.getType();
if (type == NumberType.NUMBER) {
return this.lib.numericUnaryMinus((NUMBER) leftOperand);
} else {
return this.lib.durationUnaryMinus((DURATION) leftOperand);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
import java.util.*;
import java.util.stream.Collectors;

import static com.gs.dmn.feel.analysis.semantics.type.NumberType.NUMBER;

public class FEELToTripleNativeVisitor extends AbstractFEELToJavaVisitor<Object> {
private static final int INITIAL_VALUE = -1;
private int filterCount = INITIAL_VALUE;
Expand Down Expand Up @@ -366,7 +368,7 @@ public Triple visit(FilterExpression<Type> element, DMNContext context) {
// Filter
if (filterType == BooleanType.BOOLEAN) {
return this.triples.makeCollectionLogicFilter(source, newParameterName, filter);
} else if (filterType == NumberType.NUMBER) {
} else if (filterType == NUMBER) {
// Compute element type
Type elementType;
if (sourceType instanceof ListType) {
Expand Down Expand Up @@ -514,7 +516,12 @@ public Triple visit(Exponentiation<Type> element, DMNContext context) {
public Triple visit(ArithmeticNegation<Type> element, DMNContext context) {
Expression<Type> leftOperand = element.getLeftOperand();
Triple leftOpd = (Triple) leftOperand.accept(this, context);
return this.triples.makeBuiltinFunctionInvocation("numericUnaryMinus", leftOpd);
Type type = element.getType();
if (type == NUMBER) {
return this.triples.makeBuiltinFunctionInvocation("numericUnaryMinus", leftOpd);
} else {
return this.triples.makeBuiltinFunctionInvocation("durationUnaryMinus", leftOpd);
}
}

//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2192,46 +2192,67 @@ public void testExponentiation() {
@Test
public void testArithmeticNegation() {
String number = "1";
String yearsAndMonthsDuration = "@\"P1Y1M\"";
String daysAndTimeDuration = "@\"P1DT1H\"";
String duration = "duration(\"P1DT1H\")";
List<EnvironmentEntry> entries = Collections.singletonList(
new EnvironmentEntry("input", NUMBER, this.lib.number("1")));
new EnvironmentEntry("input", NUMBER, this.lib.number("1"))
);

doExpressionTest(entries, "", String.format("- %s", number),
"ArithmeticNegation(NumericLiteral(1))",
"number",
"numericUnaryMinus(number(\"1\"))",
this.lib.numericUnaryMinus(this.lib.number("1")),
this.lib.number("-1"));

doExpressionTest(entries, "", String.format("-- %s", number),
"NumericLiteral(1)",
"number",
"number(\"1\")",
this.lib.number("1"),
this.lib.number("1"));

doExpressionTest(entries, "", String.format("--- %s", number),
"ArithmeticNegation(NumericLiteral(1))",
"number",
"numericUnaryMinus(number(\"1\"))",
this.lib.numericUnaryMinus(this.lib.number("1")),
this.lib.number("-1"));

doExpressionTest(entries, "", String.format("- %s", yearsAndMonthsDuration),
"ArithmeticNegation(DateTimeLiteral(duration, \"P1Y1M\"))",
"years and months duration",
"durationUnaryMinus(duration(\"P1Y1M\"))",
this.lib.durationUnaryMinus(this.lib.duration("P1Y1M")),
this.lib.duration("-P1Y1M"));
doExpressionTest(entries, "", String.format("- %s", daysAndTimeDuration),
"ArithmeticNegation(DateTimeLiteral(duration, \"P1DT1H\"))",
"days and time duration",
"durationUnaryMinus(duration(\"P1DT1H\"))",
this.lib.durationUnaryMinus(this.lib.duration("P1DT1H")),
this.lib.duration("-PT25H"));
doExpressionTest(entries, "", String.format("- %s", duration),
"ArithmeticNegation(DateTimeLiteral(duration, \"P1DT1H\"))",
"days and time duration",
"durationUnaryMinus(duration(\"P1DT1H\"))",
this.lib.durationUnaryMinus(this.lib.duration("P1DT1H")),
this.lib.duration("-PT25H"));
}

@Test
public void testArithmeticNegationOnIncorrectOperands() {
Assertions.assertThrows(SemanticError.class, () -> {
String yearsAndMonthsDuration = "duration(\"P1Y1M\")";
String daysAndTimeDuration = "duration(\"P1DT1H\")";
String date = "date(\"2020-01-01\")";
String time = "time(\"21:00:00\")";

List<EnvironmentEntry> entries = Collections.singletonList(
new EnvironmentEntry("input", NUMBER, this.lib.number("1")));
doExpressionTest(entries, "", String.format("- %s", yearsAndMonthsDuration),
doExpressionTest(entries, "", String.format("- %s", date),
"ArithmeticNegation(DateTimeLiteral(duration, \"P1Y1M\"))",
"years and months duration",
"numericUnaryMinus(duration(\"P1Y1M\"))",
null,
null);
doExpressionTest(entries, "", String.format("- %s", daysAndTimeDuration),
doExpressionTest(entries, "", String.format("- %s", time),
"ArithmeticNegation(DateTimeLiteral(duration, \"P1DT1H\"))",
"days and time duration",
"numericUnaryMinus(duration(\"P1DT1H\"))",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,11 @@ public void test_15_cl3_0068_feel_equality() {
doSingleModelTest("1.5", "0068-feel-equality", new Pair<>("strongTyping", "false") );
}

@Test
public void test_15_cl3_0099_arithmetic_negation() {
doSingleModelTest("1.5", "0099-arithmetic-negation");
}

@Test
public void test_15_cl3_1155_list_replace_function() {
doSingleModelTest("1.5", "1155-list-replace-function");
Expand Down
11 changes: 11 additions & 0 deletions dmn-runtime/src/main/java/com/gs/dmn/feel/lib/BaseFEELLib.java
Original file line number Diff line number Diff line change
Expand Up @@ -1113,6 +1113,17 @@ public DURATION durationDivideNumber(DURATION first, NUMBER second) {
}
}

@Override
public DURATION durationUnaryMinus(DURATION first) {
try {
return durationType.durationUnaryMinus(first);
} catch (Exception e) {
String message = String.format("durationUnaryMinus(%s", first);
logError(message, e);
return null;
}
}

//
// List operators
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,6 @@ default boolean isDuration(Object value) {
DURATION durationMultiplyNumber(DURATION first, NUMBER second);

DURATION durationDivideNumber(DURATION first, NUMBER second);

DURATION durationUnaryMinus(DURATION first);
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,21 @@ public TemporalAmount durationDivideNumber(TemporalAmount first, Number second)
}
}

@Override
public TemporalAmount durationUnaryMinus(TemporalAmount first) {
if (first == null) {
return null;
}

if (isDaysAndTimeDuration(first)) {
return ((Duration) first).negated();
} else if (isYearsAndMonthsDuration(first)) {
return ((Period) first).negated();
} else {
throw new DMNRuntimeException(String.format("Cannot negate '%s'", first));
}
}

private Long value(Period duration) {
return duration == null ? null : duration.toTotalMonths();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import javax.xml.datatype.Duration;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Period;

public class DefaultDurationType extends BaseDefaultDurationType implements DurationType<Duration, BigDecimal> {
public DefaultDurationType() {
Expand Down Expand Up @@ -110,6 +111,21 @@ public Duration durationDivideNumber(Duration first, BigDecimal second) {
}
}

@Override
public Duration durationUnaryMinus(Duration first) {
if (first == null) {
return null;
}

if (isDaysAndTimeDuration(first)) {
return first.negate();
} else if (isYearsAndMonthsDuration(first)) {
return first.negate();
} else {
throw new DMNRuntimeException(String.format("Cannot negate '%s'", first));
}
}

private BigDecimal multiplyNumbers(Long firstValue, BigDecimal second) {
return BigDecimal.valueOf(firstValue).multiply(second);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.gs.dmn.runtime.DMNRuntimeException;

import javax.xml.datatype.Duration;
import java.time.Period;

public class DoubleDurationType extends BaseDefaultDurationType implements DurationType<Duration, Double> {
public DoubleDurationType() {
Expand Down Expand Up @@ -100,4 +101,19 @@ public Duration durationDivideNumber(Duration first, Double second) {
throw new DMNRuntimeException(String.format("Cannot divide '%s' by '%s'", first, second));
}
}

@Override
public Duration durationUnaryMinus(Duration first) {
if (first == null) {
return null;
}

if (isDaysAndTimeDuration(first)) {
return first.negate();
} else if (isYearsAndMonthsDuration(first)) {
return first.negate();
} else {
throw new DMNRuntimeException(String.format("Cannot negate '%s'", first));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,9 @@ public DURATION durationMultiplyNumber(DURATION first, NUMBER second) {
public DURATION durationDivideNumber(DURATION first, NUMBER second) {
throw new DMNRuntimeException("Not supported yet");
}

@Override
public DURATION durationUnaryMinus(DURATION first) {
throw new DMNRuntimeException("Not supported yet");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Contributed to DMN TCK by StrayAlien -->
<testCases xmlns="http://www.omg.org/spec/DMN/20160719/testcase"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<modelName>0099-arithmetic-negation.dmn</modelName>

<testCase id="decision_001">
<description>negate number</description>
<resultNode name="decision_001" type="decision">
<expected>
<value xsi:type="xsd:decimal">-10</value>
</expected>
</resultNode>
</testCase>

<testCase id="decision_002">
<description>negate negative number</description>
<resultNode name="decision_002" type="decision">
<expected>
<value xsi:type="xsd:decimal">10</value>
</expected>
</resultNode>
</testCase>

<testCase id="decision_003">
<description>negate days and time duration</description>
<resultNode name="decision_003" type="decision">
<expected>
<value xsi:type="xsd:duration">-P1D</value>
</expected>
</resultNode>
</testCase>

<testCase id="decision_003_a">
<description>negate a negative days and time duration</description>
<resultNode name="decision_003_a" type="decision">
<expected>
<value xsi:type="xsd:duration">P1D</value>
</expected>
</resultNode>
</testCase>

<testCase id="decision_004">
<description>negate years and months duration</description>
<resultNode name="decision_004" type="decision">
<expected>
<value xsi:type="xsd:duration">-P1Y</value>
</expected>
</resultNode>
</testCase>

<testCase id="decision_004_a">
<description>negate a negative years and months duration</description>
<resultNode name="decision_004_a" type="decision">
<expected>
<value xsi:type="xsd:duration">P1Y</value>
</expected>
</resultNode>
</testCase>

<testCase id="decision_005">
<description>negate date gives null</description>
<resultNode name="decision_005" type="decision" errorResult="true">
<expected>
<value xsi:nil="true"></value>
</expected>
</resultNode>
</testCase>

<testCase id="decision_006">
<description>negate datetime gives null</description>
<resultNode name="decision_006" type="decision" errorResult="true">
<expected>
<value xsi:nil="true"></value>
</expected>
</resultNode>
</testCase>

<testCase id="decision_007">
<description>negate time gives null</description>
<resultNode name="decision_007" type="decision" errorResult="true">
<expected>
<value xsi:nil="true"></value>
</expected>
</resultNode>
</testCase>

<testCase id="decision_008">
<description>negate context gives null</description>
<resultNode name="decision_008" type="decision" errorResult="true">
<expected>
<value xsi:nil="true"></value>
</expected>
</resultNode>
</testCase>

<testCase id="decision_009">
<description>negate string gives null</description>
<resultNode name="decision_009" type="decision" errorResult="true">
<expected>
<value xsi:nil="true"></value>
</expected>
</resultNode>
</testCase>

<testCase id="decision_010">
<description>negate singleton list gives null</description>
<resultNode name="decision_010" type="decision" errorResult="true">
<expected>
<value xsi:nil="true"></value>
</expected>
</resultNode>
</testCase>

<testCase id="decision_011">
<description>negate range gives null</description>
<resultNode name="decision_011" type="decision" errorResult="true">
<expected>
<value xsi:nil="true"></value>
</expected>
</resultNode>
</testCase>

<testCase id="decision_012">
<description>negate expression result</description>
<resultNode name="decision_012" type="decision">
<expected>
<value xsi:type="xsd:decimal">-10</value>
</expected>
</resultNode>
</testCase>

</testCases>
Loading

0 comments on commit b24a625

Please sign in to comment.