diff --git a/drools-model/drools-canonical-model/src/main/java/org/drools/model/operators/MatchesOperator.java b/drools-model/drools-canonical-model/src/main/java/org/drools/model/operators/MatchesOperator.java index 880748db1ec..6635f790ca3 100644 --- a/drools-model/drools-canonical-model/src/main/java/org/drools/model/operators/MatchesOperator.java +++ b/drools-model/drools-canonical-model/src/main/java/org/drools/model/operators/MatchesOperator.java @@ -7,7 +7,7 @@ * "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 + * 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 @@ -20,13 +20,67 @@ import org.drools.model.functions.Operator; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + public enum MatchesOperator implements Operator.SingleValue { INSTANCE; + // not final due to unit tests + private static int MAX_SIZE_CACHE = getMaxSizeCache(); + + // store Pattern for regular expressions using the regular expression as the key up to MAX_SIZE_CACHE entries. + private static final Map patternMap = Collections.synchronizedMap(new LinkedHashMap<>() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > (MAX_SIZE_CACHE); + } + }); + + // 0 default disables the Pattern map + private static int getMaxSizeCache() { + final String CACHE_MATCHES_COMPILED_MAX_PROPERTY = "drools.matches.compiled.cache.count"; + return Integer.parseInt(System.getProperty(CACHE_MATCHES_COMPILED_MAX_PROPERTY, "0")); + } + + // package-private for unit testing + void forceCacheSize(int size) { + MAX_SIZE_CACHE = size; + patternMap.clear(); + } + + // package-private for unit testing + void reInitialize() { + forceCacheSize(getMaxSizeCache()); + } + + // package-private for unit testing + int mapSize() { + return patternMap.size(); + } + @Override - public boolean eval( String s1, String s2 ) { - return s1 != null && s1.matches( s2 ); + public boolean eval(String input, String regex) { + if (input == null) { + return false; + } else if (MAX_SIZE_CACHE == 0) { + return input.matches(regex); + } else { + Pattern pattern = patternMap.get(regex); + if (pattern == null) { + // Cache miss on regex, compile it, store it. + // Storing in patternMap may remove the oldest entry per MAX_SIZE_CACHE. + pattern = Pattern.compile(regex); + patternMap.put(regex, pattern); + } + Matcher matcher = pattern.matcher(input); + return matcher.matches(); + } } @Override diff --git a/drools-model/drools-canonical-model/src/test/java/org/drools/model/operators/MatchesOperatorTest.java b/drools-model/drools-canonical-model/src/test/java/org/drools/model/operators/MatchesOperatorTest.java new file mode 100644 index 00000000000..4520828c4ea --- /dev/null +++ b/drools-model/drools-canonical-model/src/test/java/org/drools/model/operators/MatchesOperatorTest.java @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.drools.model.operators; + +import org.junit.After; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class MatchesOperatorTest { + + + @Test + public void testMatchesOperatorCache() { + MatchesOperator instance = MatchesOperator.INSTANCE; + instance.forceCacheSize(100); + + // input maybe null + assertThat(instance.eval(null,"anything")).isFalse(); + assertThat(instance.mapSize()).isEqualTo(0); // not added to cache with null input + // cache enabled + assertThat(instance.eval("a","a")).isTrue(); + assertThat(instance.mapSize()).isEqualTo(1); + assertThat(instance.eval("a","b")).isFalse(); + assertThat(instance.mapSize()).isEqualTo(2); + assertThat(instance.eval("a","a")).isTrue(); // regular expression "a" in map. + assertThat(instance.eval("b","b")).isTrue(); // regular expression "b" in map. + assertThat(instance.eval("c","a")).isFalse(); // regular expression "a" in map. + assertThat(instance.eval("c","b")).isFalse(); // regular expression "b" in map. + assertThat(instance.mapSize()).isEqualTo(2); + } + + @Test + public void testMatchesOperatorNoCache() { + MatchesOperator instance = MatchesOperator.INSTANCE; + instance.forceCacheSize(0); + // input maybe null + assertThat(instance.eval(null,"anything")).isFalse(); + assertThat(instance.eval("a","a")).isTrue(); + assertThat(instance.eval("a","b")).isFalse(); + assertThat(instance.eval("b","a")).isFalse(); + assertThat(instance.eval("b","b")).isTrue(); + assertThat(instance.mapSize()).isEqualTo(0); + } + + @After + public void resetCache() { + MatchesOperator instance = MatchesOperator.INSTANCE; + instance.reInitialize(); + } + +} diff --git a/drools-model/drools-codegen-common/pom.xml b/drools-model/drools-codegen-common/pom.xml index a0888da9179..d8d7227d22c 100644 --- a/drools-model/drools-codegen-common/pom.xml +++ b/drools-model/drools-codegen-common/pom.xml @@ -39,6 +39,10 @@ + + org.drools + drools-util + com.github.javaparser javaparser-core @@ -58,5 +62,10 @@ junit-jupiter-params test + + org.mockito + mockito-core + test + \ No newline at end of file diff --git a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/AppPaths.java b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/AppPaths.java index e12fd0c5501..a03639b7b02 100644 --- a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/AppPaths.java +++ b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/AppPaths.java @@ -26,28 +26,63 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import java.util.function.UnaryOperator; public class AppPaths { public enum BuildTool { - MAVEN, - GRADLE; + MAVEN("target", + Path.of("target","generated-sources"), + Path.of("target","generated-resources"), + Path.of("target","generated-test-resources"), + Path.of("target","classes"), + Path.of("target","test-classes")), + GRADLE("build", + Path.of("build", "generated", "sources"), + Path.of("build","generated", "resources"), + Path.of("build","generated", "test", "resources"), + Path.of("build","classes", "java", "main"), + Path.of("build","classes", "java", "test")); + + public final String OUTPUT_DIRECTORY; + public final Path GENERATED_SOURCES_PATH; + public final Path GENERATED_RESOURCES_PATH; + public final Path GENERATED_TEST_RESOURCES_PATH; + public final Path CLASSES_PATH; + public final Path TEST_CLASSES_PATH; + + BuildTool(String outputDirectory, Path generatedSourcesPath, Path generatedResourcesPath, Path generatedTestResourcesPath, Path classesPath, Path testClassesPath) { + this.OUTPUT_DIRECTORY = outputDirectory; + this.GENERATED_SOURCES_PATH = generatedSourcesPath; + this.GENERATED_RESOURCES_PATH = generatedResourcesPath; + this.GENERATED_TEST_RESOURCES_PATH = generatedTestResourcesPath; + this.CLASSES_PATH = classesPath; + this.TEST_CLASSES_PATH = testClassesPath; + } public static AppPaths.BuildTool findBuildTool() { return System.getProperty("org.gradle.appname") == null ? MAVEN : GRADLE; } } - public static final String TARGET_DIR = "target"; + public static final String TARGET_DIR; + public static final String GENERATED_SOURCES_DIR; + public static final String GENERATED_RESOURCES_DIR; + public static final BuildTool BT; + + static { + BT = BuildTool.findBuildTool(); + TARGET_DIR = BT.OUTPUT_DIRECTORY; + GENERATED_SOURCES_DIR = BT.GENERATED_SOURCES_PATH.toString(); + GENERATED_RESOURCES_DIR = BT.GENERATED_RESOURCES_PATH.toString(); + } public static final String SRC_DIR = "src"; public static final String RESOURCES_DIR = "resources"; - public static final String GENERATED_RESOURCES_DIR = "generated-resources"; - public static final String MAIN_DIR = "main"; public static final String TEST_DIR = "test"; @@ -66,8 +101,8 @@ public static AppPaths.BuildTool findBuildTool() { private final Path[] sourcePaths; - public static AppPaths fromProjectDir(Path projectDir, Path outputTarget) { - return new AppPaths(Collections.singleton(projectDir), Collections.emptyList(), false, BuildTool.findBuildTool(), MAIN_DIR, outputTarget); + public static AppPaths fromProjectDir(Path projectDir) { + return fromProjectDir(projectDir, BT); } /** @@ -77,7 +112,27 @@ public static AppPaths fromProjectDir(Path projectDir, Path outputTarget) { * @return */ public static AppPaths fromTestDir(Path projectDir) { - return new AppPaths(Collections.singleton(projectDir), Collections.emptyList(), false, BuildTool.findBuildTool(), TEST_DIR, Paths.get(projectDir.toString(), TARGET_DIR)); + return fromTestDir(projectDir, BT); + } + + /** + * Default-access method for testing purpose + * @param projectDir + * @param bt + * @return + */ + static AppPaths fromProjectDir(Path projectDir, BuildTool bt) { + return new AppPaths(Collections.singletonList(projectDir), Collections.emptyList(), false, bt, MAIN_DIR, false); + } + + /** + * Default-access method for testing purpose + * @param projectDir + * @param bt + * @return + */ + static AppPaths fromTestDir(Path projectDir, BuildTool bt) { + return new AppPaths(Collections.singletonList(projectDir), Collections.emptyList(), false, bt, TEST_DIR, true); } /** @@ -87,14 +142,14 @@ public static AppPaths fromTestDir(Path projectDir) { * @param bt * @param resourcesBasePath "main" or "test" */ - protected AppPaths(Set projectPaths, Collection classesPaths, boolean isJar, BuildTool bt, - String resourcesBasePath, Path outputTarget) { + protected AppPaths(List projectPaths, Collection classesPaths, boolean isJar, BuildTool bt, + String resourcesBasePath, boolean isTest) { this.isJar = isJar; this.projectPaths.addAll(projectPaths); this.classesPaths.addAll(classesPaths); - this.outputTarget = outputTarget; - firstProjectPath = getFirstProjectPath(this.projectPaths, outputTarget, bt); - resourcePaths = getResourcePaths(this.projectPaths, resourcesBasePath, bt); + this.outputTarget = Paths.get(".", bt.OUTPUT_DIRECTORY); + firstProjectPath = projectPaths.get(0); + resourcePaths = getResourcePaths(this.projectPaths, resourcesBasePath, bt, isTest); paths = isJar ? getJarPaths(isJar, this.classesPaths) : resourcePaths; resourceFiles = getResourceFiles(resourcePaths); sourcePaths = getSourcePaths(this.projectPaths); @@ -137,12 +192,6 @@ public String toString() { '}'; } - static Path getFirstProjectPath(Set innerProjectPaths, Path innerOutputTarget, BuildTool innerBt) { - return innerBt == BuildTool.MAVEN - ? innerProjectPaths.iterator().next() - : innerOutputTarget; - } - static Path[] getJarPaths(boolean isInnerJar, Collection innerClassesPaths) { if (!isInnerJar) { throw new IllegalStateException("Not a jar"); @@ -151,20 +200,14 @@ static Path[] getJarPaths(boolean isInnerJar, Collection innerClassesPaths } } - static Path[] getResourcePaths(Set innerProjectPaths, String resourcesBasePath, BuildTool innerBt) { - Path[] toReturn; - if (innerBt == BuildTool.GRADLE) { - toReturn = transformPaths(innerProjectPaths, p -> p.resolve(Paths.get(""))); - } else { - toReturn = transformPaths(innerProjectPaths, p -> p.resolve(Paths.get(SRC_DIR, resourcesBasePath, - RESOURCES_DIR))); - Path[] generatedResourcesPaths = transformPaths(innerProjectPaths, p -> p.resolve(Paths.get(TARGET_DIR, - GENERATED_RESOURCES_DIR))); - Path[] newToReturn = new Path[toReturn.length + generatedResourcesPaths.length]; - System.arraycopy(toReturn, 0, newToReturn, 0, toReturn.length); - System.arraycopy(generatedResourcesPaths, 0, newToReturn, toReturn.length, generatedResourcesPaths.length); - toReturn = newToReturn; - } + static Path[] getResourcePaths(Set innerProjectPaths, String resourcesBasePath, BuildTool innerBt, boolean isTest) { + Path[] resourcesPaths = transformPaths(innerProjectPaths, p -> p.resolve(Paths.get(SRC_DIR, resourcesBasePath, + RESOURCES_DIR))); + Path generatedResourcesPath = isTest ? innerBt.GENERATED_TEST_RESOURCES_PATH : innerBt.GENERATED_RESOURCES_PATH; + Path[] generatedResourcesPaths = transformPaths(innerProjectPaths, p -> p.resolve(generatedResourcesPath)); + Path[] toReturn = new Path[resourcesPaths.length + generatedResourcesPaths.length]; + System.arraycopy(resourcesPaths, 0, toReturn, 0, resourcesPaths.length); + System.arraycopy(generatedResourcesPaths, 0, toReturn, resourcesPaths.length, generatedResourcesPaths.length); return toReturn; } diff --git a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/GeneratedFileType.java b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/GeneratedFileType.java index 8818f461f8e..9af06e14145 100644 --- a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/GeneratedFileType.java +++ b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/GeneratedFileType.java @@ -57,7 +57,7 @@ enum Category { /** * Represent a cp resource automatically generated during codegen, so after generate-resources maven phase. * This means to add it to target/classes both for Quarkus or using kogito-maven-plugin (SB). For additional - * information see {@link org.kie.kogito.codegen.utils.GeneratedFileWriter#write(GeneratedFile)} + * information see {@link org.drools.codegen.common.GeneratedFileWriter#write(GeneratedFile)} * For Quarkus it will be subject of GeneratedResourceBuildItem and NativeImageResourceBuildItem too */ INTERNAL_RESOURCE, diff --git a/drools-quarkus-extension/drools-quarkus-util-deployment/src/main/java/org/drools/quarkus/util/deployment/GeneratedFileWriter.java b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/GeneratedFileWriter.java similarity index 59% rename from drools-quarkus-extension/drools-quarkus-util-deployment/src/main/java/org/drools/quarkus/util/deployment/GeneratedFileWriter.java rename to drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/GeneratedFileWriter.java index e3c62e60eab..feacd558ccf 100644 --- a/drools-quarkus-extension/drools-quarkus-util-deployment/src/main/java/org/drools/quarkus/util/deployment/GeneratedFileWriter.java +++ b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/GeneratedFileWriter.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.drools.quarkus.util.deployment; +package org.drools.codegen.common; import java.io.IOException; import java.io.UncheckedIOException; @@ -24,28 +24,62 @@ import java.nio.file.Path; import java.util.Collection; -import org.drools.codegen.common.GeneratedFile; -import org.drools.codegen.common.GeneratedFileType; +import static org.drools.util.Config.getConfig; +/** + * Writes {@link GeneratedFile} to the right directory, depending on its + * {@link GeneratedFileType.Category} + */ public class GeneratedFileWriter { - public static class Builder { + /** + * + * @param finalPath e.g. "drools" or "kogito" + * @param resourcesDirectoryProperty e.g. "drools.codegen.resources.directory" or "kogito.codegen.resources.directory" + * @param sourcesDirectoryProperty e.g. "drools.codegen.sources.directory" or "kogito.codegen.sources.directory" + * @return + */ + public static Builder builder(String finalPath, String resourcesDirectoryProperty, String sourcesDirectoryProperty ) { + return builder(finalPath, + resourcesDirectoryProperty, + sourcesDirectoryProperty, + AppPaths.BT); + } + + /** + * Default-access for testing purpose + * @param finalPath + * @param resourcesDirectoryProperty + * @param sourcesDirectoryProperty + * @param bt + * @return + */ + static Builder builder(String finalPath, String resourcesDirectoryProperty, String sourcesDirectoryProperty, AppPaths.BuildTool bt ) { + // using runtime BT instead to allow usage of + // Springboot from GRADLE + String targetClasses = bt.CLASSES_PATH.toString(); + + String generatedResourcesSourcesKogito = Path.of(bt.GENERATED_RESOURCES_PATH.toString(), finalPath).toString(); + String generatedSourcesKogito = Path.of(bt.GENERATED_SOURCES_PATH.toString(), finalPath).toString(); + return new Builder(targetClasses, + getConfig(resourcesDirectoryProperty, generatedResourcesSourcesKogito), + getConfig(sourcesDirectoryProperty, generatedSourcesKogito)); + } - private final String classesDir; - private final String sourcesDir; - private final String resourcePath; - private final String scaffoldedSourcesDir; + public static class Builder { + //Default-access for testing purpose + final String classesDir; + final String resourcePath; + final String scaffoldedSourcesDir; /** * * @param classesDir usually target/classes/ - * @param sourcesDir usually target/generated-sources/kogito/ * @param resourcesDir usually target/generated-resources/kogito/ - * @param scaffoldedSourcesDir usually src/main/java/ + * @param scaffoldedSourcesDir usually target/generated-sources/kogito/ */ - public Builder(String classesDir, String sourcesDir, String resourcesDir, String scaffoldedSourcesDir) { + private Builder(String classesDir, String resourcesDir, String scaffoldedSourcesDir) { this.classesDir = classesDir; - this.sourcesDir = sourcesDir; this.resourcePath = resourcesDir; this.scaffoldedSourcesDir = scaffoldedSourcesDir; } @@ -58,32 +92,23 @@ public Builder(String classesDir, String sourcesDir, String resourcesDir, String public GeneratedFileWriter build(Path basePath) { return new GeneratedFileWriter( basePath.resolve(classesDir), - basePath.resolve(sourcesDir), basePath.resolve(resourcePath), basePath.resolve(scaffoldedSourcesDir)); } } private final Path classesDir; - private final Path sourcesDir; private final Path resourcePath; private final Path scaffoldedSourcesDir; - - public static final String DEFAULT_SOURCES_DIR = "generated-sources/kogito/"; - public static final String DEFAULT_RESOURCE_PATH = "generated-resources/kogito/"; - public static final String DEFAULT_SCAFFOLDED_SOURCES_DIR = "src/main/java/"; - public static final String DEFAULT_CLASSES_DIR = "target/classes"; - /** * - * @param classesDir usually {@link #DEFAULT_CLASSES_DIR} - * @param sourcesDir usually target/generated-sources/kogito/. See {@link #DEFAULT_SOURCES_DIR} - * @param resourcePath usually target/generated-resources/kogito/ {@link #DEFAULT_RESOURCE_PATH} - * @param scaffoldedSourcesDir usually {@link #DEFAULT_SCAFFOLDED_SOURCES_DIR} + * @param classesDir usually target/classes/ + * @param resourcePath usually target/generated-resources/kogito/ + * @param scaffoldedSourcesDir usually target/generated-sources/kogito/ */ - public GeneratedFileWriter(Path classesDir, Path sourcesDir, Path resourcePath, Path scaffoldedSourcesDir) { + //Default-access for testing purpose + GeneratedFileWriter(Path classesDir, Path resourcePath, Path scaffoldedSourcesDir) { this.classesDir = classesDir; - this.sourcesDir = sourcesDir; this.resourcePath = resourcePath; this.scaffoldedSourcesDir = scaffoldedSourcesDir; } @@ -102,11 +127,7 @@ public void write(GeneratedFile f) throws UncheckedIOException { writeGeneratedFile(f, classesDir); break; case SOURCE: - if (f.type().isCustomizable()) { - writeGeneratedFile(f, scaffoldedSourcesDir); - } else { - writeGeneratedFile(f, sourcesDir); - } + writeGeneratedFile(f, scaffoldedSourcesDir); break; default: throw new IllegalArgumentException("Unknown Category " + category.name()); @@ -120,10 +141,6 @@ public Path getClassesDir() { return classesDir; } - public Path getSourcesDir() { - return sourcesDir; - } - public Path getResourcePath() { return resourcePath; } @@ -132,7 +149,7 @@ public Path getScaffoldedSourcesDir() { return scaffoldedSourcesDir; } - private void writeGeneratedFile(GeneratedFile f, Path location) throws IOException { + void writeGeneratedFile(GeneratedFile f, Path location) throws IOException { if (location == null) { return; } diff --git a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/context/AbstractDroolsModelBuildContext.java b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/context/AbstractDroolsModelBuildContext.java index 487f5f033a1..eb732b40ebf 100644 --- a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/context/AbstractDroolsModelBuildContext.java +++ b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/context/AbstractDroolsModelBuildContext.java @@ -153,7 +153,7 @@ protected abstract static class AbstractBuilder implements Builder { protected ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); protected Predicate classAvailabilityResolver = this::hasClass; // default fallback value (usually overridden) - protected AppPaths appPaths = AppPaths.fromProjectDir(new File(".").toPath(), Paths.get(".", AppPaths.TARGET_DIR)); + protected AppPaths appPaths = AppPaths.fromProjectDir(new File(".").toPath()); protected AbstractBuilder() { } diff --git a/drools-model/drools-codegen-common/src/test/java/org/drools/codegen/common/AppPathsTest.java b/drools-model/drools-codegen-common/src/test/java/org/drools/codegen/common/AppPathsTest.java index ed7824518b3..74cf5a75b5d 100644 --- a/drools-model/drools-codegen-common/src/test/java/org/drools/codegen/common/AppPathsTest.java +++ b/drools-model/drools-codegen-common/src/test/java/org/drools/codegen/common/AppPathsTest.java @@ -7,13 +7,11 @@ import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.EnumSource; -import static org.drools.codegen.common.AppPaths.GENERATED_RESOURCES_DIR; import static org.drools.codegen.common.AppPaths.MAIN_DIR; import static org.drools.codegen.common.AppPaths.RESOURCES_DIR; import static org.drools.codegen.common.AppPaths.SRC_DIR; -import static org.drools.codegen.common.AppPaths.TARGET_DIR; import static org.drools.codegen.common.AppPaths.TEST_DIR; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -22,21 +20,28 @@ class AppPathsTest { @ParameterizedTest - @ValueSource(booleans = {true, false}) - void fromProjectDir(boolean withGradle) { + @EnumSource(value = AppPaths.BuildTool.class, names = {"GRADLE", "MAVEN"}) + void fromProjectDir(AppPaths.BuildTool bt) { String projectDirPath = "projectDir"; - String outputTargetPath = "outputTarget"; + String outputTargetPath; + String generatedResourceDir; Path projectDir = Path.of(projectDirPath); - Path outputTarget = Path.of(outputTargetPath); - if (withGradle) { - System.setProperty("org.gradle.appname", "gradle-impl"); - } else { - System.clearProperty("org.gradle.appname"); - } - AppPaths retrieved = AppPaths.fromProjectDir(projectDir, outputTarget); + boolean withGradle; + generatedResourceDir = switch (bt) { + case GRADLE -> { + withGradle = true; + yield "generated/resources"; + } + default -> { + withGradle = false; + yield "generated-resources"; + } + }; + outputTargetPath = bt.OUTPUT_DIRECTORY; + AppPaths retrieved = AppPaths.fromProjectDir(projectDir, bt); getPathsTest(retrieved, projectDirPath, withGradle, false); - getFirstProjectPathTest(retrieved, projectDirPath, outputTargetPath, withGradle); - getResourceFilesTest(retrieved, projectDirPath, withGradle, false); + getFirstProjectPathTest(retrieved, projectDirPath); + getResourceFilesTest(retrieved, projectDirPath, outputTargetPath, generatedResourceDir, false); getResourcePathsTest(retrieved, projectDirPath, withGradle, false); getSourcePathsTest(retrieved, projectDirPath); getClassesPathsTest(retrieved); @@ -44,20 +49,28 @@ void fromProjectDir(boolean withGradle) { } @ParameterizedTest - @ValueSource(booleans = {true, false}) - void fromTestDir(boolean withGradle) { + @EnumSource(value = AppPaths.BuildTool.class, names = {"GRADLE", "MAVEN"}) + void fromTestDir(AppPaths.BuildTool bt) { String projectDirPath = "projectDir"; - String outputTargetPath = String.format("%s/%s", projectDirPath, TARGET_DIR).replace("/", File.separator); + String outputTargetPath; + String generatedResourceDir; Path projectDir = Path.of(projectDirPath); - if (withGradle) { - System.setProperty("org.gradle.appname", "gradle-impl"); - } else { - System.clearProperty("org.gradle.appname"); - } - AppPaths retrieved = AppPaths.fromTestDir(projectDir); + boolean withGradle; + generatedResourceDir = switch (bt) { + case GRADLE -> { + withGradle = true; + yield "generated/test/resources"; + } + default -> { + withGradle = false; + yield "generated-test-resources"; + } + }; + outputTargetPath = bt.OUTPUT_DIRECTORY; + AppPaths retrieved = AppPaths.fromTestDir(projectDir, bt); getPathsTest(retrieved, projectDirPath, withGradle, true); - getFirstProjectPathTest(retrieved, projectDirPath, outputTargetPath, withGradle); - getResourceFilesTest(retrieved, projectDirPath, withGradle, true); + getFirstProjectPathTest(retrieved, projectDirPath); + getResourceFilesTest(retrieved, projectDirPath, outputTargetPath, generatedResourceDir, true); getResourcePathsTest(retrieved, projectDirPath, withGradle, true); getSourcePathsTest(retrieved, projectDirPath); getClassesPathsTest(retrieved); @@ -69,33 +82,21 @@ private void getPathsTest(AppPaths toCheck, String projectDirPath, boolean isGra commonCheckResourcePaths(retrieved, projectDirPath, isGradle, isTestDir,"getPathsTest"); } - private void getFirstProjectPathTest(AppPaths toCheck, String projectPath, String outputPath, boolean isGradle) { + private void getFirstProjectPathTest(AppPaths toCheck, String expectedPath) { Path retrieved = toCheck.getFirstProjectPath(); - String expectedPath; - if (isGradle) { - expectedPath = outputPath; - } else { - expectedPath = projectPath; - } assertEquals(Path.of(expectedPath), retrieved, "AppPathsTest.getFirstProjectPathTest"); } - private void getResourceFilesTest(AppPaths toCheck, String projectDirPath, boolean isGradle, boolean isTestDir) { + private void getResourceFilesTest(AppPaths toCheck, String projectDirPath, String outputDir, String generatedResourceDir, boolean isTestDir) { File[] retrieved = toCheck.getResourceFiles(); - int expected = isGradle ? 1 : 2; + int expected = 2; assertEquals(expected, retrieved.length, "AppPathsTest.getResourceFilesTest"); String expectedPath; String sourceDir = isTestDir ? TEST_DIR : MAIN_DIR; - if (isGradle) { - expectedPath = projectDirPath; - } else { - expectedPath = String.format("%s/%s/%s/%s", projectDirPath, SRC_DIR, sourceDir, RESOURCES_DIR).replace("/", File.separator); - } + expectedPath = String.format("%s/%s/%s/%s", projectDirPath, SRC_DIR, sourceDir, RESOURCES_DIR).replace("/", File.separator); assertEquals(new File(expectedPath), retrieved[0], "AppPathsTest.getResourceFilesTest"); - if (!isGradle) { - expectedPath = String.format("%s/%s/%s", projectDirPath, TARGET_DIR, GENERATED_RESOURCES_DIR).replace("/", File.separator); - assertEquals(new File(expectedPath), retrieved[1], "AppPathsTest.getResourceFilesTest"); - } + expectedPath = String.format("%s/%s/%s", projectDirPath, outputDir, generatedResourceDir).replace("/", File.separator); + assertEquals(new File(expectedPath), retrieved[1], "AppPathsTest.getResourceFilesTest"); } private void getResourcePathsTest(AppPaths toCheck, String projectDirPath, boolean isGradle, boolean isTestDir) { @@ -117,22 +118,24 @@ private void getClassesPathsTest(AppPaths toCheck) { private void getOutputTargetTest(AppPaths toCheck, String outputPath) { Path retrieved = toCheck.getOutputTarget(); + outputPath = String.format(".%s%s", File.separator, outputPath); assertEquals(Path.of(outputPath), retrieved, "AppPathsTest.getOutputTargetTest"); } private void commonCheckResourcePaths(Path[] toCheck, String projectDirPath, boolean isGradle, boolean isTestDir, String methodName) { - int expected = isGradle ? 1 : 2; + int expected = 2; assertEquals(expected, toCheck.length, String.format("AppPathsTest.%s", methodName)); String expectedPath; String sourceDir = isTestDir ? "test" : "main"; + expectedPath = String.format("%s/src/%s/resources", projectDirPath, sourceDir).replace("/", File.separator); + assertEquals(Path.of(expectedPath), toCheck[0], String.format("AppPathsTest.%s", methodName)); if (isGradle) { - expectedPath = projectDirPath; + String toFormat = isTestDir ? "%s/build/generated/test/resources" : "%s/build/generated/resources"; + expectedPath = String.format(toFormat, projectDirPath).replace("/", File.separator); + assertEquals(Path.of(expectedPath), toCheck[1], String.format("AppPathsTest.%s", methodName)); } else { - expectedPath = String.format("%s/src/%s/resources", projectDirPath, sourceDir).replace("/", File.separator); - } - assertEquals(Path.of(expectedPath), toCheck[0], String.format("AppPathsTest.%s", methodName)); - if (!isGradle) { - expectedPath = String.format("%s/target/generated-resources", projectDirPath).replace("/", File.separator); + String toFormat = isTestDir ? "%s/target/generated-test-resources" : "%s/target/generated-resources"; + expectedPath = String.format(toFormat, projectDirPath).replace("/", File.separator); assertEquals(Path.of(expectedPath), toCheck[1], String.format("AppPathsTest.%s", methodName)); } } diff --git a/drools-model/drools-codegen-common/src/test/java/org/drools/codegen/common/GeneratedFileWriterTest.java b/drools-model/drools-codegen-common/src/test/java/org/drools/codegen/common/GeneratedFileWriterTest.java new file mode 100644 index 00000000000..6cb5b1b181d --- /dev/null +++ b/drools-model/drools-codegen-common/src/test/java/org/drools/codegen/common/GeneratedFileWriterTest.java @@ -0,0 +1,99 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.drools.codegen.common; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.stream.Stream; + +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +@Execution(SAME_THREAD) +class GeneratedFileWriterTest { + + @ParameterizedTest + @EnumSource(value = AppPaths.BuildTool.class, names = {"GRADLE", "MAVEN"}) + void builderWithConfig(AppPaths.BuildTool bt) { + String resourcesDirectoryProperty = "resource.property"; + String sourcesDirectoryProperty = "source.property"; + String finalPath = "final/destination/"; + String resourcesDirectory = "custom/resources/path"; + String sourcesDirectory = "custom/sources/path"; + System.setProperty(resourcesDirectoryProperty, resourcesDirectory); + System.setProperty(sourcesDirectoryProperty, sourcesDirectory); + GeneratedFileWriter.Builder retrieved = GeneratedFileWriter.builder(finalPath, resourcesDirectoryProperty, + sourcesDirectoryProperty, bt); + assertEquals(bt.CLASSES_PATH.toString(), retrieved.classesDir); + assertEquals(resourcesDirectory, retrieved.resourcePath); + assertEquals(sourcesDirectory, retrieved.scaffoldedSourcesDir); + System.clearProperty(resourcesDirectoryProperty); + System.clearProperty(sourcesDirectoryProperty); + } + + @ParameterizedTest + @EnumSource(value = AppPaths.BuildTool.class, names = {"GRADLE", "MAVEN"}) + void builderWithoutConfig(AppPaths.BuildTool bt) { + String resourcesDirectoryProperty = "resource.property"; + String sourcesDirectoryProperty = "source.property"; + String finalPath = "final"; + GeneratedFileWriter.Builder retrieved = GeneratedFileWriter.builder(finalPath, resourcesDirectoryProperty, + sourcesDirectoryProperty, bt); + assertEquals(bt.CLASSES_PATH.toString(), retrieved.classesDir); + String expected = String.format("%s/%s", bt.GENERATED_RESOURCES_PATH.toString(), finalPath).replace("/", + File.separator); + assertEquals(expected, retrieved.resourcePath); + expected = String.format("%s/%s", bt.GENERATED_SOURCES_PATH.toString(), finalPath).replace("/", File.separator); + assertEquals(expected, retrieved.scaffoldedSourcesDir); + } + + @ParameterizedTest + @MethodSource("btAndGeneratedFileCategoryProvider") + void write(AppPaths.BuildTool bt, GeneratedFileType.Category category) throws IOException { + String relativePath = "relative/path"; + GeneratedFile generatedFile = new GeneratedFile(GeneratedFileType.of(category), relativePath, ""); + GeneratedFileWriter spiedWriter = spy(new GeneratedFileWriter(bt.CLASSES_PATH, bt.GENERATED_RESOURCES_PATH, + bt.GENERATED_SOURCES_PATH)); + spiedWriter.write(generatedFile); + Path location = switch (category) { + case INTERNAL_RESOURCE, STATIC_HTTP_RESOURCE, COMPILED_CLASS -> bt.CLASSES_PATH; + case SOURCE -> bt.GENERATED_SOURCES_PATH; + }; + verify(spiedWriter).writeGeneratedFile(generatedFile, location); + } + + static Stream btAndGeneratedFileCategoryProvider() { + Stream.Builder argumentBuilder = Stream.builder(); + for (AppPaths.BuildTool bt : AppPaths.BuildTool.values()) { + for (GeneratedFileType.Category category : GeneratedFileType.Category.values()) { + argumentBuilder.add(Arguments.of(bt, category)); + } + } + return argumentBuilder.build(); + } +} \ No newline at end of file diff --git a/drools-quarkus-extension/drools-quarkus-deployment/src/main/java/org/drools/quarkus/deployment/DroolsAssetsProcessor.java b/drools-quarkus-extension/drools-quarkus-deployment/src/main/java/org/drools/quarkus/deployment/DroolsAssetsProcessor.java index 30d9a6dd35c..ded98cfc90e 100644 --- a/drools-quarkus-extension/drools-quarkus-deployment/src/main/java/org/drools/quarkus/deployment/DroolsAssetsProcessor.java +++ b/drools-quarkus-extension/drools-quarkus-deployment/src/main/java/org/drools/quarkus/deployment/DroolsAssetsProcessor.java @@ -32,7 +32,6 @@ import io.quarkus.deployment.builditem.LiveReloadBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; -import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; import io.quarkus.maven.dependency.ResolvedDependency; import io.quarkus.resteasy.reactive.spi.GeneratedJaxRsResourceBuildItem; import io.quarkus.vertx.http.deployment.spi.AdditionalStaticResourceBuildItem; @@ -71,8 +70,6 @@ public class DroolsAssetsProcessor { CurateOutcomeBuildItem curateOutcomeBuildItem; @Inject CombinedIndexBuildItem combinedIndexBuildItem; - @Inject - OutputTargetBuildItem outputTargetBuildItem; private static final String FEATURE = "drools"; @@ -91,7 +88,7 @@ public void generateSources( BuildProducer generatedBean BuildProducer globalsBI, BuildProducer jaxrsProducer) { DroolsModelBuildContext context = - createDroolsBuildContext(outputTargetBuildItem.getOutputDirectory(), root.getPaths(), combinedIndexBuildItem.getIndex()); + createDroolsBuildContext(root.getPaths(), combinedIndexBuildItem.getIndex()); Collection resources = ResourceCollector.fromPaths(context.getAppPaths().getPaths()); diff --git a/drools-quarkus-extension/drools-quarkus-util-deployment/src/main/java/org/drools/quarkus/util/deployment/DroolsQuarkusResourceUtils.java b/drools-quarkus-extension/drools-quarkus-util-deployment/src/main/java/org/drools/quarkus/util/deployment/DroolsQuarkusResourceUtils.java index 612dfeceb52..2f774a9d221 100644 --- a/drools-quarkus-extension/drools-quarkus-util-deployment/src/main/java/org/drools/quarkus/util/deployment/DroolsQuarkusResourceUtils.java +++ b/drools-quarkus-extension/drools-quarkus-util-deployment/src/main/java/org/drools/quarkus/util/deployment/DroolsQuarkusResourceUtils.java @@ -41,6 +41,7 @@ import org.drools.codegen.common.DroolsModelBuildContext; import org.drools.codegen.common.GeneratedFile; import org.drools.codegen.common.GeneratedFileType; +import org.drools.codegen.common.GeneratedFileWriter; import org.drools.codegen.common.context.QuarkusDroolsModelBuildContext; import org.drools.wiring.api.ComponentsSupplier; import org.jboss.jandex.ClassInfo; @@ -52,7 +53,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.drools.util.Config.getConfig; import static org.kie.memorycompiler.KieMemoryCompiler.compileNoLoad; /** @@ -79,17 +79,13 @@ private DroolsQuarkusResourceUtils() { // since quarkus-maven-plugin is later phase of maven-resources-plugin, // need to manually late-provide the resource in the expected location for quarkus:dev phase --so not: writeGeneratedFile( f, resourcePath ); - private static final GeneratedFileWriter.Builder generatedFileWriterBuilder = - new GeneratedFileWriter.Builder( - "target/classes", - getConfig("drools.codegen.sources.directory", "target/generated-sources/drools/"), - getConfig("drools.codegen.resources.directory", "target/generated-resources/drools/"), - "target/generated-sources/drools/"); - - public static DroolsModelBuildContext createDroolsBuildContext(Path outputTarget, Iterable paths, IndexView index) { + private static final GeneratedFileWriter.Builder generatedFileWriterBuilder = GeneratedFileWriter.builder("drools" + , "drools.codegen.resources.directory", "drools.codegen.sources.directory"); + + + public static DroolsModelBuildContext createDroolsBuildContext(Iterable paths, IndexView index) { // scan and parse paths - AppPaths.BuildTool buildTool = AppPaths.BuildTool.findBuildTool(); - AppPaths appPaths = QuarkusAppPaths.from(outputTarget, paths, buildTool); + AppPaths appPaths = QuarkusAppPaths.from(paths); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); DroolsModelBuildContext context = QuarkusDroolsModelBuildContext.builder() .withClassLoader(classLoader) diff --git a/drools-quarkus-extension/drools-quarkus-util-deployment/src/main/java/org/drools/quarkus/util/deployment/QuarkusAppPaths.java b/drools-quarkus-extension/drools-quarkus-util-deployment/src/main/java/org/drools/quarkus/util/deployment/QuarkusAppPaths.java index 47abd3ae151..2f76fa71dcd 100644 --- a/drools-quarkus-extension/drools-quarkus-util-deployment/src/main/java/org/drools/quarkus/util/deployment/QuarkusAppPaths.java +++ b/drools-quarkus-extension/drools-quarkus-util-deployment/src/main/java/org/drools/quarkus/util/deployment/QuarkusAppPaths.java @@ -24,11 +24,11 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; -import org.drools.codegen.common.AppPaths; - import io.quarkus.deployment.pkg.steps.JarResultBuildStep; +import org.drools.codegen.common.AppPaths; /** * {@link AppPaths}'s extension in Quarkus context. @@ -36,8 +36,6 @@ public class QuarkusAppPaths extends AppPaths { private static final Path MUTABLE_JAR_PATH = Paths.get("dev", "app"); - private static final Path CLASSES_PATH = Paths.get(TARGET_DIR, "classes"); - private static final Path TEST_CLASSES_PATH = Paths.get(TARGET_DIR, "test-classes"); private enum PathType { CLASSES, @@ -53,11 +51,11 @@ private enum PathType { UNKNOWN } - protected QuarkusAppPaths(Set projectPaths, Collection classesPaths, boolean isJar, BuildTool bt, Path outputTarget) { - super(projectPaths, classesPaths, isJar, bt, JarResultBuildStep.MAIN, outputTarget); + protected QuarkusAppPaths(List projectPaths, Collection classesPaths, boolean isJar) { + super(projectPaths, classesPaths, isJar, AppPaths.BT, JarResultBuildStep.MAIN, false); } - public static AppPaths from(Path outputTarget, Iterable paths, AppPaths.BuildTool bt) { + public static AppPaths from(Iterable paths) { final Set projectPaths = new LinkedHashSet<>(); final Collection classesPaths = new ArrayList<>(); boolean isJar = false; @@ -66,37 +64,45 @@ public static AppPaths from(Path outputTarget, Iterable paths, AppPaths.Bu switch (pathType) { case CLASSES: classesPaths.add(path); - projectPaths.add(path.getParent().getParent()); + projectPaths.add(getParentPath(path)); break; case TEST_CLASSES: - projectPaths.add(path.getParent().getParent()); + projectPaths.add(getParentPath(path)); break; case JAR: isJar = true; classesPaths.add(path); - projectPaths.add(path.getParent().getParent()); + projectPaths.add(getParentPath(path)); break; case MUTABLE_JAR: // project, class, and target are all the same. // also, we don't need any prefix (see constructor), hence passing GRADLE as the build tool - return new QuarkusAppPaths(Collections.singleton(path), Collections.singleton(path), false, BuildTool.GRADLE, path); + return new QuarkusAppPaths(Collections.singletonList(path), Collections.singleton(path), false); case UNKNOWN: classesPaths.add(path); projectPaths.add(path); break; } } - return new QuarkusAppPaths(projectPaths, classesPaths, isJar, bt, outputTarget); + return new QuarkusAppPaths(new ArrayList<>(projectPaths), classesPaths, isJar); + } + + private static Path getParentPath(Path path) { + if (AppPaths.BT.equals(BuildTool.GRADLE)) { + return path.getParent().getParent().getParent().getParent(); + } else { + return path.getParent().getParent(); + } } private static PathType getPathType(Path archiveLocation) { if (archiveLocation.endsWith(MUTABLE_JAR_PATH)) { return PathType.MUTABLE_JAR; } - if (archiveLocation.endsWith(CLASSES_PATH)) { + if (archiveLocation.endsWith(AppPaths.BT.CLASSES_PATH)) { return PathType.CLASSES; } - if (archiveLocation.endsWith(TEST_CLASSES_PATH)) { + if (archiveLocation.endsWith(AppPaths.BT.TEST_CLASSES_PATH)) { return PathType.TEST_CLASSES; } // Quarkus generates a file with extension .jar.original when doing a native compilation of a uberjar diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/interval/FEELTemporalFunctionsTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/interval/FEELTemporalFunctionsTest.java index 5c38f18c79a..e63fd109a8f 100644 --- a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/interval/FEELTemporalFunctionsTest.java +++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/interval/FEELTemporalFunctionsTest.java @@ -40,6 +40,8 @@ public static Collection data() { { "before( [1..10], [10..20] )", false, null }, { "before( [1..10), [10..20] )", true, null }, { "before( [1..10], (10..20] )", true, null }, + { "before( \"@2020-01-01\", [\"@2021-01-01\"..\"@2022-01-01\"])", true, null }, + { "before( \"@2024-01-01\", [\"@2021-01-01\"..\"@2022-01-01\"])", false, null }, { "after( 10, 5 )", true, null }, { "after( 5, 10 )", false, null }, @@ -54,17 +56,20 @@ public static Collection data() { { "after( [1..10], [11..20] )", false, null }, { "after( [11..20], [1..11) )", true, null }, { "after( (11..20], [1..11] )", true, null }, + + { "after( \"@2020-01-01\", [\"@2021-01-01\"..\"@2022-01-01\"])", false, null }, + { "after( \"@2024-01-01\", [\"@2021-01-01\"..\"@2022-01-01\"])", true, null }, { "meets( [1..5], [5..10] )", true, null }, { "meets( [1..5), [5..10] )", false, null }, { "meets( [1..5], (5..10] )", false, null }, { "meets( [1..5], [6..10] )", false, null }, - + { "met by( [5..10], [1..5] )", true, null }, { "met by( [5..10], [1..5) )", false, null }, { "met by( (5..10], [1..5] )", false, null }, { "met by( [6..10], [1..5] )", false, null }, - + { "overlaps( [1..5], [3..8] )", true, null }, { "overlaps( [3..8], [1..5] )", true, null }, { "overlaps( [1..8], [3..5] )", true, null }, @@ -79,7 +84,7 @@ public static Collection data() { { "overlaps( (5..8], [1..5] )", false, null }, { "overlaps( [5..8], [1..5) )", false, null }, { "overlaps( (5..8], [1..5) )", false, null }, - + { "overlaps before( [1..5], [3..8] )", true, null }, { "overlaps before( [1..5], [6..8] )", false, null }, { "overlaps before( [1..5], [5..8] )", true, null }, @@ -89,7 +94,7 @@ public static Collection data() { { "overlaps before( [1..5], (1..5] )", true, null }, { "overlaps before( [1..5), [1..5] )", false, null }, { "overlaps before( [1..5], [1..5] )", false, null }, - + { "overlaps before( [1..5], (1..5) )", false, null }, // additional { "overlaps before( [1..6], (1..5] )", false, null }, // additional { "overlaps before( (1..5], (1..5] )", false, null }, // additional @@ -108,7 +113,7 @@ public static Collection data() { { "overlaps after( (1..5], [1..6] )", false, null }, // additional { "overlaps after( (1..5], (1..5] )", false, null }, // additional { "overlaps after( (1..5], [2..5] )", false, null }, // additional - + { "finishes( 10, [1..10] )", true, null }, { "finishes( 10, [1..10) ) ", false, null }, { "finishes( [5..10], [1..10] )", true, null }, @@ -116,11 +121,11 @@ public static Collection data() { { "finishes( [5..10), [1..10) )", true, null }, { "finishes( [1..10], [1..10] )", true, null }, { "finishes( (1..10], [1..10] )", true, null }, - + { "finishes( [5..11], [1..10] )", false, null }, // additional { "finishes( [0..10], [1..10] )", false, null }, // additional { "finishes( [1..10], (1..10] )", false, null }, // additional - + { "finished by( [1..10], 10 )", true, null }, { "finished by( [1..10), 10 ) ", false, null }, { "finished by( [1..10], [5..10] ) ", true, null }, @@ -128,12 +133,12 @@ public static Collection data() { { "finished by( [1..10), [5..10) ) ", true, null }, { "finished by( [1..10], [1..10] ) ", true, null }, { "finished by( [1..10], (1..10] ) ", true, null }, - + { "finished by( [1..10], [5..11] )", false, null }, // additional { "finished by( [1..10], [0..10] )", false, null }, // additional { "finished by( (1..10], [1..10] )", false, null }, // additional { "finished by( (1..10], (1..10] )", true, null }, // additional - + { "includes( [1..10], 5 )", true, null }, { "includes( [1..10], 12 )", false, null }, { "includes( [1..10], 1 )", true, null }, @@ -148,12 +153,12 @@ public static Collection data() { { "includes( [1..10], [1..10) )", true, null }, { "includes( [1..10], (1..10] )", true, null }, { "includes( [1..10], [1..10] )", true, null }, - + { "includes( [4..6], [1..10] )", false, null }, // additional { "includes( (1..5], [1..5] )", false, null }, // additional { "includes( [1..5), [1..5] )", false, null }, // additional { "includes( [1..4], [1..5] )", false, null }, // additional - + { "during( 5, [1..10] )", true, null }, { "during( 12, [1..10] )", false, null }, { "during( 1, [1..10] )", true, null }, @@ -168,12 +173,12 @@ public static Collection data() { { "during( [1..10), [1..10] )", true, null }, { "during( (1..10], [1..10] )", true, null }, { "during( [1..10], [1..10] )", true, null }, - + { "during( [1..10], [4..6] )", false, null }, // additional { "during( [1..5] , (1..5])", false, null }, // additional { "during( [1..5] , [1..5))", false, null }, // additional { "during( [1..5] , [1..4])", false, null }, // additional - + { "starts( 1, [1..10] )", true, null }, { "starts( 1, (1..10] )", false, null }, { "starts( 2, [1..10] )", false, null }, @@ -184,11 +189,11 @@ public static Collection data() { { "starts( [1..10], [1..10] )", true, null }, { "starts( [1..10), [1..10] )", true, null }, { "starts( (1..10), (1..10) )", true, null }, - + { "starts( [1..9], [1..5] )", false, null }, // additional { "starts( [1..5], [1..5) )", false, null }, // additional { "starts( [2..9], [1..5] )", false, null }, // additional - + { "started by( [1..10], 1 )", true, null }, { "started by( (1..10], 1 )", false, null }, { "started by( [1..10], 2 )", false, null }, @@ -199,11 +204,11 @@ public static Collection data() { { "started by( [1..10], [1..10] )", true, null }, { "started by( [1..10], [1..10) )", true, null }, { "started by( (1..10), (1..10) )", true, null }, - + { "started by( [1..5], [1..9] )", false, null }, // additional { "started by( [1..5), [1..5] )", false, null }, // additional { "started by( [1..5], [2..9] )", false, null }, // additional - + { "coincides( 5, 5 )", true, null }, { "coincides( 3, 4 )", false, null }, { "coincides( [1..5], [1..5] )", true, null }, diff --git a/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/DMNTypeSchemas.java b/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/DMNTypeSchemas.java index 94706266053..92a04692a81 100644 --- a/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/DMNTypeSchemas.java +++ b/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/DMNTypeSchemas.java @@ -18,8 +18,10 @@ */ package org.kie.dmn.openapi.impl; +import java.time.LocalDate; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -72,8 +74,7 @@ private Schema refOrBuiltinSchema(DMNType t) { return FEELBuiltinTypeSchemas.from(t); } if (typesIndex.contains(t)) { - Schema schema = OASFactory.createObject(Schema.class).ref(namingPolicy.getRef(t)); - return schema; + return OASFactory.createObject(Schema.class).ref(namingPolicy.getRef(t)); } throw new UnsupportedOperationException(); } @@ -131,7 +132,7 @@ private Schema schemaFromSimpleType(SimpleTypeImpl t) { parseSimpleType(DMNOASConstants.X_DMN_ALLOWED_VALUES, t, schema, t.getAllowedValuesFEEL(), t.getAllowedValues()); } if (t.getTypeConstraint() != null && !t.getTypeConstraint().isEmpty()) { - parseSimpleType(DMNOASConstants.X_DMN_TYPE_CONSTRAINTS, t, schema, t.getTypeConstraintFEEL(), t.getAllowedValues()); + parseSimpleType(DMNOASConstants.X_DMN_TYPE_CONSTRAINTS, t, schema, t.getTypeConstraintFEEL(), t.getTypeConstraint()); } schema = nestAsItemIfCollection(schema, t); schema.addExtension(X_DMN_TYPE, getDMNTypeSchemaXDMNTYPEdescr(t)); @@ -142,7 +143,9 @@ private Schema schemaFromSimpleType(SimpleTypeImpl t) { private void parseSimpleType(String schemaString, SimpleTypeImpl t, Schema schema, List feelUnaryTests, List dmnUnaryTests) { schema.addExtension(schemaString, feelUnaryTests.stream().map(UnaryTest::toString).collect(Collectors.joining(", "))); if (DMNTypeUtils.getFEELBuiltInType(ancestor(t)) == BuiltInType.NUMBER) { - FEELSchemaEnum.parseNumbersIntoSchema(schema, dmnUnaryTests); + FEELSchemaEnum.parseRangeableValuesIntoSchema(schema, dmnUnaryTests, Number.class); + } else if (DMNTypeUtils.getFEELBuiltInType(ancestor(t)) == BuiltInType.DATE) { + FEELSchemaEnum.parseRangeableValuesIntoSchema(schema, dmnUnaryTests, LocalDate.class); } else { FEELSchemaEnum.parseValuesIntoSchema(schema, dmnUnaryTests); } @@ -154,7 +157,7 @@ private Schema schemaFromCompositeType(CompositeTypeImpl ct) { for (Entry fkv : ct.getFields().entrySet()) { schema.addProperty(fkv.getKey(), refOrBuiltinSchema(fkv.getValue())); } - if (isIOSetForInputScope(ct) && ct.getFields().size() > 0) { + if (isIOSetForInputScope(ct) && !ct.getFields().isEmpty()) { schema.required(new ArrayList<>(ct.getFields().keySet())); } } else { diff --git a/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/FEELSchemaEnum.java b/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/FEELSchemaEnum.java index 30e2cb58138..ccdb377508f 100644 --- a/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/FEELSchemaEnum.java +++ b/kie-dmn/kie-dmn-openapi/src/main/java/org/kie/dmn/openapi/impl/FEELSchemaEnum.java @@ -18,10 +18,6 @@ */ package org.kie.dmn.openapi.impl; -import java.math.BigDecimal; -import java.util.List; -import java.util.stream.Collectors; - import org.eclipse.microprofile.openapi.models.media.Schema; import org.kie.dmn.api.core.DMNUnaryTest; import org.kie.dmn.feel.FEEL; @@ -32,24 +28,35 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.math.BigDecimal; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + public class FEELSchemaEnum { private static final Logger LOG = LoggerFactory.getLogger(FEELSchemaEnum.class); - public static void parseValuesIntoSchema(Schema schema, List list) { - List expectLiterals = evaluateUnaryTests(list); + public static void parseValuesIntoSchema(Schema schema, List unaryTests) { + List expectLiterals = evaluateUnaryTests(unaryTests); + try { + checkEvaluatedUnaryTestsForTypeConsistency(expectLiterals); + } catch (IllegalArgumentException e) { + LOG.warn("Unable to parse generic value into the JSON Schema for enumeration"); + return; + } if (expectLiterals.contains(null)) { schema.setNullable(true); } - boolean allLiterals = expectLiterals.stream().allMatch(o -> o == null || o instanceof String || o instanceof Number || o instanceof Boolean); + boolean allLiterals = !expectLiterals.isEmpty() && expectLiterals.stream().allMatch(o -> o == null || o instanceof String || o instanceof Number || o instanceof Boolean); if (allLiterals) { schema.enumeration(expectLiterals); } else { LOG.warn("Unable to parse generic value into the JSON Schema for enumeration"); } } - - public static void parseNumbersIntoSchema(Schema schema, List list) { + + public static void parseRangeableValuesIntoSchema(Schema schema, List list, Class expectedType) { List uts = evaluateUnaryTests(list); // we leverage the property of the *base* FEEL grammar(non visited by ASTVisitor, only the ParseTree->AST Visitor) that `>x` is a Range boolean allowNull = uts.remove(null); if (allowNull) { @@ -67,13 +74,13 @@ public static void parseNumbersIntoSchema(Schema schema, List list schema.exclusiveMaximum(range.getHighBoundary() == RangeBoundary.OPEN); } } - } else if (uts.stream().allMatch(o -> o instanceof Number)) { + } else if (uts.stream().allMatch(expectedType::isInstance)) { if (allowNull) { uts.add(null); } schema.enumeration(uts); } else { - LOG.warn("Unable to parse number value into the JSON Schema for enumeration"); + LOG.warn("Unable to parse {} value into the JSON Schema for enumeration", expectedType); } } @@ -99,13 +106,43 @@ public static Range consolidateRanges(List ranges) { return consistent ? result : null; } - private static List evaluateUnaryTests(List list) { - FEEL SimpleFEEL = FEEL.newInstance(); - List utEvaluated = list.stream().map(UnaryTestImpl.class::cast) - .map(UnaryTestImpl::toString) - .map(SimpleFEEL::evaluate) - .collect(Collectors.toList()); - return utEvaluated; + static List evaluateUnaryTests(List list) { + FEEL feelInstance = FEEL.newInstance(); + List toReturn = list.stream() + .map(UnaryTestImpl.class::cast) + .map(UnaryTestImpl::toString) + .filter(str -> !str.contains("?")) // We have to exclude "TEST" expressions, because they can't be evaluated without a context and their return is meaningless + .map(feelInstance::evaluate) + .collect(Collectors.toList()); + checkEvaluatedUnaryTestsForNull(toReturn); + return toReturn; + } + + /** + * Method used to verify if the given List contains at most one null, + * since those should be put in the "enum" attribute + * + * @param toCheck + */ + static void checkEvaluatedUnaryTestsForNull(List toCheck) { + if (toCheck.stream().filter(Objects::isNull).toList().size() > 1) { + throw new IllegalArgumentException("More then one object is null, only one allowed at maximum"); + } + } + + /** + * Method used to verify if the given List contains the same type of Objects, + * since those should be put in the "enum" attribute + * + * @param toCheck + */ + static void checkEvaluatedUnaryTestsForTypeConsistency(List toCheck) { + if (toCheck.stream().filter(Objects::nonNull) + .map(Object::getClass) + .collect(Collectors.toUnmodifiableSet()) + .size() > 1) { + throw new IllegalArgumentException("Different types of objects, only one allowed"); + } } private FEELSchemaEnum() { diff --git a/kie-dmn/kie-dmn-openapi/src/test/java/org/kie/dmn/openapi/FEELSchemaEnumTest.java b/kie-dmn/kie-dmn-openapi/src/test/java/org/kie/dmn/openapi/FEELSchemaEnumTest.java deleted file mode 100644 index 6cfc24287b1..00000000000 --- a/kie-dmn/kie-dmn-openapi/src/test/java/org/kie/dmn/openapi/FEELSchemaEnumTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.kie.dmn.openapi; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.Test; -import org.kie.dmn.feel.runtime.Range; -import org.kie.dmn.feel.runtime.Range.RangeBoundary; -import org.kie.dmn.feel.runtime.impl.RangeImpl; -import org.kie.dmn.openapi.impl.FEELSchemaEnum; - -import static org.assertj.core.api.Assertions.assertThat; - -public class FEELSchemaEnumTest extends BaseDMNOASTest { - - @Test - public void testBasic() { - List list = new ArrayList<>(); - list.add(new RangeImpl(RangeBoundary.CLOSED, 0, null, RangeBoundary.CLOSED)); - list.add(new RangeImpl(RangeBoundary.CLOSED, null, 100, RangeBoundary.CLOSED)); - Range result = FEELSchemaEnum.consolidateRanges(list); - assertThat(result).isNotNull().isEqualTo(new RangeImpl(RangeBoundary.CLOSED, 0, 100, RangeBoundary.CLOSED)); - } - - @Test - public void testInvalidRepeatedLB() { - List list = new ArrayList<>(); - list.add(new RangeImpl(RangeBoundary.CLOSED, 0, null, RangeBoundary.CLOSED)); - list.add(new RangeImpl(RangeBoundary.CLOSED, 0, 100, RangeBoundary.CLOSED)); - Range result = FEELSchemaEnum.consolidateRanges(list); - assertThat(result).isNull(); - } - - @Test - public void testInvalidRepeatedUB() { - List list = new ArrayList<>(); - list.add(new RangeImpl(RangeBoundary.CLOSED, null, 50, RangeBoundary.CLOSED)); - list.add(new RangeImpl(RangeBoundary.CLOSED, null, 100, RangeBoundary.CLOSED)); - Range result = FEELSchemaEnum.consolidateRanges(list); - assertThat(result).isNull(); - } -} diff --git a/kie-dmn/kie-dmn-openapi/src/test/java/org/kie/dmn/openapi/impl/FEELSchemaEnumTest.java b/kie-dmn/kie-dmn-openapi/src/test/java/org/kie/dmn/openapi/impl/FEELSchemaEnumTest.java new file mode 100644 index 00000000000..70704d44524 --- /dev/null +++ b/kie-dmn/kie-dmn-openapi/src/test/java/org/kie/dmn/openapi/impl/FEELSchemaEnumTest.java @@ -0,0 +1,206 @@ +package org.kie.dmn.openapi.impl; + + +import org.eclipse.microprofile.openapi.OASFactory; +import org.eclipse.microprofile.openapi.models.media.Schema; +import org.junit.Test; +import org.kie.dmn.api.core.DMNUnaryTest; +import org.kie.dmn.feel.FEEL; +import org.kie.dmn.feel.runtime.Range; +import org.kie.dmn.feel.runtime.impl.RangeImpl; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +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.assertNull; +import static org.junit.Assert.assertTrue; +import static org.kie.dmn.openapi.impl.FEELSchemaEnum.checkEvaluatedUnaryTestsForNull; +import static org.kie.dmn.openapi.impl.FEELSchemaEnum.checkEvaluatedUnaryTestsForTypeConsistency; +import static org.kie.dmn.openapi.impl.FEELSchemaEnum.evaluateUnaryTests; +import static org.kie.dmn.openapi.impl.FEELSchemaEnum.parseRangeableValuesIntoSchema; +import static org.kie.dmn.openapi.impl.FEELSchemaEnum.parseValuesIntoSchema; + +public class FEELSchemaEnumTest { + + private static final FEEL feel = FEEL.newInstance(); + + @Test + public void testParseValuesIntoSchemaWithoutNull() { + Schema toPopulate = OASFactory.createObject(Schema.class); + List expectedStrings = Arrays.asList("ONE", "TWO"); + List toEnum = expectedStrings.stream().map(toMap -> String.format("\"%s\"", toMap)).collect(Collectors.toUnmodifiableList()); + String expression = String.join(",", toEnum.stream().map(toMap -> String.format("%s", toMap.toString())).toList()); + expression += ", count (?) > 1"; + List unaryTests = feel.evaluateUnaryTests(expression).stream().map(DMNUnaryTest.class::cast).toList(); + parseValuesIntoSchema(toPopulate, unaryTests); + assertNull(toPopulate.getNullable()); + assertNotNull(toPopulate.getEnumeration()); + assertEquals(expectedStrings.size(), toPopulate.getEnumeration().size()); + expectedStrings.forEach(expectedString -> assertTrue(toPopulate.getEnumeration().contains(expectedString))); + } + + @Test + public void testParseValuesIntoSchemaWithNull() { + Schema toPopulate = OASFactory.createObject(Schema.class); + List expectedStrings = Arrays.asList(null, "ONE", "TWO"); + List toEnum = expectedStrings.stream().map(toFormat -> toFormat == null ? "null": String.format("\"%s\"", toFormat)).collect(Collectors.toUnmodifiableList()); + String expression = String.join(",", toEnum.stream().map(toMap -> String.format("%s", toMap.toString())).toList()); + List unaryTests = feel.evaluateUnaryTests(expression).stream().map(DMNUnaryTest.class::cast).toList(); + parseValuesIntoSchema(toPopulate, unaryTests); + assertTrue(toPopulate.getNullable()); + assertNotNull(toPopulate.getEnumeration()); + assertEquals(expectedStrings.size(), toPopulate.getEnumeration().size()); + expectedStrings.stream().filter(Objects::nonNull).forEach(expectedString -> assertTrue(toPopulate.getEnumeration().contains(expectedString))); + } + + @Test + public void testParseRangeableValuesIntoSchemaNumberWithoutNull() { + Schema toPopulate = OASFactory.createObject(Schema.class); + List expectedNumbers = Arrays.asList(1, 2); + String expression = String.join(",", expectedNumbers.stream().map(toMap -> String.format("%s", toMap)).toList()); + List unaryTests = feel.evaluateUnaryTests(expression).stream().map(DMNUnaryTest.class::cast).toList(); + parseRangeableValuesIntoSchema(toPopulate, unaryTests, Number.class); + assertNull(toPopulate.getNullable()); + assertNotNull(toPopulate.getEnumeration()); + assertEquals(expectedNumbers.size(), toPopulate.getEnumeration().size()); + List expected = Arrays.asList(BigDecimal.valueOf(1), BigDecimal.valueOf(2)); + expected.forEach(expectedNumber -> assertTrue(toPopulate.getEnumeration().contains(expectedNumber))); + } + + @Test + public void testParseRangeableValuesNumberIntoSchemaWithNull() { + Schema toPopulate = OASFactory.createObject(Schema.class); + List expectedNumbers = Arrays.asList(null, 1, 2); + String expression = String.join(",", expectedNumbers.stream().map(toMap -> String.format("%s", toMap)).toList()); + List unaryTests = feel.evaluateUnaryTests(expression).stream().map(DMNUnaryTest.class::cast).toList(); + parseRangeableValuesIntoSchema(toPopulate, unaryTests, Number.class); + assertTrue(toPopulate.getNullable()); + assertNotNull(toPopulate.getEnumeration()); + assertEquals(expectedNumbers.size(), toPopulate.getEnumeration().size()); + List expected = Arrays.asList(null, BigDecimal.valueOf(1), BigDecimal.valueOf(2)); + expected.stream().forEach(expectedNumber -> assertTrue(toPopulate.getEnumeration().contains(expectedNumber))); + } + + @Test + public void testParseRangeableValuesIntoSchemaLocalDateWithoutNull() { + Schema toPopulate = OASFactory.createObject(Schema.class); + List expectedDates = Arrays.asList(LocalDate.of(2022, 1, 1), LocalDate.of(2024, 1, 1)); + List formattedDates = expectedDates.stream() + .map(toFormat -> String.format("@\"%s-0%s-0%s\"", toFormat.getYear(), toFormat.getMonthValue(), toFormat.getDayOfMonth())) + .toList(); + String expression = String.join(",", formattedDates.stream().map(toMap -> String.format("%s", toMap)).toList()); + List unaryTests = feel.evaluateUnaryTests(expression).stream().map(DMNUnaryTest.class::cast).toList(); + parseRangeableValuesIntoSchema(toPopulate, unaryTests, LocalDate.class); + assertNull(toPopulate.getNullable()); + assertNotNull(toPopulate.getEnumeration()); + assertEquals(expectedDates.size(), toPopulate.getEnumeration().size()); + expectedDates.forEach(expectedDate -> assertTrue(toPopulate.getEnumeration().contains(expectedDate))); + } + + @Test + public void testParseRangeableValuesDateIntoSchemaWithNull() { + Schema toPopulate = OASFactory.createObject(Schema.class); + List expectedDates = Arrays.asList(null, LocalDate.of(2022, 1, 1), LocalDate.of(2024, 1, 1)); + List formattedDates = expectedDates.stream() + .map(toFormat -> toFormat == null ? "null" : String.format("@\"%s-0%s-0%s\"", toFormat.getYear(), toFormat.getMonthValue(), toFormat.getDayOfMonth())) + .toList(); + String expression = String.join(",", formattedDates.stream().map(toMap -> String.format("%s", toMap)).toList()); + List unaryTests = feel.evaluateUnaryTests(expression).stream().map(DMNUnaryTest.class::cast).toList(); + parseRangeableValuesIntoSchema(toPopulate, unaryTests, LocalDate.class); + assertTrue(toPopulate.getNullable()); + assertNotNull(toPopulate.getEnumeration()); + assertEquals(expectedDates.size(), toPopulate.getEnumeration().size()); + expectedDates.forEach(expectedDate -> assertTrue(toPopulate.getEnumeration().contains(expectedDate))); + } + + @Test + public void testConsolidateRangesSucceed() { + Range lowRange = new RangeImpl(Range.RangeBoundary.CLOSED, 0, null, Range.RangeBoundary.CLOSED); + Range highRange = new RangeImpl(Range.RangeBoundary.CLOSED, null, 100, Range.RangeBoundary.CLOSED); + List list = Arrays.asList(lowRange, highRange); + Range result = FEELSchemaEnum.consolidateRanges(list); + assertThat(result).isNotNull().isEqualTo(new RangeImpl(lowRange.getLowBoundary(), lowRange.getLowEndPoint(), highRange.getHighEndPoint(), highRange.getHighBoundary())); + // + lowRange = new RangeImpl(Range.RangeBoundary.CLOSED, LocalDate.of(2022, 1, 1), null, Range.RangeBoundary.CLOSED); + highRange = new RangeImpl(Range.RangeBoundary.CLOSED, null, LocalDate.of(2024, 1, 1), Range.RangeBoundary.CLOSED); + list = Arrays.asList(lowRange, highRange); + result = FEELSchemaEnum.consolidateRanges(list); + assertThat(result).isNotNull().isEqualTo(new RangeImpl(lowRange.getLowBoundary(), lowRange.getLowEndPoint(), highRange.getHighEndPoint(), highRange.getHighBoundary())); + } + + @Test + public void testConsolidateRangesInvalidRepeatedLB() { + List list = new ArrayList<>(); + list.add(new RangeImpl(Range.RangeBoundary.CLOSED, 0, null, Range.RangeBoundary.CLOSED)); + list.add(new RangeImpl(Range.RangeBoundary.CLOSED, 0, 100, Range.RangeBoundary.CLOSED)); + Range result = FEELSchemaEnum.consolidateRanges(list); + assertThat(result).isNull(); + } + + @Test + public void testConsolidateRangesInvalidRepeatedUB() { + List list = new ArrayList<>(); + list.add(new RangeImpl(Range.RangeBoundary.CLOSED, null, 50, Range.RangeBoundary.CLOSED)); + list.add(new RangeImpl(Range.RangeBoundary.CLOSED, null, 100, Range.RangeBoundary.CLOSED)); + Range result = FEELSchemaEnum.consolidateRanges(list); + assertThat(result).isNull(); + } + + @Test + public void testEvaluateUnaryTestsSucceed() { + List toEnum = Arrays.asList("\"a string\"", 3, "@\"2024-01-01\""); + String expression = String.join(",", toEnum.stream().map(toMap -> String.format("%s", toMap.toString())).toList()); + expression += ", count (?) > 1"; + List toCheck = feel.evaluateUnaryTests(expression).stream().map(DMNUnaryTest.class::cast).toList(); + assertEquals(toEnum.size() + 1, toCheck.size()); + List retrieved = evaluateUnaryTests(toCheck); + List expected = Arrays.asList("a string", BigDecimal.valueOf(3), LocalDate.of(2024, 1, 1)); + assertEquals(expected.size(), retrieved.size()); + expected.forEach(expectedEntry -> assertTrue("Failing asserts for " + expectedEntry, retrieved.stream().anyMatch(ret -> Objects.equals(expectedEntry, ret)))); + } + + @Test(expected = IllegalArgumentException.class) + public void testEvaluateUnaryTestsFails() { + List toEnum = Arrays.asList(null, null, "@\"2024-01-01\""); + String expression = String.join(",", toEnum.stream().map(toMap -> String.format("%s", toMap)).toList()); + List toCheck = feel.evaluateUnaryTests(expression).stream().map(DMNUnaryTest.class::cast).toList(); + assertEquals(toEnum.size(), toCheck.size()); + evaluateUnaryTests(toCheck); + } + + @Test + public void testCheckEvaluatedUnaryTestsForNullSucceed() { + List toCheck = new ArrayList<>(Arrays.asList("1", "2", "3")); + checkEvaluatedUnaryTestsForNull(toCheck); + toCheck.add(null); + checkEvaluatedUnaryTestsForNull(toCheck); + } + + @Test(expected = IllegalArgumentException.class) + public void testCheckEvaluatedUnaryTestsForNullFails() { + List toCheck = new ArrayList<>(Arrays.asList("1", "2", "3")); + toCheck.add(null); + toCheck.add(null); + checkEvaluatedUnaryTestsForNull(toCheck); + } + + @Test + public void testCheckEvaluatedUnaryTestsForTypeConsistencySucceed() { + List toCheck = Arrays.asList("1", "2", "3"); + checkEvaluatedUnaryTestsForTypeConsistency(toCheck); + } + + @Test(expected = IllegalArgumentException.class) + public void testCheckEvaluatedUnaryTestsForTypeConsistencyFails() { + List toCheck = Arrays.asList("1", "2", 3); + checkEvaluatedUnaryTestsForTypeConsistency(toCheck); + } +} \ No newline at end of file