All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.n52.svalbard.odata.ODataFesParser Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2015-2022 52°North Spatial Information Research GmbH
 *
 * Licensed 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.n52.svalbard.odata;

import com.google.common.escape.Escaper;
import com.google.common.net.PercentEscaper;
import org.apache.olingo.commons.api.edm.Edm;
import org.apache.olingo.commons.api.edm.EdmEnumType;
import org.apache.olingo.commons.api.edm.EdmType;
import org.apache.olingo.commons.api.ex.ODataException;
import org.apache.olingo.commons.core.edm.EdmProviderImpl;
import org.apache.olingo.server.api.ODataApplicationException;
import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.api.uri.UriInfoResource;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind;
import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitException;
import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitor;
import org.apache.olingo.server.api.uri.queryoption.expression.Literal;
import org.apache.olingo.server.api.uri.queryoption.expression.Member;
import org.apache.olingo.server.api.uri.queryoption.expression.MethodKind;
import org.apache.olingo.server.api.uri.queryoption.expression.UnaryOperatorKind;
import org.apache.olingo.server.core.ODataImpl;
import org.apache.olingo.server.core.uri.parser.Parser;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.PrecisionModel;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.n52.shetland.ogc.filter.BinaryLogicFilter;
import org.n52.shetland.ogc.filter.ComparisonFilter;
import org.n52.shetland.ogc.filter.Filter;
import org.n52.shetland.ogc.filter.FilterConstants.BinaryLogicOperator;
import org.n52.shetland.ogc.filter.FilterConstants.ComparisonOperator;
import org.n52.shetland.ogc.filter.FilterConstants.SpatialOperator;
import org.n52.shetland.ogc.filter.FilterConstants.UnaryLogicOperator;
import org.n52.shetland.ogc.filter.SpatialFilter;
import org.n52.shetland.ogc.filter.UnaryLogicFilter;
import org.n52.svalbard.decode.Decoder;
import org.n52.svalbard.decode.DecoderKey;
import org.n52.svalbard.decode.exception.DecodingException;
import org.n52.svalbard.odata.core.expr.BinaryExpr;
import org.n52.svalbard.odata.core.expr.Expr;
import org.n52.svalbard.odata.core.expr.ExprVisitor;
import org.n52.svalbard.odata.core.expr.GeoValueExpr;
import org.n52.svalbard.odata.core.expr.MemberExpr;
import org.n52.svalbard.odata.core.expr.MethodCallExpr;
import org.n52.svalbard.odata.core.expr.StringValueExpr;
import org.n52.svalbard.odata.core.expr.TextExpr;
import org.n52.svalbard.odata.core.expr.UnaryExpr;
import org.n52.svalbard.odata.core.expr.arithmetic.NumericValueExpr;
import org.n52.svalbard.odata.core.expr.arithmetic.SimpleArithmeticExpr;
import org.n52.svalbard.odata.core.expr.bool.BooleanBinaryExpr;
import org.n52.svalbard.odata.core.expr.bool.BooleanExpr;
import org.n52.svalbard.odata.core.expr.bool.BooleanUnaryExpr;
import org.n52.svalbard.odata.core.expr.bool.ComparisonExpr;
import org.n52.svalbard.odata.core.expr.temporal.TimeValueExpr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.CheckReturnValue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * Class to parse OData-based {@code $filter} expression into FES filters. See {@link ObservationCsdlEdmProvider} for
 * the available properties, their types and the resulting value references.
 *
 * @author Christian Autermann
 * @see ObservationCsdlEdmProvider
 */
public class ODataFesParser
        implements
        Decoder, String> {
    private static final String METHOD_CONTAINS = "contains";
    private static final String METHOD_STARTS_WITH = "startswith";
    private static final String METHOD_ENDS_WITH = "endswith";
    private static final String METHOD_GEO_INTERSECTS = "geo.intersects";
    private static final Logger LOG = LoggerFactory.getLogger(ODataFesParser.class);
    private static final String PATH = "/ObservationCollection";
    private static final String FRAGMENT = "";
    private static final String BASE_URI = "/";
    private static final String GEOGRAPHY_TYPE = "geography";
    private static final String SRID_PREFIX = "SRID=";
    private static final String GEOMETRY_TYPE = "geometry";
    private static final String FEATURE_EQUALS = "featureOfInterest eq '";
    private final Escaper urlEscaper;
    private final Edm edm;
    private final Parser parser;
    private final ObservationCsdlEdmProvider csdlProvider;
    private final ODataImpl odata;

    /**
     * Creates a new {@code ODataFesParser}.
     */
    public ODataFesParser() {
        this.urlEscaper = new PercentEscaper("-_.*", false);
        this.odata = new ODataImpl();
        this.csdlProvider = new ObservationCsdlEdmProvider();
        this.edm = new EdmProviderImpl(this.csdlProvider);
        // >=4.2.0
        this.parser = new Parser(this.edm, this.odata);
        // >=4.0.0 <4.2.0
        // this.parser = new Parser();

    }

    @Override
    public Filter decode(String objectToDecode)
            throws DecodingException {
        LOG.debug("Parsing filter: {}", objectToDecode);
        if (objectToDecode == null || objectToDecode.isEmpty()) {
            return null;
        }
        try {
            String encode = urlEscaper.escape(checkForGeoFitler(objectToDecode));
            // >=4.4.0
            UriInfo parseUri = parser.parseUri(PATH, "$filter=" + encode, FRAGMENT, BASE_URI);
            // >=4.2.0 <4.4.0
            // UriInfo parseUri = parser.parseUri(PATH, "$filter=" + encode,
            // FRAGMENT);
            // >=4.0.0 <4.2.0
            // UriInfo parseUri = parser.parseUri(PATH, "$filter=" + encode,
            // FRAGMENT, this.edm);
            return parseUri.getFilterOption().getExpression().accept(new ExpressionGenerator())
                           .accept(new RenamingVisitor(csdlProvider::mapProperty)).accept(new FilterGenerator());
        } catch (ODataException ex) {
            throw new DecodingException(ex);
        }
    }

    @Override
    public Set getKeys() {
        // TODO implement ODataFesParser.getKeys()
        return Collections.emptySet();
    }

    private String checkForGeoFitler(String objectToDecode) {
        String modified = objectToDecode;
        if (objectToDecode.contains("geo.")) {
            modified = objectToDecode.replace(",'SRID", ",geometry'SRID").replace("(featureOfInterest,",
                                                                                  "(featureOfInterest/shape,");
        } else if (objectToDecode.contains(FEATURE_EQUALS)) {
            modified = modified.replace(FEATURE_EQUALS, "featureOfInterest/id eq '");
        }
        return modified;
    }

    /**
     * Parse the value expression as an {@code Geometry} in WKT or EWKT format. Geographies are handled as if they would
     * be geometries.
     *
     * @param val the geometry value
     * @return the geometry
     * @throws DecodingException if the geometry is invalid
     */
    private static Geometry parseGeometry(StringValueExpr val)
            throws DecodingException {
        String value = val.getValue();
        if (value.startsWith(GEOGRAPHY_TYPE)) {
            value = value.substring(GEOGRAPHY_TYPE.length());
        }
        if (value.startsWith(GEOMETRY_TYPE)) {
            value = value.substring(GEOMETRY_TYPE.length());
        }
        value = stripQuotes(value).toUpperCase();
        int srid = 4326;
        if (value.startsWith(SRID_PREFIX)) {
            int sep = value.indexOf(';');
            if (sep > SRID_PREFIX.length() && value.length() > sep) {
                try {
                    srid = Integer.parseInt(value.substring(SRID_PREFIX.length(), sep));
                } catch (NumberFormatException ex) {
                    throw invalidGeometry(val, ex);
                }
                value = value.substring(sep + 1);
            } else {
                throw invalidGeometry(val);
            }
        }
        PrecisionModel precisionModel = new PrecisionModel(PrecisionModel.FLOATING);
        GeometryFactory geometryFactory = new GeometryFactory(precisionModel, srid);
        WKTReader wktReader = new WKTReader(geometryFactory);
        try {
            return wktReader.read(value);
        } catch (ParseException ex) {
            throw invalidGeometry(val, ex);
        }
    }

    /**
     * Get the the pair of value and member expression from the to expressions or {@code Optional.empty()} if the
     * expression do not match the types.
     *
     * @param first  the first expression
     * @param second the second expression
     * @return the member-value-pair
     */
    private static Optional getMemberValuePair(Expr first, Expr second) {
        if (first.asMember().isPresent()) {
            MemberExpr member = first.asMember().get();
            Optional valueOpt = second.asTextValue();
            return valueOpt.map(textExpr -> new MemberValueExprPair(member, textExpr));
        } else {
            MemberExpr member = second.asMember().get();
            Optional valueOpt = first.asTextValue();
            return valueOpt.map(textExpr -> new MemberValueExprPair(member, textExpr));
        }
    }

    /**
     * Get the the pair of value and member expression from the to expressions or {@code Optional.empty()} if the
     * expression do not match the types.
     *
     * @param expr the binary expression
     * @return the member-value-pair
     */
    private static Optional getMemberValuePair(BinaryExpr expr) {
        return getMemberValuePair(expr.getLeft(), expr.getRight());
    }

    /**
     * Get the the pair of value and member expression from the to expressions or {@code Optional.empty()} if the
     * expression do not match the types.
     *
     * @param expr the binary expression
     * @return the member-value-pair
     */
    private static Optional getMemberValuePair(List expr) {
        if (expr.size() != 2) {
            return Optional.empty();
        }
        Iterator iter = expr.iterator();
        return getMemberValuePair(iter.next(), iter.next());
    }

    /**
     * Strip any enclosing single quotes from the string.
     *
     * @param value the string value
     * @return the string value without quotes
     */
    @CheckReturnValue
    private static String stripQuotes(String value) {
        return value != null && value.length() >= 2 && value.startsWith("'") && value.endsWith("'")
               ? value.substring(1, value.length() - 1)
               : value;
    }

    /**
     * Get the {@code ComparisonOperator} matching the supplied {@code BinaryOperatorKind}.
     *
     * @param op the operator
     * @return the {@code ComparisonOperator} or {@code Optional.empty()} if none matches
     */
    private static Optional getComparisonOperator(BinaryOperatorKind op) {
        switch (op) {
            case EQ:
                return Optional.of(ComparisonOperator.PropertyIsEqualTo);
            case GE:
                return Optional.of(ComparisonOperator.PropertyIsGreaterThanOrEqualTo);
            case LE:
                return Optional.of(ComparisonOperator.PropertyIsLessThanOrEqualTo);
            case GT:
                return Optional.of(ComparisonOperator.PropertyIsGreaterThan);
            case LT:
                return Optional.of(ComparisonOperator.PropertyIsLessThan);
            case NE:
                return Optional.of(ComparisonOperator.PropertyIsNotEqualTo);
            default:
                return Optional.empty();
        }
    }

    /**
     * Get the {@code BinaryLogicOperator} matching the supplied {@code BinaryOperatorKind}.
     *
     * @param op the operator
     * @return the {@code BinaryLogicOperator} or {@code Optional.empty()} if none matches
     */
    private static Optional getLogicOperator(BinaryOperatorKind op) {
        switch (op) {
            case AND:
                return Optional.of(BinaryLogicOperator.And);
            case OR:
                return Optional.of(BinaryLogicOperator.Or);
            default:
                return Optional.empty();
        }
    }

    /**
     * Createa new {@code DecodingException} indicating that the geometry in {@code val} is invalid.
     *
     * @param val the value containing the invalid geometry
     * @return the exception
     */
    private static DecodingException invalidGeometry(StringValueExpr val) {
        return invalidGeometry(val, null);
    }

    /**
     * Createa new {@code DecodingException} indicating that the geometry in {@code val} is invalid.
     *
     * @param val   the value containing the invalid geometry
     * @param cause the exception describing the invalidity
     * @return the exception
     */
    private static DecodingException invalidGeometry(StringValueExpr val, Throwable cause) {
        return new DecodingException(cause, "invalid geometry: %s", val.getValue());
    }

    /**
     * Class to hold a pair of member and value expressions.
     */
    private static final class MemberValueExprPair {
        private final MemberExpr member;
        private final StringValueExpr value;

        /**
         * Create a new {@code MemberValueExprPair}.
         *
         * @param member the member
         * @param value  the value
         */
        MemberValueExprPair(MemberExpr member, TextExpr value) {
            this.member = Objects.requireNonNull(member);
            this.value = Objects.requireNonNull((StringValueExpr) value);
        }

        /**
         * Get the member expression.
         *
         * @return the expression
         */
        MemberExpr getMember() {
            return member;
        }

        /**
         * Get the value expression.
         *
         * @return the expression
         */
        StringValueExpr getValue() {
            return value;
        }
    }

    /**
     * Adapter for {@link ExpressionVisitor} to compensate for olingo's terrible version incompatibilities.
     *
     * @param  The return type
     */
    private interface ExpressionVisitorAdapter extends ExpressionVisitor {
        // >=4.7.0
        @Override
        default T visitBinaryOperator(BinaryOperatorKind operator, T left, List right)
                throws ExpressionVisitException, ODataApplicationException {
            T result = left;
            for (T expr : right) {
                result = visitBinaryOperator(operator, result, expr);
            }
            return result;
        }

        // >=4.2.0
        @Override
        default T visitMember(Member member)
                throws ExpressionVisitException, ODataApplicationException {
            return visitMember(member.getResourcePath());
        }

        // >=4.0.0<=4.2.0
        // @Override
        T visitMember(UriInfoResource member)
                throws ExpressionVisitException, ODataApplicationException;
    }

    /**
     * Class to generate a {@code Expr} from the Olingo structures.
     */
    private static final class ExpressionGenerator implements ExpressionVisitorAdapter {

        @Override
        public Expr visitBinaryOperator(BinaryOperatorKind op, Expr left, Expr right)
                throws ExpressionVisitException {
            Supplier exceptionSupplier = () -> new ExpressionVisitException(
                    String.format("Operator %s is not supported: %s %s %s", op, left, op, right));
            switch (op) {
                case AND:
                case OR: {
                    BinaryLogicOperator operator = getLogicOperator(op).orElseThrow(exceptionSupplier);
                    BooleanExpr leftOperand = left.asBoolean().orElseThrow(exceptionSupplier);
                    BooleanExpr rightOperand = right.asBoolean().orElseThrow(exceptionSupplier);
                    return new BooleanBinaryExpr(operator, leftOperand, rightOperand);
                }
                case EQ:
                case NE:
                case GT:
                case GE:
                case LT:
                case LE: {
                    MemberValueExprPair mv = getMemberValuePair(left, right).orElseThrow(exceptionSupplier);
                    ComparisonOperator operator = getComparisonOperator(op).orElseThrow(exceptionSupplier);
                    return new ComparisonExpr(operator, mv.getMember(), mv.getValue());
                }
                default:
                    throw exceptionSupplier.get();
            }

        }

        @Override
        public StringValueExpr visitLiteral(Literal literal) {
            return new StringValueExpr(stripQuotes(literal.getText()));
        }

        @Override
        public MethodCallExpr visitMethodCall(MethodKind methodCall, List parameters) {
            return new MethodCallExpr(methodCall.toString(), parameters);
        }

        @Override
        public UnaryExpr visitUnaryOperator(UnaryOperatorKind op, Expr operand)
                throws ExpressionVisitException {
            Supplier exceptionSupplier =
                    () -> new ExpressionVisitException(String.format("Operator is not supported: %s %s", op,
                                                                     operand));
            switch (op) {
                case NOT:
                    return new BooleanUnaryExpr(UnaryLogicOperator.Not,
                                                operand.asBoolean().orElseThrow(exceptionSupplier));
                case MINUS:
                default:
                    throw exceptionSupplier.get();

            }
        }

        @Override
        public Expr visitLambdaExpression(String fun, String var, Expression expr)
                throws ExpressionVisitException {
            throw new ExpressionVisitException("Lambda expressions are not supported");
        }

        @Override
        public Expr visitMember(UriInfoResource member)
                throws ExpressionVisitException {
            return new MemberExpr(member.getUriResourceParts().stream().map(UriResource::getSegmentValue)
                                        .collect(Collectors.joining("/")));
        }

        @Override
        public Expr visitAlias(String aliasName)
                throws ExpressionVisitException {
            throw new ExpressionVisitException("aliases are not supported");
        }

        @Override
        public Expr visitTypeLiteral(EdmType type)
                throws ExpressionVisitException {
            throw new ExpressionVisitException("type literals are not supported");
        }

        @Override
        public Expr visitLambdaReference(String variableName)
                throws ExpressionVisitException {
            throw new ExpressionVisitException("Lambda references are not supported");
        }

        @Override
        public Expr visitEnum(EdmEnumType type, List enumValues)
                throws ExpressionVisitException {
            throw new ExpressionVisitException("enums are not supported");
        }
    }

    /**
     * Class to create a {@code Filter} from an {@code Expr}.
     */
    private static final class FilterGenerator implements ExprVisitor, DecodingException> {
        private static final String WILDCARD = "%";

        @Override
        public Filter visitBooleanBinary(BooleanBinaryExpr expr) throws DecodingException {
            return new BinaryLogicFilter(expr.getOperator(),
                                         expr.getLeft().accept(this),
                                         expr.getRight().accept(this));
        }

        @Override
        public Filter visitBooleanUnary(BooleanUnaryExpr expr) throws DecodingException {
            return new UnaryLogicFilter(expr.getOperand().accept(this));
        }

        @Override
        public Filter visitComparison(ComparisonExpr expr) throws DecodingException {
            MemberValueExprPair memberValuePair = getMemberValuePair(expr).orElseThrow(this::unsupported);
            return new ComparisonFilter(expr.getOperator(),
                                        memberValuePair.getMember().getValue(),
                                        memberValuePair.getValue().getValue());
        }

        @Override
        public Filter visitMethodCall(MethodCallExpr expr) throws DecodingException {
            switch (expr.getName()) {
                case METHOD_CONTAINS: {
                    MemberValueExprPair mv = getMemberValuePair(expr.getParameters()).orElseThrow(this::unsupported);
                    String referenceValue = mv.getMember().getValue();
                    String value = WILDCARD + mv.getValue().getValue() + WILDCARD;
                    return new ComparisonFilter(ComparisonOperator.PropertyIsLike, referenceValue, value);
                }
                case METHOD_STARTS_WITH: {
                    MemberValueExprPair mv = getMemberValuePair(expr.getParameters()).orElseThrow(this::unsupported);
                    String referenceValue = mv.getMember().getValue();
                    String value = mv.getValue().getValue() + WILDCARD;
                    return new ComparisonFilter(ComparisonOperator.PropertyIsLike, referenceValue, value);
                }
                case METHOD_ENDS_WITH: {
                    MemberValueExprPair mv = getMemberValuePair(expr.getParameters()).orElseThrow(this::unsupported);
                    String referenceValue = mv.getMember().getValue();
                    String value = WILDCARD + mv.getValue().getValue();
                    return new ComparisonFilter(ComparisonOperator.PropertyIsLike, referenceValue, value);

                }
                case METHOD_GEO_INTERSECTS: {
                    MemberValueExprPair mv = getMemberValuePair(expr.getParameters()).orElseThrow(this::unsupported);
                    String referenceValue = mv.getMember().getValue();
                    if (referenceValue.equals("om:featureOfInterest")) {
                        referenceValue += "/*/sams:shape";
                    }
                    Geometry geometry = parseGeometry(mv.getValue());
                    return new SpatialFilter(SpatialOperator.Intersects, geometry, referenceValue);
                }
                default:
                    throw new DecodingException("unsupported method '%s'", expr.getName());
            }
        }

        @Override
        public Filter visitMember(MemberExpr expr)
                throws DecodingException {
            throw new DecodingException("unexpected member expression '%s'", expr.getValue());
        }

        /**
         * Visit a value expression.
         *
         * @param expr the expression
         * @return the result of the visit
         * @throws DecodingException if the visit fails
         */
        @Override
        public Filter visitString(StringValueExpr expr) throws DecodingException {
            return null;
        }

        /**
         * Visit a arithmetic expression.
         *
         * @param expr the expression
         * @return the result of the visit
         * @throws DecodingException if the visit fails
         */
        @Override
        public Filter visitSimpleArithmetic(SimpleArithmeticExpr expr) throws DecodingException {
            return null;
        }

        /**
         * Visit a time expression.
         *
         * @param expr the expression
         * @return the result of the visit
         * @throws DecodingException if the visit fails
         */
        @Override
        public Filter visitTime(TimeValueExpr expr) throws DecodingException {
            return null;
        }

        /**
         * Visit a geometry expression.
         *
         * @param expr the expression
         * @return the result of the visit
         * @throws DecodingException if the visit fails
         */
        @Override
        public Filter visitGeometry(GeoValueExpr expr) throws DecodingException {
            return null;
        }

        /**
         * Visit a number expression.
         *
         * @param expr the expression
         * @return the result of the visit
         * @throws DecodingException if the visit fails
         */
        @Override
        public Filter visitNumeric(NumericValueExpr expr) throws DecodingException {
            return null;
        }

        public Filter visitValue(StringValueExpr expr)
                throws DecodingException {
            throw new DecodingException("unexpected value expression '%s'", expr.getValue());
        }

        /**
         * Creates an {@code DecodingException} indicating that the supplied expression is not supported.
         *
         * @return the exception
         */
        private DecodingException unsupported() {
            return new DecodingException("unsupported expression");
        }
    }

    /**
     * Abstract transforming visitor that is able to modify expression.
     *
     * @param  The exception type
     */
    private static class AbstractExprTransformer
            implements
            ExprVisitor {

        @Override
        public Expr visitBooleanBinary(BooleanBinaryExpr expr)
                throws T {
            BinaryLogicOperator op = expr.getOperator();
            BooleanExpr left = expr.getLeft().accept(this).asBoolean().orElseThrow(Error::new);
            BooleanExpr right = expr.getRight().accept(this).asBoolean().orElseThrow(Error::new);
            return new BooleanBinaryExpr(op, left, right);
        }

        @Override
        public Expr visitBooleanUnary(BooleanUnaryExpr expr)
                throws T {
            UnaryLogicOperator op = expr.getOperator();
            BooleanExpr operand = expr.getOperand().accept(this).asBoolean().orElseThrow(Error::new);
            return new BooleanUnaryExpr(op, operand);
        }

        @Override
        public Expr visitComparison(ComparisonExpr expr)
                throws T {
            ComparisonOperator op = expr.getOperator();
            Expr left = expr.getLeft().accept(this);
            Expr right = expr.getRight().accept(this);
            return new ComparisonExpr(op, left, right);
        }

        @Override
        public Expr visitMethodCall(MethodCallExpr expr)
                throws T {
            String name = expr.getName();
            List list = new ArrayList<>(expr.getParameters().size());
            for (Expr e : expr.getParameters()) {
                list.add(e.accept(this));
            }
            return new MethodCallExpr(name, list);
        }

        @Override
        public Expr visitMember(MemberExpr expr) {
            String value = expr.getValue();
            return new MemberExpr(value);
        }

        /**
         * Visit a value expression.
         *
         * @param expr the expression
         * @return the result of the visit
         * @throws T if the visit fails
         */
        @Override
        public Expr visitString(StringValueExpr expr) throws T {
            String value = expr.getValue();
            return new StringValueExpr(value);
        }

        /**
         * Visit a arithmetic expression.
         *
         * @param expr the expression
         * @return the result of the visit
         * @throws T if the visit fails
         */
        @Override
        public Expr visitSimpleArithmetic(SimpleArithmeticExpr expr) throws T {
            return null;
        }

        /**
         * Visit a time expression.
         *
         * @param expr the expression
         * @return the result of the visit
         * @throws T if the visit fails
         */
        @Override
        public Expr visitTime(TimeValueExpr expr) throws T {
            return null;
        }

        /**
         * Visit a geometry expression.
         *
         * @param expr the expression
         * @return the result of the visit
         * @throws T if the visit fails
         */
        @Override
        public Expr visitGeometry(GeoValueExpr expr) throws T {
            return null;
        }

        /**
         * Visit a number expression.
         *
         * @param expr the expression
         * @return the result of the visit
         * @throws T if the visit fails
         */
        @Override
        public Expr visitNumeric(NumericValueExpr expr) throws T {
            return null;
        }
    }

    /**
     * Transformer for expression that modifies the member referneces.
     */
    private static class RenamingVisitor
            extends
            AbstractExprTransformer {

        private final Function mapper;

        /**
         * Create a new {@code RenamingVisitor}.
         *
         * @param mapper the mapper used to modifiy the member references
         */
        RenamingVisitor(Function mapper) {
            this.mapper = mapper;
        }

        @Override
        public Expr visitMember(MemberExpr expr) {
            return new MemberExpr(mapper.apply(expr.getValue()));
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy