001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.imagery.vectortile.mapbox.style;
003
004import java.util.Arrays;
005import java.util.Objects;
006import java.util.stream.Collectors;
007
008import javax.json.JsonArray;
009import javax.json.JsonObject;
010import javax.json.JsonString;
011import javax.json.JsonValue;
012
013/**
014 * A Mapbox vector style expression (immutable)
015 * @author Taylor Smock
016 * @see <a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/expressions/">https://docs.mapbox.com/mapbox-gl-js/style-spec/expressions/</a>
017 * @since 17862
018 */
019public final class Expression {
020    /** An empty expression to use */
021    public static final Expression EMPTY_EXPRESSION = new Expression(JsonValue.NULL);
022    private static final String EMPTY_STRING = "";
023
024    private final String mapcssFilterExpression;
025
026    /**
027     * Create a new filter expression. <i>Please note that this currently only supports basic comparators!</i>
028     * @param value The value to parse
029     */
030    public Expression(JsonValue value) {
031        if (value.getValueType() == JsonValue.ValueType.ARRAY) {
032            final JsonArray array = value.asJsonArray();
033            if (!array.isEmpty() && array.get(0).getValueType() == JsonValue.ValueType.STRING) {
034                if ("==".equals(array.getString(0))) {
035                    // The mapcss equivalent of == is = (for the most part)
036                    this.mapcssFilterExpression = convertToString(array.get(1)) + "=" + convertToString(array.get(2));
037                } else if (Arrays.asList("<=", ">=", ">", "<", "!=").contains(array.getString(0))) {
038                    this.mapcssFilterExpression = convertToString(array.get(1)) + array.getString(0) + convertToString(array.get(2));
039                } else {
040                    this.mapcssFilterExpression = EMPTY_STRING;
041                }
042            } else {
043                this.mapcssFilterExpression = EMPTY_STRING;
044            }
045        } else {
046            this.mapcssFilterExpression = EMPTY_STRING;
047        }
048    }
049
050    /**
051     * Convert a value to a string
052     * @param value The value to convert
053     * @return A string
054     */
055    private static String convertToString(JsonValue value) {
056        switch (value.getValueType()) {
057        case STRING:
058            return ((JsonString) value).getString();
059        case FALSE:
060            return Boolean.FALSE.toString();
061        case TRUE:
062            return Boolean.TRUE.toString();
063        case NUMBER:
064            return value.toString();
065        case ARRAY:
066            return '['
067              + ((JsonArray) value).stream().map(Expression::convertToString).collect(Collectors.joining(","))
068              + ']';
069        case OBJECT:
070            return '{'
071              + ((JsonObject) value).entrySet().stream()
072              .map(entry -> entry.getKey() + ":" + convertToString(entry.getValue())).collect(
073                Collectors.joining(","))
074              + '}';
075        case NULL:
076        default:
077            return EMPTY_STRING;
078        }
079    }
080
081    @Override
082    public String toString() {
083        return !EMPTY_STRING.equals(this.mapcssFilterExpression) ? '[' + this.mapcssFilterExpression + ']' : EMPTY_STRING;
084    }
085
086    @Override
087    public boolean equals(Object other) {
088        if (other instanceof Expression) {
089            Expression o = (Expression) other;
090            return Objects.equals(this.mapcssFilterExpression, o.mapcssFilterExpression);
091        }
092        return false;
093    }
094
095    @Override
096    public int hashCode() {
097        return Objects.hash(this.mapcssFilterExpression);
098    }
099}