Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[incubator-kie-issues#854] DMN - Add implicit type conversion of date to date and time when necessary #5664

Merged
merged 19 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
125bc4e
[private-bamoe-issues#244] DMN - Add implicit type conversion of date…
Jan 18, 2024
a1749fc
[private-bamoe-issues#244] WIP
Jan 23, 2024
45a23b4
Merge remote-tracking branch 'origin/main' into private-bamoe-issues#244
Jan 23, 2024
0fb8490
[private-bamoe-issues#244] WIP
Jan 23, 2024
cc25601
Merge remote-tracking branch 'origin/main' into incubator-kie-issues#854
Jan 25, 2024
00be48c
[incubator-kie-issues#854] Experiment implicit date -> date-time conv…
Jan 25, 2024
0b6d179
[incubator-kie-issues#854] Remove implicit date -> date-time conversion
Jan 25, 2024
cab1904
[incubator-kie-issues#854] Remove implicit date -> date-time conversion
Jan 25, 2024
e8b8a3f
Merge remote-tracking branch 'origin/main' into incubator-kie-issues#854
Jan 26, 2024
ab679f0
[incubator-kie-issues#854] Converting value after its evaluation, whe…
Jan 26, 2024
e53e89f
[incubator-kie-issues#854] Minor modification on test
Jan 26, 2024
1227e27
[incubator-kie-issues#854] WIP
Jan 29, 2024
e3a9f20
Merge remote-tracking branch 'origin/main' into incubator-kie-issues#854
Jan 30, 2024
43c65f0
[incubator-kie-issues#854] Centralize Coerce behaviour
Jan 31, 2024
360c576
Merge remote-tracking branch 'origin/main' into incubator-kie-issues#854
Jan 31, 2024
ae400cc
[incubator-kie-issues#854] Add tests. Removed strong assumption where…
Jan 31, 2024
7704a6e
[incubator-kie-issues#854] Enforcing coercion inside lists
Feb 1, 2024
40d4579
[incubator-kie-issues#854] Enforcing coercion inside collections
Feb 5, 2024
c810775
Merge remote-tracking branch 'origin/main' into incubator-kie-issues#854
Feb 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
*/
package org.kie.dmn.core.ast;

import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
Expand All @@ -37,6 +40,7 @@
import org.kie.dmn.core.impl.DMNResultImpl;
import org.kie.dmn.core.impl.DMNRuntimeEventManagerUtils;
import org.kie.dmn.core.impl.DMNRuntimeImpl;
import org.kie.dmn.core.impl.SimpleTypeImpl;
import org.kie.dmn.core.util.Msg;
import org.kie.dmn.core.util.MsgUtil;
import org.kie.dmn.model.api.Context;
Expand Down Expand Up @@ -99,12 +103,8 @@ public EvaluatorResult evaluate(DMNRuntimeEventManager eventManager, DMNResult d
EvaluatorResult er = ed.getEvaluator().evaluate( eventManager, result );
if ( er.getResultType() == ResultType.SUCCESS ) {
Object value = er.getResult();
if( ! ed.getType().isCollection() && value instanceof Collection &&
((Collection)value).size()==1 ) {
// spec defines that "a=[a]", i.e., singleton collections should be treated as the single element
// and vice-versa
value = ((Collection)value).toArray()[0];
}
value = optionallyConvertCollectionToArray(value, ed.getType());
value = optionallyConvertDateToDateTime(value, ed.getType());

if (((DMNRuntimeImpl) eventManager.getRuntime()).performRuntimeTypeCheck(result.getModel())) {
if (!(ed.getContextEntry().getExpression() instanceof FunctionDefinition)) {
Expand Down Expand Up @@ -172,6 +172,28 @@ public EvaluatorResult evaluate(DMNRuntimeEventManager eventManager, DMNResult d
}
}

static Object optionallyConvertCollectionToArray(Object originalValue, DMNType requiredType) {
Object toReturn = originalValue;
if( ! requiredType.isCollection() &&
originalValue instanceof Collection collection &&
collection.size()==1 ) {
// spec defines that "a=[a]", i.e., singleton collections should be treated as the single element
// and vice-versa
toReturn = collection.toArray()[0];
}
return toReturn;
}

static Object optionallyConvertDateToDateTime(Object originalValue, DMNType requiredType) {
Object toReturn = originalValue;
if (originalValue instanceof LocalDate localDate &&
requiredType instanceof SimpleTypeImpl simpleType &&
simpleType.getFeelType().getName().equals("date and time")) {
toReturn = ZonedDateTime.of(localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth(), 0, 0, 0, 0, ZoneOffset.UTC);
}
return toReturn;
}

private String getEntryExprId(ContextEntryDef ed) {
return ed.getContextEntry().getExpression() != null ? ed.getContextEntry().getExpression().getId() : null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;

import javax.xml.namespace.QName;

import org.antlr.v4.runtime.CommonToken;
import org.kie.dmn.api.core.DMNContext;
import org.kie.dmn.api.core.DMNMessage;
Expand All @@ -53,6 +56,7 @@
import org.kie.dmn.feel.runtime.events.UnknownVariableErrorEvent;
import org.kie.dmn.feel.util.ClassLoaderUtil;
import org.kie.dmn.model.api.DMNElement;
import org.kie.dmn.model.api.InformationItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -138,6 +142,17 @@ public CompiledExpression compileFeelExpression(DMNCompilerContext ctx, String e
feelctx.addInputVariableType( entry.getKey(), ((BaseDMNTypeImpl) entry.getValue()).getFeelType() );
}
feelctx.setFEELTypeRegistry(model.getTypeRegistry());
// TODO: EXPERIMENT
// String baseExpression = expression.contains("(") ? expression.substring(0, expression.indexOf("(")) : expression;
// baseExpression = baseExpression.replace(" ", "");
// if (baseExpression.equals("date")) {
// Optional<Type> type = getTypeFromParent(element, feelctx);
// // Optional<QName> qnameType = getFirstInformationItemQNameFromParent(element);
// if (type.isPresent() &&
// type.get().getName().equals("date and time")) {
// expression = "date and time" + (expression.substring(expression.indexOf("(")));
// }
// }
CompiledExpression ce = feel.compile( expression, feelctx );
processEvents( model, element, errorMsg, msgParams );
return ce;
Expand Down Expand Up @@ -308,4 +323,43 @@ public ClassOrInterfaceDeclaration generateFeelExpressionSource(String input, Co
public CompilationUnit generateFeelExpressionCompilationUnit(String input, CompilerContext compilerContext1) {
return ((FEELImpl) feel).compileExpression(input, compilerContext1).getSourceCode();
}

// static String optionallyFixDateWithDateTimeExpression() {
//
// }

/**
* Returns the <code>Optional&lt;QName&gt;</code> from the first <code>InformationItem</code> found in
* the <code>element</code>'s parent, if found, otherwise <code>Optional.empty()</code>
* @param element
* @return
*/
static Optional<Type> getTypeFromParent(DMNElement element, CompilerContext feelctx) {
return element.getParent() != null ?
element.getParent().getChildren()
.stream()
.filter(InformationItem.class::isInstance)
.findFirst()
.map(i -> (InformationItem)i)
.filter(i -> feelctx.getInputVariableTypes().containsKey(i.getName()))
.map(i -> feelctx.getInputVariableTypes().get(i.getName())) :
Optional.empty();
}

/**
* Returns the <code>Optional&lt;QName&gt;</code> from the first <code>InformationItem</code> found in
* the <code>element</code>'s parent, if found, otherwise <code>Optional.empty()</code>
* @param element
* @return
*/
static Optional<QName> getFirstInformationItemQNameFromParent(DMNElement element) {
return element.getParent() != null ?
element.getParent().getChildren()
.stream()
.filter(InformationItem.class::isInstance)
.findFirst()
.map(i -> (InformationItem)i)
.map(InformationItem::getTypeRef) :
Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,7 @@ public void testDateAndTime() {
assertThat(ctx.get("Date-Time")).isEqualTo( ZonedDateTime.of( 2016, 12, 24, 23, 59, 0, 0, ZoneOffset.ofHours( -5 )));
assertThat(ctx.get("Date")).isEqualTo( new HashMap<String, Object>( ) {{
put( "fromString", LocalDate.of( 2015, 12, 24 ) );
put( "fromStringToDateTime", ZonedDateTime.of( 2015, 12, 24, 0, 0, 0, 0, ZoneOffset.UTC) );
put( "fromDateTime", LocalDate.of( 2016, 12, 24 ) );
put( "fromYearMonthDay", LocalDate.of( 1999, 11, 22 ) );
}});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ public void testDateAndTime() {
assertThat(ctx.get("Date-Time")).isEqualTo(ZonedDateTime.of(2016, 12, 24, 23, 59, 0, 0, ZoneOffset.ofHours(-5)));
assertThat(ctx.get("Date")).isEqualTo(new HashMap<String, Object>() {{
put("fromString", LocalDate.of(2015, 12, 24));
put( "fromStringToDateTime", ZonedDateTime.of( 2015, 12, 24, 0, 0, 0, 0, ZoneOffset.UTC) );
put("fromDateTime", LocalDate.of(2016, 12, 24));
put("fromYearMonthDay", LocalDate.of(1999, 11, 22));
}});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package org.kie.dmn.core.ast;

import java.io.File;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.stream.Collectors;

import org.drools.util.FileUtils;
import org.junit.Test;
import org.kie.dmn.api.core.DMNContext;
import org.kie.dmn.api.core.DMNModel;
import org.kie.dmn.api.core.DMNRuntime;
import org.kie.dmn.api.core.DMNType;
import org.kie.dmn.api.core.ast.DecisionNode;
import org.kie.dmn.core.api.DMNExpressionEvaluator;
import org.kie.dmn.core.api.DMNFactory;
import org.kie.dmn.core.api.EvaluatorResult;
import org.kie.dmn.core.impl.DMNDecisionResultImpl;
import org.kie.dmn.core.impl.DMNResultImpl;
import org.kie.dmn.core.impl.DMNResultImplFactory;
import org.kie.dmn.core.impl.SimpleTypeImpl;
import org.kie.dmn.core.util.DMNRuntimeUtil;
import org.kie.dmn.feel.lang.types.BuiltInType;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

public class DMNContextEvaluatorTest {

private DMNResultImplFactory dmnResultFactory = new DMNResultImplFactory();

@Test
public void optionallyConvertCollectionToArrayConverted() {
Object item = "TESTED_OBJECT";
Object value = Collections.singleton(item);
DMNType requiredType = new SimpleTypeImpl("http://www.omg.org/spec/DMN/20180521/FEEL/",
"string",
null,
false,
null,
null,
BuiltInType.STRING);
Object retrieved = DMNContextEvaluator.optionallyConvertCollectionToArray(value, requiredType);
assertNotNull(retrieved);
assertEquals(item, retrieved);
}

@Test
public void optionallyConvertCollectionToArrayNotConverted() {
Object item = "TESTED_OBJECT";
Object value = Collections.singleton(item);
DMNType requiredType = new SimpleTypeImpl("http://www.omg.org/spec/DMN/20180521/FEEL/",
"string",
null,
true,
null,
null,
BuiltInType.STRING);
Object retrieved = DMNContextEvaluator.optionallyConvertCollectionToArray(value, requiredType);
assertNotNull(retrieved);
assertEquals(value, retrieved);
value = "TESTED_OBJECT";
requiredType = new SimpleTypeImpl("http://www.omg.org/spec/DMN/20180521/FEEL/",
"string",
null,
false,
null,
null,
BuiltInType.STRING);
retrieved = DMNContextEvaluator.optionallyConvertCollectionToArray(value, requiredType);
assertNotNull(retrieved);
assertEquals(value, retrieved);
}

@Test
public void optionallyConvertDateToDateTimeConverted() {
Object value = LocalDate.now();
DMNType requiredType = new SimpleTypeImpl("http://www.omg.org/spec/DMN/20180521/FEEL/",
"date and time",
null,
false,
null,
null,
BuiltInType.DATE_TIME);
Object retrieved = DMNContextEvaluator.optionallyConvertDateToDateTime(value, requiredType);
assertNotNull(retrieved);
assertTrue(retrieved instanceof ZonedDateTime);
ZonedDateTime zdtRetrieved = (ZonedDateTime)retrieved;
assertEquals(value, zdtRetrieved.toLocalDate());
assertEquals(ZoneOffset.UTC, zdtRetrieved.getOffset());
assertEquals(0, zdtRetrieved.getHour());
assertEquals(0, zdtRetrieved.getMinute());
assertEquals(0, zdtRetrieved.getSecond());
}

@Test
public void optionallyConvertDateToDateTimeNotConverted() {
Object value = "TEST_OBJECT";
DMNType requiredType = new SimpleTypeImpl("http://www.omg.org/spec/DMN/20180521/FEEL/",
"date and time",
null,
false,
null,
null,
BuiltInType.DATE_TIME);
Object retrieved = DMNContextEvaluator.optionallyConvertDateToDateTime(value, requiredType);
assertNotNull(retrieved);
assertEquals(value, retrieved);
value = LocalDate.now();
requiredType = new SimpleTypeImpl("http://www.omg.org/spec/DMN/20180521/FEEL/",
"date",
null,
false,
null,
null,
BuiltInType.DATE);
retrieved = DMNContextEvaluator.optionallyConvertDateToDateTime(value, requiredType);
assertNotNull(retrieved);
assertEquals(value, retrieved);
}

@Test
public void dateToDateTime() {
File file = FileUtils.getFile("0007-date-time.dmn");
final DMNRuntime runtime = DMNRuntimeUtil.createRuntime(file);
final DMNModel dmnModel = runtime.getModel("http://www.trisotech.com/definitions/_69430b3e-17b8-430d-b760-c505bf6469f9", "dateTime Table 58");
assertThat(dmnModel).isNotNull();
assertThat(dmnModel.hasErrors()).as(DMNRuntimeUtil.formatMessages(dmnModel.getMessages())).isFalse();

DecisionNode date = dmnModel.getDecisionByName("Date");
DMNExpressionEvaluator dateDecisionEvaluator = ((DecisionNodeImpl) date).getEvaluator();
DMNContextEvaluator.ContextEntryDef ed = ((DMNContextEvaluator) dateDecisionEvaluator).getEntries().stream()
.filter(entry -> entry.getName().equals("fromStringToDateTime"))
.findFirst()
.orElseThrow(() -> new RuntimeException("Failed to find fromStringToDateTime ContextEntryDef"));
final DMNContext context = DMNFactory.newContext();
context.set( "dateString", "2015-12-24" );
context.set( "timeString", "00:00:01-01:00" );
context.set( "dateTimeString", "2016-12-24T23:59:00-05:00" );
context.set( "Hours", 12 );
context.set( "Minutes", 59 );
context.set( "Seconds", new BigDecimal("1.3" ) );
context.set( "Timezone", "PT-1H" );
context.set( "Year", 1999 );
context.set( "Month", 11 );
context.set( "Day", 22 );
context.set( "durationString", "P13DT2H14S" ); // <variable name="durationString" typeRef="feel:string"/>
DMNResultImpl result = createResult(dmnModel, context );
DMNExpressionEvaluator evaluator = ed.getEvaluator();
EvaluatorResult evaluated = evaluator.evaluate(runtime, result);
assertNotNull(evaluated);
assertEquals(EvaluatorResult.ResultType.SUCCESS, evaluated.getResultType());
}

private DMNResultImpl createResult(DMNModel model, DMNContext context) {
DMNResultImpl result = createResultImpl(model, context);

for (DecisionNode decision : model.getDecisions().stream().filter(d -> d.getModelNamespace().equals(model.getNamespace())).collect(Collectors.toSet())) {
result.addDecisionResult(new DMNDecisionResultImpl(decision.getId(), decision.getName()));
}
return result;
}

private DMNResultImpl createResultImpl(DMNModel model, DMNContext context) {
DMNResultImpl result = dmnResultFactory.newDMNResultImpl(model);
result.setContext(context.clone());
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.kie.dmn.core.util;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
Expand Down Expand Up @@ -72,10 +73,15 @@ public static DMNRuntime createRuntime(final String resourceName, final Class te
final KieContainer kieContainer = KieHelper.getKieContainer(
ks.newReleaseId("org.kie", "dmn-test-"+UUID.randomUUID(), "1.0"),
ks.getResources().newClassPathResource(resourceName, testClass));
return createRuntime(kieContainer);
}

final DMNRuntime runtime = typeSafeGetKieRuntime(kieContainer);
assertThat(runtime).isNotNull();
return runtime;
public static DMNRuntime createRuntime(final File resourceFile) {
final KieServices ks = KieServices.Factory.get();
final KieContainer kieContainer = KieHelper.getKieContainer(
ks.newReleaseId("org.kie", "dmn-test-"+UUID.randomUUID(), "1.0"),
ks.getResources().newFileSystemResource(resourceFile));
return createRuntime(kieContainer);
}

public static List<DMNMessage> createExpectingDMNMessages(final String resourceName, final Class testClass) {
Expand Down Expand Up @@ -192,4 +198,10 @@ public static byte[] createJarIgnoringErrors(KieServices ks, ReleaseId releaseId
byte[] jar = kieModule.getBytes();
return jar;
}

static DMNRuntime createRuntime(KieContainer kieContainer) {
final DMNRuntime runtime = typeSafeGetKieRuntime(kieContainer);
assertThat(runtime).isNotNull();
return runtime;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@
<semantic:text>date(dateString)</semantic:text>
</semantic:literalExpression>
</semantic:contextEntry>
<semantic:contextEntry>
<semantic:variable name="fromStringToDateTime" typeRef="date and time"/>
<semantic:literalExpression>
<semantic:text>date(dateString)</semantic:text>
</semantic:literalExpression>
</semantic:contextEntry>
<semantic:contextEntry>
<semantic:variable name="fromDateTime" typeRef="date"/>
<semantic:literalExpression>
Expand Down
Loading
Loading