diff --git a/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/BaseInterlisFunction.java b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/BaseInterlisFunction.java index 2368229..8f90f01 100644 --- a/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/BaseInterlisFunction.java +++ b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/BaseInterlisFunction.java @@ -16,6 +16,7 @@ public abstract class BaseInterlisFunction implements InterlisFunction { protected TransferDescription td; protected Settings settings; protected Validator validator; + protected ObjectPool objectPool; @Override public final void init(TransferDescription td, Settings settings, IoxValidationConfig validationConfig, ObjectPool objectPool, LogEventFactory logEventFactory) { @@ -24,6 +25,7 @@ public final void init(TransferDescription td, Settings settings, IoxValidationC this.td = td; this.settings = settings; this.validator = (Validator) settings.getTransientObject(IOX_VALIDATOR); + this.objectPool = objectPool; } @Override diff --git a/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/FindObjectsIoxPlugin.java b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/FindObjectsIoxPlugin.java new file mode 100644 index 0000000..6a24a93 --- /dev/null +++ b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/FindObjectsIoxPlugin.java @@ -0,0 +1,109 @@ +package ch.geowerkstatt.ilivalidator.extensions.functions; + +import ch.ehi.iox.objpool.impl.ObjPoolImpl2; +import ch.interlis.ili2c.metamodel.Element; +import ch.interlis.ili2c.metamodel.PathEl; +import ch.interlis.ili2c.metamodel.TextType; +import ch.interlis.ili2c.metamodel.Viewable; +import ch.interlis.iom.IomObject; +import ch.interlis.iox_j.validator.ObjectPoolKey; +import ch.interlis.iox_j.validator.Value; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +public final class FindObjectsIoxPlugin extends BaseInterlisFunction { + private static final Map> OBJECTS_CACHE = new HashMap<>(); + + @Override + public String getQualifiedIliName() { + return "GeoW_FunctionsExt.FindObjects"; + } + + @Override + protected Value evaluateInternal(String validationKind, String usageScope, IomObject contextObject, Value[] arguments) { + Value argClassName = arguments[0]; + Value argPath = arguments[1]; + Value argValue = arguments[2]; + + if (argClassName.isUndefined() || argPath.isUndefined()) { + return Value.createSkipEvaluation(); + } + + String className = argClassName.getValue(); + String attributePath = argPath.getValue(); + if (className == null || attributePath == null) { + return Value.createUndefined(); + } + + FindObjectsKey key = new FindObjectsKey(className, attributePath, argValue); + return new Value(OBJECTS_CACHE.computeIfAbsent(key, this::findObjects)); + } + + private List findObjects(FindObjectsKey key) { + Element classElement = td.getElement(key.className); + if (!(classElement instanceof Viewable)) { + throw new IllegalStateException("Could not find class \"" + key.className + "\""); + } + PathEl[] attributePath = EvaluationHelper.getAttributePathEl(validator, (Viewable) classElement, new Value(new TextType(), key.attributePath)); + + List objects = findObjectsOfClass(key.className); + return objects.stream() + .filter(object -> { + Value value = validator.getValueFromObjectPath(null, object, attributePath, null); + return value.compareTo(key.value) == 0; + }) + .collect(Collectors.toList()); + } + + private List findObjectsOfClass(String className) { + List objects = new ArrayList<>(); + for (String basketId : objectPool.getBasketIds()) { + ObjPoolImpl2 basketObjectPool = objectPool.getObjectsOfBasketId(basketId); + Iterator valueIterator = basketObjectPool.valueIterator(); + while (valueIterator.hasNext()) { + IomObject object = valueIterator.next(); + if (object.getobjecttag().equals(className)) { + objects.add(object); + } + } + } + return objects; + } + + private static final class FindObjectsKey { + private final String className; + private final String attributePath; + private final Value value; + + FindObjectsKey(String className, String attributePath, Value value) { + this.className = className; + this.attributePath = attributePath; + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof FindObjectsKey)) { + return false; + } + FindObjectsKey that = (FindObjectsKey) o; + return className.equals(that.className) + && attributePath.equals(that.attributePath) + && value.compareTo(that.value) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(className, attributePath, value.getValue()); + } + } +} diff --git a/src/model/GeoW_FunctionsExt.ili b/src/model/GeoW_FunctionsExt.ili index f09a34e..b08f988 100644 --- a/src/model/GeoW_FunctionsExt.ili +++ b/src/model/GeoW_FunctionsExt.ili @@ -57,4 +57,10 @@ MODEL GeoW_FunctionsExt !!@ fn.return = "TRUE, wenn sich zwei Linien überlappen oder zwischen zwei Linien eine gemeinsame Teilstrecke vorhanden ist"; !!@ fn.since = "2023-12-18"; FUNCTION PolylinesOverlap (Objects: OBJECTS OF ANYCLASS; LineAttr: TEXT): BOOLEAN; + + !!@ fn.description = "Sucht im aktuellen Transfer nach Objekten der angegebenen Klasse, welche das Filterkriterium erfüllen. Für 'FilterAttr' soll der Pfad zum Attribut in INTERLIS 2 Syntax angegeben werden. Für 'FilterValue' kann ein beliebiger Wert angegeben werden."; + !!@ fn.param = "ClassName: Qualifizierter Klassenname (inklusive Modell und Topic) der Objekte, die gesucht werden. FilterAttr: Pfad zum Attribut, welches für den Filter verwendet werden soll. FilterValue: Wert für das Filterkriterium"; + !!@ fn.return = "Alle Objekte der angegebenen Klasse aus dem aktuellen Transfer, welche das Filterkriterium erfüllen"; + !!@ fn.since = "2024-01-10"; + FUNCTION FindObjects(ClassName: TEXT; FilterAttr: TEXT; FilterValue: ANYSTRUCTURE): BAG OF ANYSTRUCTURE; END GeoW_FunctionsExt. \ No newline at end of file diff --git a/src/test/data/FindObjects/MandatoryConstraints.ili b/src/test/data/FindObjects/MandatoryConstraints.ili new file mode 100644 index 0000000..579bc21 --- /dev/null +++ b/src/test/data/FindObjects/MandatoryConstraints.ili @@ -0,0 +1,26 @@ +INTERLIS 2.4; + +MODEL TestSuite + AT "mailto:info@geowerkstatt.ch" VERSION "2022-12-02" = + IMPORTS GeoW_FunctionsExt; + + TOPIC FunctionTestTopic = + + CLASS ReferencedClass = + textAttr: TEXT*16; + enumAttr: (val1,val2,val3); + numberAttr: 0..10; + END ReferencedClass; + + CLASS BaseClass = + MANDATORY CONSTRAINT trueConstraintTextAttr: INTERLIS.elementCount(GeoW_FunctionsExt.FindObjects("TestSuite.FunctionTestTopic.ReferencedClass", "textAttr", "Some Value")) == 2; + MANDATORY CONSTRAINT trueConstraintEnumAttr: INTERLIS.elementCount(GeoW_FunctionsExt.FindObjects("TestSuite.FunctionTestTopic.ReferencedClass", "enumAttr", #val2)) == 3; + MANDATORY CONSTRAINT trueConstraintNumberAttr: INTERLIS.elementCount(GeoW_FunctionsExt.FindObjects("TestSuite.FunctionTestTopic.ReferencedClass", "numberAttr", 3)) == 1; + MANDATORY CONSTRAINT falseConstraintTextAttr: INTERLIS.elementCount(GeoW_FunctionsExt.FindObjects("TestSuite.FunctionTestTopic.ReferencedClass", "textAttr", "Some Value")) == 0; + MANDATORY CONSTRAINT falseConstraintEnumAttr: INTERLIS.elementCount(GeoW_FunctionsExt.FindObjects("TestSuite.FunctionTestTopic.ReferencedClass", "enumAttr", #val2)) == 0; + MANDATORY CONSTRAINT falseConstraintNumberAttr: INTERLIS.elementCount(GeoW_FunctionsExt.FindObjects("TestSuite.FunctionTestTopic.ReferencedClass", "numberAttr", 3)) == 0; + END BaseClass; + + END FunctionTestTopic; + +END TestSuite. diff --git a/src/test/data/FindObjects/TestData.xtf b/src/test/data/FindObjects/TestData.xtf new file mode 100644 index 0000000..03da50d --- /dev/null +++ b/src/test/data/FindObjects/TestData.xtf @@ -0,0 +1,42 @@ + + + + + GeoW_FunctionsExt + TestSuite + + ili2gpkg-4.6.1-63db90def1260a503f0f2d4cb846686cd4851184 + + + + + + + Some Value + val2 + 2 + + + aaa + val2 + 2 + + + aaa + val2 + 2 + + + Some Value + val3 + 1 + + + bbb + val1 + 3 + + + + diff --git a/src/test/java/ch/geowerkstatt/ilivalidator/extensions/functions/FindObjectsIoxPluginTest.java b/src/test/java/ch/geowerkstatt/ilivalidator/extensions/functions/FindObjectsIoxPluginTest.java new file mode 100644 index 0000000..07035ca --- /dev/null +++ b/src/test/java/ch/geowerkstatt/ilivalidator/extensions/functions/FindObjectsIoxPluginTest.java @@ -0,0 +1,30 @@ +package ch.geowerkstatt.ilivalidator.extensions.functions; + +import ch.interlis.ili2c.Ili2cFailure; +import ch.interlis.iox.IoxException; +import com.vividsolutions.jts.util.Assert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class FindObjectsIoxPluginTest { + protected static final String TEST_DATA = "FindObjects/TestData.xtf"; + private ValidationTestHelper vh = null; + + @BeforeEach + void setUp() { + vh = new ValidationTestHelper(); + vh.addFunction(new FindObjectsIoxPlugin()); + } + + @Test + void mandatoryConstraint() throws Ili2cFailure, IoxException { + vh.runValidation(new String[]{TEST_DATA}, new String[]{"FindObjects/MandatoryConstraints.ili"}); + Assert.equals(3, vh.getErrs().size()); + AssertionHelper.assertNoConstraintError(vh, "trueConstraintTextAttr"); + AssertionHelper.assertNoConstraintError(vh, "trueConstraintEnumAttr"); + AssertionHelper.assertNoConstraintError(vh, "trueConstraintNumberAttr"); + AssertionHelper.assertConstraintErrors(vh, 1, "base", "falseConstraintTextAttr"); + AssertionHelper.assertConstraintErrors(vh, 1, "base", "falseConstraintEnumAttr"); + AssertionHelper.assertConstraintErrors(vh, 1, "base", "falseConstraintNumberAttr"); + } +}