From 216448524b262b636d0b83c9dc31390f1359c98e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Paczos?= Date: Mon, 20 Aug 2018 19:38:16 +0200 Subject: [android] "collator" and "resolved-locale" expressions support --- .../mapboxsdk/style/expressions/Expression.java | 596 +++++++++++++++++++-- .../style/expressions/ExpressionTest.java | 77 +++ .../mapboxsdk/testapp/style/ExpressionTest.java | 63 ++- 3 files changed, 701 insertions(+), 35 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java index 1aa0ce9093..bd4d5d7d4b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java @@ -4,17 +4,22 @@ import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.Size; + import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonNull; +import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.mapbox.mapboxsdk.style.layers.PropertyFactory; import com.mapbox.mapboxsdk.style.layers.PropertyValue; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Locale; +import java.util.Map; /** * The value for any layout property, paint property, or filter may be specified as an expression. @@ -345,7 +350,8 @@ public class Expression { * @return expression * @see Style specification */ - public static Expression rgba(@NonNull Number red, @NonNull Number green, @NonNull Number blue, @NonNull Number alpha) { + public static Expression rgba(@NonNull Number red, @NonNull Number green, @NonNull Number blue, @NonNull Number + alpha) { return rgba(literal(red), literal(green), literal(blue), literal(alpha)); } @@ -384,6 +390,32 @@ public class Expression { return new Expression("==", compareOne, compareTwo); } + /** + * Returns true if the input values are equal, false otherwise. + * The inputs must be numbers, strings, or booleans, and both of the same type. + *

+ * Example usage: + *

+ *
+   * {@code
+   * FillLayer fillLayer = new FillLayer("layer-id", "source-id");
+   * fillLayer.setFilter(
+   *     eq(get("keyToValue"), get("keyToOtherValue"), collator(true, false))
+   * );
+   * }
+   * 
+ * + * @param compareOne the first expression + * @param compareTwo the second expression + * @param collator the collator expression + * @return expression + * @see Style specification + */ + public static Expression eq(@NonNull Expression compareOne, @NonNull Expression compareTwo, + @NonNull Expression collator) { + return new Expression("==", compareOne, compareTwo, collator); + } + /** * Returns true if the input values are equal, false otherwise. *

@@ -430,6 +462,32 @@ public class Expression { return eq(compareOne, literal(compareTwo)); } + /** + * Returns true if the input values are equal, false otherwise. + * The inputs must be numbers, strings, or booleans, and both of the same type. + *

+ * Example usage: + *

+ *
+   * {@code
+   * FillLayer fillLayer = new FillLayer("layer-id", "source-id");
+   * fillLayer.setFilter(
+   *     eq(get("keyToValue"), get("keyToOtherValue"), collator(true, false))
+   * );
+   * }
+   * 
+ * + * @param compareOne the first expression + * @param compareTwo the second String + * @param collator the collator expression + * @return expression + * @see Style specification + */ + public static Expression eq(@NonNull Expression compareOne, @NonNull String compareTwo, + @NonNull Expression collator) { + return eq(compareOne, literal(compareTwo), collator); + } + /** * Returns true if the input values are equal, false otherwise. *

@@ -477,6 +535,32 @@ public class Expression { return new Expression("!=", compareOne, compareTwo); } + /** + * Returns true if the input values are not equal, false otherwise. + * The inputs must be numbers, strings, or booleans, and both of the same type. + *

+ * Example usage: + *

+ *
+   * {@code
+   * FillLayer fillLayer = new FillLayer("layer-id", "source-id");
+   * fillLayer.setFilter(
+   *     neq(get("keyToValue"), get("keyToOtherValue"), collator(true, false))
+   * );
+   * }
+   * 
+ * + * @param compareOne the first expression + * @param compareTwo the second expression + * @param collator the collator expression + * @return expression + * @see Style specification + */ + public static Expression neq(@NonNull Expression compareOne, @NonNull Expression compareTwo, + @NonNull Expression collator) { + return new Expression("!=", compareOne, compareTwo, collator); + } + /** * Returns true if the input values are equal, false otherwise. *

@@ -523,6 +607,32 @@ public class Expression { return new Expression("!=", compareOne, literal(compareTwo)); } + /** + * Returns true if the input values are not equal, false otherwise. + * The inputs must be numbers, strings, or booleans, and both of the same type. + *

+ * Example usage: + *

+ *
+   * {@code
+   * FillLayer fillLayer = new FillLayer("layer-id", "source-id");
+   * fillLayer.setFilter(
+   *     neq(get("keyToValue"), get("keyToOtherValue"), collator(true, false))
+   * );
+   * }
+   * 
+ * + * @param compareOne the first expression + * @param compareTwo the second String + * @param collator the collator expression + * @return expression + * @see Style specification + */ + public static Expression neq(@NonNull Expression compareOne, @NonNull String compareTwo, + @NonNull Expression collator) { + return new Expression("!=", compareOne, literal(compareTwo), collator); + } + /** * Returns `true` if the input values are not equal, `false` otherwise. *

@@ -570,6 +680,32 @@ public class Expression { return new Expression(">", compareOne, compareTwo); } + /** + * Returns true if the first input is strictly greater than the second, false otherwise. + * The inputs must be numbers or strings, and both of the same type. + *

+ * Example usage: + *

+ *
+   * {@code
+   * FillLayer fillLayer = new FillLayer("layer-id", "source-id");
+   * fillLayer.setFilter(
+   *     gt(get("keyToValue"), get("keyToOtherValue"), collator(true, false))
+   * );
+   * }
+   * 
+ * + * @param compareOne the first expression + * @param compareTwo the second expression + * @param collator the collator expression + * @return expression + * @see Style specification + */ + public static Expression gt(@NonNull Expression compareOne, @NonNull Expression compareTwo, + @NonNull Expression collator) { + return new Expression(">", compareOne, compareTwo, collator); + } + /** * Returns true if the first input is strictly greater than the second, false otherwise. *

@@ -616,6 +752,32 @@ public class Expression { return new Expression(">", compareOne, literal(compareTwo)); } + /** + * Returns true if the first input is strictly greater than the second, false otherwise. + * The inputs must be numbers or strings, and both of the same type. + *

+ * Example usage: + *

+ *
+   * {@code
+   * FillLayer fillLayer = new FillLayer("layer-id", "source-id");
+   * fillLayer.setFilter(
+   *     gt(get("keyToValue"), get("keyToOtherValue"), collator(true, false))
+   * );
+   * }
+   * 
+ * + * @param compareOne the first expression + * @param compareTwo the second String + * @param collator the collator expression + * @return expression + * @see Style specification + */ + public static Expression gt(@NonNull Expression compareOne, @NonNull String compareTwo, + @NonNull Expression collator) { + return new Expression(">", compareOne, literal(compareTwo), collator); + } + /** * Returns true if the first input is strictly less than the second, false otherwise. * The inputs must be numbers or strings, and both of the same type. @@ -626,13 +788,13 @@ public class Expression { * {@code * FillLayer fillLayer = new FillLayer("layer-id", "source-id"); * fillLayer.setFilter( - * lt(get("keyToValue"), get("keyToOtherValue")) + * lt(get("keyToValue"), get("keyToOtherValue"), collator(true, false)) * ); * } * * * @param compareOne the first expression - * @param compareTwo the second number + * @param compareTwo the second expression * @return expression * @see Style specification */ @@ -640,6 +802,32 @@ public class Expression { return new Expression("<", compareOne, compareTwo); } + /** + * Returns true if the first input is strictly less than the second, false otherwise. + * The inputs must be numbers or strings, and both of the same type. + *

+ * Example usage: + *

+ *
+   * {@code
+   * FillLayer fillLayer = new FillLayer("layer-id", "source-id");
+   * fillLayer.setFilter(
+   *     lt(get("keyToValue"), get("keyToOtherValue"), collator(true, false))
+   * );
+   * }
+   * 
+ * + * @param compareOne the first expression + * @param compareTwo the second number + * @param collator the collator expression + * @return expression + * @see Style specification + */ + public static Expression lt(@NonNull Expression compareOne, @NonNull Expression compareTwo, + @NonNull Expression collator) { + return new Expression("<", compareOne, compareTwo, collator); + } + /** * Returns true if the first input is strictly less than the second, false otherwise. *

@@ -686,6 +874,32 @@ public class Expression { return new Expression("<", compareOne, literal(compareTwo)); } + /** + * Returns true if the first input is strictly less than the second, false otherwise. + * The inputs must be numbers or strings, and both of the same type. + *

+ * Example usage: + *

+ *
+   * {@code
+   * FillLayer fillLayer = new FillLayer("layer-id", "source-id");
+   * fillLayer.setFilter(
+   *     lt(get("keyToValue"), get("keyToOtherValue"), collator(true, false))
+   * );
+   * }
+   * 
+ * + * @param compareOne the first expression + * @param compareTwo the second String + * @param collator the collator expression + * @return expression + * @see Style specification + */ + public static Expression lt(@NonNull Expression compareOne, @NonNull String compareTwo, + @NonNull Expression collator) { + return new Expression("<", compareOne, literal(compareTwo), collator); + } + /** * Returns true if the first input is greater than or equal to the second, false otherwise. * The inputs must be numbers or strings, and both of the same type. @@ -710,6 +924,32 @@ public class Expression { return new Expression(">=", compareOne, compareTwo); } + /** + * Returns true if the first input is greater than or equal to the second, false otherwise. + * The inputs must be numbers or strings, and both of the same type. + *

+ * Example usage: + *

+ *
+   * {@code
+   * FillLayer fillLayer = new FillLayer("layer-id", "source-id");
+   * fillLayer.setFilter(
+   *     gte(get("keyToValue"), get("keyToOtherValue"), collator(true, false))
+   * );
+   * }
+   * 
+ * + * @param compareOne the first expression + * @param compareTwo the second expression + * @param collator the collator expression + * @return expression + * @see Style specification + */ + public static Expression gte(@NonNull Expression compareOne, @NonNull Expression compareTwo, + @NonNull Expression collator) { + return new Expression(">=", compareOne, compareTwo, collator); + } + /** * Returns true if the first input is greater than or equal to the second, false otherwise. *

@@ -756,6 +996,32 @@ public class Expression { return new Expression(">=", compareOne, literal(compareTwo)); } + /** + * Returns true if the first input is greater than or equal to the second, false otherwise. + * The inputs must be numbers or strings, and both of the same type. + *

+ * Example usage: + *

+ *
+   * {@code
+   * FillLayer fillLayer = new FillLayer("layer-id", "source-id");
+   * fillLayer.setFilter(
+   *     gte(get("keyToValue"), get("keyToOtherValue"), collator(true, false))
+   * );
+   * }
+   * 
+ * + * @param compareOne the first expression + * @param compareTwo the second String + * @param collator the collator expression + * @return expression + * @see Style specification + */ + public static Expression gte(@NonNull Expression compareOne, @NonNull String compareTwo, + @NonNull Expression collator) { + return new Expression(">=", compareOne, literal(compareTwo), collator); + } + /** * Returns true if the first input is less than or equal to the second, false otherwise. * The inputs must be numbers or strings, and both of the same type. @@ -780,6 +1046,32 @@ public class Expression { return new Expression("<=", compareOne, compareTwo); } + /** + * Returns true if the first input is less than or equal to the second, false otherwise. + * The inputs must be numbers or strings, and both of the same type. + *

+ * Example usage: + *

+ *
+   * {@code
+   * FillLayer fillLayer = new FillLayer("layer-id", "source-id");
+   * fillLayer.setFilter(
+   *     lte(get("keyToValue"), get("keyToOtherValue"), collator(true, false))
+   * );
+   * }
+   * 
+ * + * @param compareOne the first expression + * @param compareTwo the second expression + * @param collator the collator expression + * @return expression + * @see Style specification + */ + public static Expression lte(@NonNull Expression compareOne, @NonNull Expression compareTwo, + @NonNull Expression collator) { + return new Expression("<=", compareOne, compareTwo, collator); + } + /** * Returns true if the first input is less than or equal to the second, false otherwise. *

@@ -826,6 +1118,32 @@ public class Expression { return new Expression("<=", compareOne, literal(compareTwo)); } + /** + * Returns true if the first input is less than or equal to the second, false otherwise. + * The inputs must be numbers or strings, and both of the same type. + *

+ * Example usage: + *

+ *
+   * {@code
+   * FillLayer fillLayer = new FillLayer("layer-id", "source-id");
+   * fillLayer.setFilter(
+   *     lte(get("keyToValue"), get("keyToOtherValue"), collator(true, false))
+   * );
+   * }
+   * 
+ * + * @param compareOne the first expression + * @param compareTwo the second String + * @param collator the collator expression + * @return expression + * @see Style specification + */ + public static Expression lte(@NonNull Expression compareOne, @NonNull String compareTwo, + @NonNull Expression collator) { + return new Expression("<=", compareOne, literal(compareTwo), collator); + } + /** * Returns `true` if all the inputs are `true`, `false` otherwise. *

@@ -2478,6 +2796,32 @@ public class Expression { return floor(literal(number)); } + /** + * Returns the IETF language tag of the locale being used by the provided collator. + * This can be used to determine the default system locale, + * or to determine if a requested locale was successfully loaded. + *

+ * Example usage: + *

+ *
+   * {@code
+   * CircleLayer circleLayer = new CircleLayer("layer-id", "source-id");
+   * circleLayer.setProperties(
+   * circleColor(switchCase(
+     eq(literal("it"), resolvedLocale(collator(true, true, "it"))), literal(ColorUtils.colorToRgbaString(Color.GREEN)),
+          literal(ColorUtils.colorToRgbaString(Color.RED))))
+   * );
+   * }
+   * 
+ * + * @param collator the collator expression + * @return expression + * @see Style specification + */ + public static Expression resolvedLocale(Expression collator) { + return new Expression("resolved-locale", collator); + } + /** * Returns the input string converted to uppercase. *

@@ -2696,6 +3040,104 @@ public class Expression { return new Expression("boolean", input); } + /** + * Returns a collator for use in locale-dependent comparison operations. + * The case-sensitive and diacritic-sensitive options default to false. + * The locale argument specifies the IETF language tag of the locale to use. + * If none is provided, the default locale is used. If the requested locale is not available, + * the collator will use a system-defined fallback locale. + * Use resolved-locale to test the results of locale fallback behavior. + * + * @param caseSensitive case sensitive flag + * @param diacriticSensitive diacritic sensitive flag + * @param locale locale + * @return expression + * @see Style specification + */ + public static Expression collator(boolean caseSensitive, boolean diacriticSensitive, Locale locale) { + Map map = new HashMap<>(); + map.put("case-sensitive", literal(caseSensitive)); + map.put("diacritic-sensitive", literal(diacriticSensitive)); + + StringBuilder localeStringBuilder = new StringBuilder(); + + String language = locale.getLanguage(); + if (language != null && !language.isEmpty()) { + localeStringBuilder.append(language); + } + + String country = locale.getCountry(); + if (country != null && !country.isEmpty()) { + localeStringBuilder.append("-"); + localeStringBuilder.append(country); + } + + map.put("locale", literal(localeStringBuilder.toString())); + return new Expression("collator", new ExpressionMap(map)); + } + + /** + * Returns a collator for use in locale-dependent comparison operations. + * The case-sensitive and diacritic-sensitive options default to false. + * The locale argument specifies the IETF language tag of the locale to use. + * If none is provided, the default locale is used. If the requested locale is not available, + * the collator will use a system-defined fallback locale. + * Use resolved-locale to test the results of locale fallback behavior. + * + * @param caseSensitive case sensitive flag + * @param diacriticSensitive diacritic sensitive flag + * @return expression + * @see Style specification + */ + public static Expression collator(boolean caseSensitive, boolean diacriticSensitive) { + Map map = new HashMap<>(); + map.put("case-sensitive", literal(caseSensitive)); + map.put("diacritic-sensitive", literal(diacriticSensitive)); + return new Expression("collator", new ExpressionMap(map)); + } + + /** + * Returns a collator for use in locale-dependent comparison operations. + * The case-sensitive and diacritic-sensitive options default to false. + * The locale argument specifies the IETF language tag of the locale to use. + * If none is provided, the default locale is used. If the requested locale is not available, + * the collator will use a system-defined fallback locale. + * Use resolved-locale to test the results of locale fallback behavior. + * + * @param caseSensitive case sensitive flag + * @param diacriticSensitive diacritic sensitive flag + * @param locale locale + * @return expression + * @see Style specification + */ + public static Expression collator(Expression caseSensitive, Expression diacriticSensitive, Expression locale) { + Map map = new HashMap<>(); + map.put("case-sensitive", caseSensitive); + map.put("diacritic-sensitive", diacriticSensitive); + map.put("locale", locale); + return new Expression("collator", new ExpressionMap(map)); + } + + /** + * Returns a collator for use in locale-dependent comparison operations. + * The case-sensitive and diacritic-sensitive options default to false. + * The locale argument specifies the IETF language tag of the locale to use. + * If none is provided, the default locale is used. If the requested locale is not available, + * the collator will use a system-defined fallback locale. + * Use resolved-locale to test the results of locale fallback behavior. + * + * @param caseSensitive case sensitive flag + * @param diacriticSensitive diacritic sensitive flag + * @return expression + * @see Style specification + */ + public static Expression collator(Expression caseSensitive, Expression diacriticSensitive) { + Map map = new HashMap<>(); + map.put("case-sensitive", caseSensitive); + map.put("diacritic-sensitive", diacriticSensitive); + return new Expression("collator", new ExpressionMap(map)); + } + /** * Asserts that the input value is an object. If it is not, the expression is an error * The asserted input value is returned as result. @@ -3422,8 +3864,8 @@ public class Expression { array.add(operator); if (arguments != null) { for (Expression argument : arguments) { - if (argument instanceof Expression.ExpressionLiteral) { - array.add(toValue((ExpressionLiteral) argument)); + if (argument instanceof ValueExpression) { + array.add(((ValueExpression) argument).toValue()); } else { array.add(argument.toArray()); } @@ -3432,22 +3874,6 @@ public class Expression { return array.toArray(); } - /** - * Converts the expression value to an Object. - * - * @param expressionValue the expression value to convert - * @return the converted object expression - */ - private Object toValue(ExpressionLiteral expressionValue) { - Object value = expressionValue.toValue(); - if (value instanceof PropertyValue) { - throw new IllegalArgumentException("PropertyValue are not allowed as an expression literal, use value instead."); - } else if (value instanceof Expression.ExpressionLiteral) { - return toValue((ExpressionLiteral) value); - } - return value; - } - /** * Returns a string representation of the object that matches the definition set in the style specification. *

@@ -3549,7 +3975,7 @@ public class Expression { * {@link #literal(String)} and {@link #literal(Object)}. *

*/ - public static class ExpressionLiteral extends Expression { + public static class ExpressionLiteral extends Expression implements ValueExpression { protected Object literal; @@ -3572,7 +3998,14 @@ public class Expression { * * @return the literal object */ - Object toValue() { + @Override + public Object toValue() { + if (literal instanceof PropertyValue) { + throw new IllegalArgumentException( + "PropertyValue are not allowed as an expression literal, use value instead."); + } else if (literal instanceof Expression.ExpressionLiteral) { + return ((ExpressionLiteral) literal).toValue(); + } return literal; } @@ -3589,7 +4022,13 @@ public class Expression { */ @Override public String toString() { - return literal.toString(); + String string; + if (literal instanceof String) { + string = "\"" + literal + "\""; + } else { + string = literal.toString(); + } + return string; } /** @@ -3627,7 +4066,7 @@ public class Expression { return result; } - private String unwrapStringLiteral(String value) { + private static String unwrapStringLiteral(String value) { if (value.length() > 1 && value.charAt(0) == '\"' && value.charAt(value.length() - 1) == '\"') { return value.substring(1, value.length() - 1); @@ -3727,19 +4166,35 @@ public class Expression { JsonElement jsonElement; for (int i = 1; i < jsonArray.size(); i++) { jsonElement = jsonArray.get(i); - if (jsonElement instanceof JsonArray) { - arguments.add(convert((JsonArray) jsonElement)); - } else if (jsonElement instanceof JsonPrimitive) { - arguments.add(convert((JsonPrimitive) jsonElement)); - } else if (jsonElement instanceof JsonNull) { - arguments.add(new Expression.ExpressionLiteral("")); - } else { - throw new RuntimeException("Unsupported expression conversion for " + jsonElement.getClass()); - } + arguments.add(convert(jsonElement)); } return new Expression(operator, arguments.toArray(new Expression[arguments.size()])); } + /** + * Converts a JsonElement to an expression + * + * @param jsonElement the json element to convert + * @return the expression + */ + private static Expression convert(@NonNull JsonElement jsonElement) { + if (jsonElement instanceof JsonArray) { + return convert((JsonArray) jsonElement); + } else if (jsonElement instanceof JsonPrimitive) { + return convert((JsonPrimitive) jsonElement); + } else if (jsonElement instanceof JsonNull) { + return new Expression.ExpressionLiteral(""); + } else if (jsonElement instanceof JsonObject) { + Map map = new HashMap<>(); + for (String key : ((JsonObject) jsonElement).keySet()) { + map.put(key, convert(((JsonObject) jsonElement).get(key))); + } + return new ExpressionMap(map); + } else { + throw new RuntimeException("Unsupported expression conversion for " + jsonElement.getClass()); + } + } + /** * Converts a JsonPrimitive to an expression literal * @@ -3815,6 +4270,79 @@ public class Expression { } } + /** + * Wraps an expression value stored in a Map. + */ + private static class ExpressionMap extends Expression implements ValueExpression { + private Map map; + + ExpressionMap(Map map) { + this.map = map; + } + + @Override + public Object toValue() { + Map unwrappedMap = new HashMap<>(); + for (String key : map.keySet()) { + Expression expression = map.get(key); + if (expression instanceof Expression.ExpressionLiteral) { + unwrappedMap.put(key, ((ExpressionLiteral) expression).toValue()); + } else { + unwrappedMap.put(key, expression.toArray()); + } + } + + return unwrappedMap; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("{"); + for (String key : map.keySet()) { + builder.append("\"").append(key).append("\": "); + builder.append(map.get(key)); + builder.append(", "); + } + + if (map.size() > 0) { + builder.delete(builder.length() - 2, builder.length()); + } + + builder.append("}"); + return builder.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + ExpressionMap that = (ExpressionMap) o; + return map.equals(that.map); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (map == null ? 0 : map.hashCode()); + return result; + } + } + + /** + * Interface used to describe expressions that hold a Java value. + */ + private interface ValueExpression { + Object toValue(); + } + /** * Converts an object that is a primitive array to an Object[] * diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/expressions/ExpressionTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/expressions/ExpressionTest.java index fb74904f96..79bcdd7b5e 100644 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/expressions/ExpressionTest.java +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/expressions/ExpressionTest.java @@ -1,12 +1,16 @@ package com.mapbox.mapboxsdk.style.expressions; import android.graphics.Color; + import com.mapbox.mapboxsdk.style.layers.PropertyFactory; + import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; import static com.mapbox.mapboxsdk.style.expressions.Expression.abs; import static com.mapbox.mapboxsdk.style.expressions.Expression.acos; @@ -19,6 +23,7 @@ import static com.mapbox.mapboxsdk.style.expressions.Expression.atan; import static com.mapbox.mapboxsdk.style.expressions.Expression.bool; import static com.mapbox.mapboxsdk.style.expressions.Expression.ceil; import static com.mapbox.mapboxsdk.style.expressions.Expression.coalesce; +import static com.mapbox.mapboxsdk.style.expressions.Expression.collator; import static com.mapbox.mapboxsdk.style.expressions.Expression.color; import static com.mapbox.mapboxsdk.style.expressions.Expression.concat; import static com.mapbox.mapboxsdk.style.expressions.Expression.cos; @@ -60,6 +65,7 @@ import static com.mapbox.mapboxsdk.style.expressions.Expression.pow; import static com.mapbox.mapboxsdk.style.expressions.Expression.product; import static com.mapbox.mapboxsdk.style.expressions.Expression.properties; import static com.mapbox.mapboxsdk.style.expressions.Expression.raw; +import static com.mapbox.mapboxsdk.style.expressions.Expression.resolvedLocale; import static com.mapbox.mapboxsdk.style.expressions.Expression.rgb; import static com.mapbox.mapboxsdk.style.expressions.Expression.rgba; import static com.mapbox.mapboxsdk.style.expressions.Expression.round; @@ -1290,4 +1296,75 @@ public class ExpressionTest { String alpha = color.substring(0, color.length() - 1); assertEquals("alpha value should match", 0.254f, Float.valueOf(alpha), 0.001f); } + + @Test + public void testCollator() { + Object[] expected = new Object[] {"collator", + new HashMap() { + { + put("case-sensitive", true); + put("diacritic-sensitive", true); + put("locale", "it-IT"); + } + } + }; + Object[] actual = collator(true, true, Locale.ITALY).toArray(); + assertTrue("expression should match", Arrays.deepEquals(expected, actual)); + } + + @Test + public void testStringCollator() { + String expected = "[\"collator\", {\"diacritic-sensitive\": true, \"case-sensitive\": true, \"locale\": " + + "\"it\"}]"; + String actual = collator(true, true, Locale.ITALIAN).toString(); + assertEquals("expression should match", expected, actual); + } + + @Test + public void testResolvedLocale() { + Object[] expected = new Object[] {"resolved-locale", + new Object[] {"collator", + new HashMap() { + { + put("case-sensitive", false); + put("diacritic-sensitive", false); + put("locale", "it"); + } + } + } + }; + Object[] actual = resolvedLocale(collator(false, false, Locale.ITALIAN)).toArray(); + assertTrue("expression should match", Arrays.deepEquals(expected, actual)); + } + + @Test + public void testRawCollator() { + Object[] expected = new Object[] {"collator", + new HashMap() { + { + put("case-sensitive", true); + put("diacritic-sensitive", true); + put("locale", "it-IT"); + } + } + }; + Object[] actual = raw("[\"collator\", {\"diacritic-sensitive\": true, \"case-sensitive\": true, \"locale\": " + + "\"it-IT\"}]").toArray(); + assertTrue("expression should match", Arrays.deepEquals(expected, actual)); + } + + @Test + public void testRawCollatorDoubleConversion() { + Expression expected = collator(false, false, Locale.ITALIAN); + Object[] actual = raw(expected.toString()).toArray(); + assertTrue("expression should match", Arrays.deepEquals(expected.toArray(), actual)); + } + + @Test + public void testStringNestedCollator() { + String expected = "[\"collator\", {\"diacritic-sensitive\": [\"==\", 2.0, 1.0], \"case-sensitive\": false," + + " \"locale\": \"it\"}]"; + String actual = collator(literal(false), eq(literal(2), literal(1)), literal("it")).toString(); + assertEquals("expression should match", expected, actual); + } } \ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/ExpressionTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/ExpressionTest.java index f1f260c919..4e284cdc14 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/ExpressionTest.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/ExpressionTest.java @@ -2,20 +2,30 @@ package com.mapbox.mapboxsdk.testapp.style; import android.graphics.Color; import android.support.test.runner.AndroidJUnit4; + +import com.mapbox.geojson.Point; +import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.style.expressions.Expression; +import com.mapbox.mapboxsdk.style.layers.CircleLayer; import com.mapbox.mapboxsdk.style.layers.FillLayer; +import com.mapbox.mapboxsdk.style.layers.Layer; import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; import com.mapbox.mapboxsdk.style.sources.Source; import com.mapbox.mapboxsdk.testapp.R; import com.mapbox.mapboxsdk.testapp.activity.BaseActivityTest; import com.mapbox.mapboxsdk.testapp.activity.espresso.EspressoTestActivity; import com.mapbox.mapboxsdk.testapp.utils.ResourceUtils; +import com.mapbox.mapboxsdk.utils.ColorUtils; + import org.junit.Test; import org.junit.runner.RunWith; -import timber.log.Timber; import java.io.IOException; +import timber.log.Timber; + +import static com.mapbox.mapboxsdk.style.expressions.Expression.collator; +import static com.mapbox.mapboxsdk.style.expressions.Expression.eq; import static com.mapbox.mapboxsdk.style.expressions.Expression.exponential; import static com.mapbox.mapboxsdk.style.expressions.Expression.get; import static com.mapbox.mapboxsdk.style.expressions.Expression.interpolate; @@ -26,13 +36,16 @@ import static com.mapbox.mapboxsdk.style.expressions.Expression.rgba; import static com.mapbox.mapboxsdk.style.expressions.Expression.step; import static com.mapbox.mapboxsdk.style.expressions.Expression.stop; import static com.mapbox.mapboxsdk.style.expressions.Expression.string; +import static com.mapbox.mapboxsdk.style.expressions.Expression.switchCase; import static com.mapbox.mapboxsdk.style.expressions.Expression.toColor; import static com.mapbox.mapboxsdk.style.expressions.Expression.zoom; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleColor; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillAntialias; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillColor; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillOutlineColor; import static com.mapbox.mapboxsdk.testapp.action.MapboxMapAction.invoke; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; @RunWith(AndroidJUnit4.class) public class ExpressionTest extends BaseActivityTest { @@ -204,6 +217,54 @@ public class ExpressionTest extends BaseActivityTest { }); } + @Test + public void testCollatorExpression() { + validateTestSetup(); + setupStyle(); + invoke(mapboxMap, (uiController, mapboxMap) -> { + LatLng latLng = new LatLng(51, 17); + + Expression expression1 = eq(literal("Łukasz"), literal("lukasz"), collator(true, true)); + Expression expression2 = eq(literal("Łukasz"), literal("lukasz"), collator(literal(false), eq(literal(1), + literal(1)), literal("en"))); + Expression expression3 = eq(literal("Łukasz"), literal("lukasz"), collator(literal(false), eq(literal(2), + literal(1)))); + + mapboxMap.addSource(new GeoJsonSource("source", Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude()))); + Layer layer = new CircleLayer("layer", "source") + .withProperties(circleColor( + switchCase( + expression1, literal(ColorUtils.colorToRgbaString(Color.GREEN)), + literal(ColorUtils.colorToRgbaString(Color.RED)) + ) + )); + mapboxMap.addLayer(layer); + uiController.loopMainThreadForAtLeast(1000); + assertFalse(mapboxMap.queryRenderedFeatures(mapboxMap.getProjection().toScreenLocation(latLng), "layer") + .isEmpty()); + + layer.setProperties(circleColor( + switchCase( + expression2, literal(ColorUtils.colorToRgbaString(Color.GREEN)), + literal(ColorUtils.colorToRgbaString(Color.RED)) + ) + )); + uiController.loopMainThreadForAtLeast(1000); + assertFalse(mapboxMap.queryRenderedFeatures(mapboxMap.getProjection().toScreenLocation(latLng), "layer") + .isEmpty()); + + layer.setProperties(circleColor( + switchCase( + expression3, literal(ColorUtils.colorToRgbaString(Color.GREEN)), + literal(ColorUtils.colorToRgbaString(Color.RED)) + ) + )); + uiController.loopMainThreadForAtLeast(1000); + assertFalse(mapboxMap.queryRenderedFeatures(mapboxMap.getProjection().toScreenLocation(latLng), "layer") + .isEmpty()); + }); + } + private void setupStyle() { invoke(mapboxMap, (uiController, mapboxMap) -> { // Add a source -- cgit v1.2.1