Skip to content

Commit

Permalink
Add Filter function
Browse files Browse the repository at this point in the history
  • Loading branch information
domi-b committed Apr 4, 2024
1 parent cc6102a commit 25433c5
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

public final class EvaluationHelper {

Expand Down Expand Up @@ -62,6 +64,46 @@ public static Viewable getContextClass(TransferDescription td, IomObject iomObje
return null;
}

/**
* Get the common base class of the given {@code objects}.
*
* @param td the {@link TransferDescription} instance.
* @param objects the collection of {@link IomObject} to find the common base class of.
*
* @return the common base class of the given {@code objects} or {@code null} if no common base class could be found.
*/
public static Viewable<?> getCommonBaseClass(TransferDescription td, Collection<IomObject> objects) {
if (objects.isEmpty()) {
return null;
}

Set<String> classNames = objects.stream()
.map(IomObject::getobjecttag)
.collect(Collectors.toSet());
Viewable<?> firstClass = (Viewable<?>) td.getElement(classNames.iterator().next());
if (classNames.size() == 1) {
return firstClass;
}

return classNames.stream()
.map(className -> (Viewable) td.getElement(className))
.reduce(firstClass, EvaluationHelper::getCommonBaseClass);
}

private static Viewable<?> getCommonBaseClass(Viewable<?> classA, Viewable<?> classB) {
if (classA == null || classB == null) {
return null;
}
Viewable<?> currentClass = classA;
while (currentClass != null) {
if (currentClass == classB || classB.isExtending(currentClass)) {
return currentClass;
}
currentClass = (Viewable<?>) currentClass.getExtending();
}
return null;
}

/**
* Get the collection of {@link IomObject} inside {@code argObjects} by following the provided {@code attributePath}.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package ch.geowerkstatt.ilivalidator.extensions.functions;

import ch.interlis.ili2c.metamodel.Evaluable;
import ch.interlis.ili2c.metamodel.Viewable;
import ch.interlis.iom.IomObject;
import ch.interlis.iox_j.validator.Value;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public final class FilterIoxPlugin extends BaseInterlisFunction {
private static final HashMap<ExpressionKey, Evaluable> EXPRESSION_CACHE = new HashMap<>();

@Override
public String getQualifiedIliName() {
return "GeoW_FunctionsExt.Filter";
}

@Override
protected Value evaluateInternal(String validationKind, String usageScope, IomObject contextObject, Value[] arguments) {
Value argObjects = arguments[0];
Value argFilter = arguments[1];

if (argObjects.isUndefined() || argObjects.getComplexObjects() == null) {
return Value.createUndefined();
}

Collection<IomObject> objects = argObjects.getComplexObjects();
if (objects.isEmpty()) {
return argObjects;
}

Viewable<?> objectClass = EvaluationHelper.getCommonBaseClass(td, objects);
if (objectClass == null) {
throw new IllegalStateException("Objects have no common base class in " + usageScope);
}

ExpressionKey expressionKey = new ExpressionKey(objectClass, argFilter.getValue());
Evaluable filter = EXPRESSION_CACHE.computeIfAbsent(expressionKey, key -> parseFilterExpression(key.objectClass, key.filter, usageScope));

List<IomObject> filteredObjects = objects.stream()
.filter(object -> {
Value value = validator.evaluateExpression(null, validationKind, usageScope, object, filter, null);
return value.skipEvaluation() || value.isTrue();
})
.collect(Collectors.toList());

return new Value(filteredObjects);
}

private Evaluable parseFilterExpression(Viewable<?> objectClass, String filter, String usageScope) {
InterlisExpressionParser parser = InterlisExpressionParser.createParser(td, filter);
parser.setFilename(getQualifiedIliName() + ":" + usageScope);
return parser.parseWhereExpression(objectClass);
}

private static final class ExpressionKey {
private final Viewable<?> objectClass;
private final String filter;

private ExpressionKey(Viewable<?> objectClass, String filter) {
this.objectClass = objectClass;
this.filter = filter;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ExpressionKey)) {
return false;
}
ExpressionKey that = (ExpressionKey) o;
return Objects.equals(objectClass, that.objectClass) && Objects.equals(filter, that.filter);
}

@Override
public int hashCode() {
return Objects.hash(objectClass, filter);
}
}
}
8 changes: 8 additions & 0 deletions src/model/GeoW_FunctionsExt.ili
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,12 @@ MODEL GeoW_FunctionsExt
!!@ fn.since = "2024-01-10";
!!sample = "MANDATORY CONSTRAINT INTERLIS.elementCount(GeoW_FunctionsExt.FindObjects("ZG_Nutzungsplanung_V1_1.TransferMetadaten.Amt", "Name", "Gemeinde Walchwil")) == 1";
FUNCTION FindObjects(ObjectClass: CLASS; FilterAttr: TEXT; FilterValue: ANYSTRUCTURE): BAG OF ANYSTRUCTURE;

!!@ fn.description = "Filtert die Eingabemenge nach dem übergebenen Filterkriterium. Für 'Filter' soll eine Selection in INTERLIS 2 Syntax angegeben werden.";
!!@ fn.param = "Objects: Eingabemenge der Objekte.";
!!@ fn.param = "Filter: Filterkriterium in INTERLIS-Syntax (WHERE <logical-expression>;). THIS verweist jeweils auf das aktuelle Objekt.";
!!@ fn.return = "Alle Objekte, welche das Filterkriterium erfüllen";
!!@ fn.since = "2024-04-04";
!!sample = "MANDATORY CONSTRAINT INTERLIS.elementCount(GeoW_FunctionsExt.Filter(THIS->references, "WHERE active == #true;")) >= 1";
FUNCTION Filter(Objects: BAG OF ANYSTRUCTURE; Filter: TEXT): BAG OF ANYSTRUCTURE;
END GeoW_FunctionsExt.
8 changes: 8 additions & 0 deletions src/model/GeoW_FunctionsExt_23.ili
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,12 @@ CONTRACTED MODEL GeoW_FunctionsExt
!!@ fn.since = "2024-01-10";
!!sample = "MANDATORY CONSTRAINT INTERLIS.elementCount(GeoW_FunctionsExt.FindObjects("ZG_Nutzungsplanung_V1_1.TransferMetadaten.Amt", "Name", "Gemeinde Walchwil")) == 1";
FUNCTION FindObjects(ObjectClass: CLASS; FilterAttr: TEXT; FilterValue: ANYSTRUCTURE): BAG OF ANYSTRUCTURE;

!!@ fn.description = "Filtert die Eingabemenge nach dem übergebenen Filterkriterium. Für 'Filter' soll eine Selection in INTERLIS 2 Syntax angegeben werden.";
!!@ fn.param = "Objects: Eingabemenge der Objekte.";
!!@ fn.param = "Filter: Filterkriterium in INTERLIS-Syntax (WHERE <logical-expression>;). THIS verweist jeweils auf das aktuelle Objekt.";
!!@ fn.return = "Alle Objekte, welche das Filterkriterium erfüllen";
!!@ fn.since = "2024-04-04";
!!sample = "MANDATORY CONSTRAINT INTERLIS.elementCount(GeoW_FunctionsExt.Filter(THIS->references, "WHERE active == #true;")) >= 1";
FUNCTION Filter(Objects: BAG OF ANYSTRUCTURE; Filter: TEXT): BAG OF ANYSTRUCTURE;
END GeoW_FunctionsExt.

0 comments on commit 25433c5

Please sign in to comment.