Compare commits

..

3 Commits

Author SHA1 Message Date
jonah 5a5d1492a5 jq path wip 2024-01-24 17:40:42 +01:00
jonah 92a20c82f8 initial commit 2024-01-24 17:39:32 +01:00
jonah 375382f04e Initial commit 2024-01-24 17:29:50 +01:00
460 changed files with 1955 additions and 9513 deletions
-25
View File
@@ -1,27 +1,2 @@
# json
A simple JSON library with support for `StringTemplate`s which were available in JDK 21 and 22 as a preview
feature and removed again in JDK 23. The library allows placeholders to be used as keys, values and inside strings.
### Example
```java
var parameter = "numbers";
var value = List.of(1, 2, 3, 4);
var name = "World";
var json = JSON_OBJECT."""
{
\{parameter}: \{value},
"string": "Hello \{name}!"
}
""";
assertEquals(3, json.getArray("numbers").getNumber(2));
assertEquals(JsonString.valueOf("Hello World!"), json.get("string"));
System.out.println(json.toPrettyJsonString());
// {
// "numbers": [1, 2, 3, 4],
// "string": "Hello World!"
// }
```
+7 -5
View File
@@ -8,18 +8,20 @@ description = "json"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(22)
version = 21
}
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
api(libs.annotations)
compileOnly(libs.lombok)
annotationProcessor(libs.lombok)
testImplementation(libs.junit.jupiter)
testRuntimeOnly(libs.junit.platform.launcher)
testImplementation(libs.bundles.junit)
}
tasks {
@@ -1,409 +0,0 @@
package eu.jonahbauer.json;
import eu.jonahbauer.json.exceptions.JsonConversionException;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Java representation of a JSON array.
* @param elements the array's elements
*/
@SuppressWarnings("unused")
public record JsonArray(
@NotNull List<? extends @Nullable JsonValue> elements
) implements JsonValue, List<@Nullable JsonValue> {
public static final @NotNull JsonArray EMPTY = new JsonArray(List.of());
public JsonArray {
Objects.requireNonNull(elements, "elements");
elements = Util.defensiveCopy(elements);
}
public static @NotNull JsonArray of(@Nullable JsonValue @NotNull... elements) {
return new JsonArray(Arrays.stream(elements).toList());
}
//<editor-fold desc="valueOf(...)" defaultstate="collapsed">
/**
* Creates a JSON array from the given list, using {@link JsonValue#valueOf(Object)} to convert the objects to
* {@link JsonValue}s.
* @param list a list of objects
* @return a new JSON array
* @throws JsonConversionException if the given list or its entries cannot be converted to json
*/
@Contract("null -> null; !null -> !null")
public static @Nullable JsonArray valueOf(@Nullable List<?> list) {
if (list == null) return null;
if (list instanceof JsonArray json) return json;
return new JsonArray(list.stream().map(JsonValue::valueOf).toList());
}
/**
* Creates a new JSON array from the given array, using {@link JsonValue#valueOf(Object)} to convert the objects to
* {@link JsonValue}s.
* @param array an array of objects
* @return a new JSON array
* @throws JsonConversionException if the given array or its entries cannot be converted to json
*/
@Contract("null -> null; !null -> !null")
public static @Nullable JsonArray valueOf(@Nullable Object @Nullable... array) {
return array == null ? null : new JsonArray(Arrays.stream(array).map(JsonValue::valueOf).toList());
}
/**
* Creates a new JSON array from the given array, using {@link JsonBoolean#valueOf(boolean)} to convert the
* booleans to {@link JsonBoolean}s.
* @param array an array of booleans
* @return a new JSON array
*/
@Contract("null -> null; !null -> !null")
public static @Nullable JsonArray valueOf(boolean @Nullable... array) {
if (array == null) return null;
var list = new ArrayList<JsonValue>();
for (var b : array) {
list.add(JsonBoolean.valueOf(b));
}
return new JsonArray(Util.trusted(Collections.unmodifiableList(list)));
}
/**
* Creates a new JSON array from the given array, using {@link JsonNumber#valueOf(int)} to convert the
* bytes to {@link JsonNumber}s.
* @param array an array of bytes
* @return a new JSON array
*/
@Contract("null -> null; !null -> !null")
public static @Nullable JsonArray valueOf(byte @Nullable... array) {
if (array == null) return null;
var list = new ArrayList<JsonValue>();
for (var b : array) {
list.add(JsonNumber.valueOf(b));
}
return new JsonArray(Util.trusted(Collections.unmodifiableList(list)));
}
/**
* Creates a new JSON array from the given array, using {@link JsonNumber#valueOf(int)} to convert the
* shorts to {@link JsonNumber}s.
* @param array an array of shorts
* @return a new JSON array
*/
@Contract("null -> null; !null -> !null")
public static @Nullable JsonArray valueOf(short @Nullable... array) {
if (array == null) return null;
var list = new ArrayList<JsonValue>();
for (var s : array) {
list.add(JsonNumber.valueOf(s));
}
return new JsonArray(Util.trusted(Collections.unmodifiableList(list)));
}
/**
* Creates a new JSON array from the given array, using {@link JsonString#valueOf(char)} to convert the
* chars to {@link JsonString}s.
* @param array an array of chars
* @return a new JSON array
*/
@Contract("null -> null; !null -> !null")
public static @Nullable JsonArray valueOf(char @Nullable... array) {
if (array == null) return null;
var list = new ArrayList<JsonValue>();
for (var c : array) {
list.add(JsonString.valueOf(c));
}
return new JsonArray(Util.trusted(Collections.unmodifiableList(list)));
}
/**
* Creates a new JSON array from the given array, using {@link JsonNumber#valueOf(int)} to convert the
* ints to {@link JsonNumber}s.
* @param array an array of ints
* @return a new JSON array
*/
@Contract("null -> null; !null -> !null")
public static @Nullable JsonArray valueOf(int @Nullable... array) {
return array == null ? null : new JsonArray(Arrays.stream(array).mapToObj(JsonNumber::valueOf).toList());
}
/**
* Creates a new JSON array from the given array, using {@link JsonNumber#valueOf(long)} to convert the
* longs to {@link JsonNumber}s.
* @param array an array of longs
* @return a new JSON array
*/
@Contract("null -> null; !null -> !null")
public static @Nullable JsonArray valueOf(long @Nullable... array) {
return array == null ? null : new JsonArray(Arrays.stream(array).mapToObj(JsonNumber::valueOf).toList());
}
/**
* Creates a new JSON array from the given array, using {@link JsonNumber#valueOf(double)} to convert the
* floats to {@link JsonNumber}s.
* @param array an array of floats
* @return a new JSON array
*/
@Contract("null -> null; !null -> !null")
public static @Nullable JsonArray valueOf(float @Nullable... array) {
if (array == null) return null;
var list = new ArrayList<JsonValue>();
for (var f : array) {
list.add(JsonNumber.valueOf(f));
}
return new JsonArray(Util.trusted(Collections.unmodifiableList(list)));
}
/**
* Creates a new JSON array from the given array, using {@link JsonNumber#valueOf(double)} to convert the
* doubles to {@link JsonNumber}s.
* @param array an array of doubles
* @return a new JSON array
*/
@Contract("null -> null; !null -> !null")
public static @Nullable JsonArray valueOf(double @Nullable... array) {
return array == null ? null : new JsonArray(Arrays.stream(array).mapToObj(JsonNumber::valueOf).toList());
}
//</editor-fold>
//<editor-fold desc="List" defaultstate="collapsed">
@Override
public int size() {
return elements.size();
}
@Override
public boolean isEmpty() {
return elements.isEmpty();
}
@Override
public boolean contains(Object o) {
return elements.contains(o);
}
@Override
@SuppressWarnings("unchecked") // elements is immutable
public @NotNull Iterator<@Nullable JsonValue> iterator() {
return (Iterator<JsonValue>) elements.iterator();
}
@Override
public Object @NotNull[] toArray() {
return elements.toArray();
}
@Override
public <T> T @NotNull[] toArray(T @NotNull[] a) {
return elements.toArray(a);
}
@Override
public boolean add(@Nullable JsonValue value) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
@SuppressWarnings("SlowListContainsAll")
public boolean containsAll(@NotNull Collection<?> c) {
return elements.containsAll(c);
}
@Override
public boolean addAll(@NotNull Collection<? extends @Nullable JsonValue> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(int index, @NotNull Collection<? extends @Nullable JsonValue> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(@NotNull Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(@NotNull Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public @Nullable JsonValue get(int index) {
return elements.get(index);
}
@Override
public @Nullable JsonValue set(int index, @Nullable JsonValue element) {
throw new UnsupportedOperationException();
}
@Override
public void add(int index, @Nullable JsonValue element) {
throw new UnsupportedOperationException();
}
@Override
public @Nullable JsonValue remove(int index) {
throw new UnsupportedOperationException();
}
@Override
public int indexOf(Object o) {
return elements.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return elements.lastIndexOf(o);
}
@Override
@SuppressWarnings("unchecked") // elements is immutable
public @NotNull ListIterator<@Nullable JsonValue> listIterator() {
return (ListIterator<JsonValue>) elements.listIterator();
}
@Override
@SuppressWarnings("unchecked") // elements is immutable
public @NotNull ListIterator<@Nullable JsonValue> listIterator(int index) {
return (ListIterator<JsonValue>) elements.listIterator(index);
}
@Override
public @NotNull JsonArray subList(int fromIndex, int toIndex) {
return new JsonArray(elements.subList(fromIndex, toIndex));
}
@Override
public @NotNull JsonArray reversed() {
return new JsonArray(Util.trusted(elements.reversed()));
}
//</editor-fold>
//<editor-fold desc="get...(int index)" defaultstate="collapsed">
/**
* {@return the element at the specified index in this array}
* @param index index of the element to return
* @throws ClassCastException if the element at the specified index is not an instance of {@link JsonString}
* @throws NullPointerException if the element at the specified index is {@code null}
* @throws IndexOutOfBoundsException if the index is less than zero or greater than or equals to the size of the array
*/
public @NotNull JsonString getString(int index) {
return get(index, JsonString.class);
}
/**
* {@return the element at the specified index in this array}
* @param index index of the element to return
* @throws ClassCastException if the element at the specified index is not an instance of {@link JsonNumber}
* @throws NullPointerException if the element at the specified index is {@code null}
* @throws IndexOutOfBoundsException if the index is less than zero or greater than or equals to the size of the array
*/
public @NotNull JsonNumber getNumber(int index) {
return get(index, JsonNumber.class);
}
/**
* {@return the element at the specified index in this array}
* @param index index of the element to return
* @throws ClassCastException if the element at the specified index is not an instance of {@link JsonBoolean}
* @throws NullPointerException if the element at the specified index is {@code null}
* @throws IndexOutOfBoundsException if the index is less than zero or greater than or equals to the size of the array
*/
public @NotNull JsonBoolean getBoolean(int index) {
return get(index, JsonBoolean.class);
}
/**
* {@return the element at the specified index in this array}
* @param index index of the element to return
* @throws ClassCastException if the element at the specified index is not an instance of {@link JsonArray}
* @throws NullPointerException if the element at the specified index is {@code null}
* @throws IndexOutOfBoundsException if the index is less than zero or greater than or equals to the size of the array
*/
public @NotNull JsonArray getArray(int index) {
return get(index, JsonArray.class);
}
/**
* {@return the element at the specified index in this array}
* @param index index of the element to return
* @throws ClassCastException if the element at the specified index is not an instance of {@link JsonObject}
* @throws NullPointerException if the element at the specified index is {@code null}
* @throws IndexOutOfBoundsException if the index is less than zero or greater than or equals to the size of the array
*/
public @NotNull JsonObject getObject(int index) {
return get(index, JsonObject.class);
}
private <T extends JsonValue> @NotNull T get(int index, @NotNull Class<T> type) {
var value = elements().get(index);
if (value == null) {
throw new NullPointerException("Value at index" + index + " is null.");
} else {
return type.cast(value);
}
}
//</editor-fold>
@Override
public @NotNull String toString() {
var joiner = new StringJoiner(",", "[", "]");
for (JsonValue element : elements) {
joiner.add(JsonValue.toJsonString(element));
}
return joiner.toString();
}
@Override
public @NotNull String toPrettyJsonString(int depth) {
if (size() < 2) return toJsonString();
var entries = new ArrayList<String>(elements.size());
var length = 0L;
var composite = false;
for (var element : elements) {
var string = JsonValue.toPrettyJsonString(element, depth + Util.PRETTY_PRINT_INDENT.length());
entries.add(string);
composite |= element instanceof JsonObject || element instanceof JsonArray;
length += string.length() + 2; // +2 to account for ", " and "[]"
}
if (!composite && depth + length <= Util.PRETTY_PRINT_ARRAY_MULTILINE_THRESHOLD) {
return "[" + String.join(", ", entries) + "]";
} else {
var out = new StringJoiner(",\n", "[\n", "\n]");
entries.forEach(e -> out.add(indent(e)));
return out.toString();
}
}
private static @NotNull String indent(@NotNull String string) {
if (string.isEmpty()) return "";
StringBuilder out = new StringBuilder();
Iterator<String> it = string.lines().iterator();
while (it.hasNext()) {
out.append(Util.PRETTY_PRINT_INDENT).append(it.next());
if (it.hasNext()) out.append('\n');
}
return out.toString();
}
}
@@ -1,63 +0,0 @@
package eu.jonahbauer.json;
import eu.jonahbauer.json.tokenizer.token.JsonToken;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Java representation of a JSON boolean. Note, that JSON does not distinguish between integers and floating point
* numbers and therefore all numbers are stored as {@code double}.
*/
public record JsonNumber(double value) implements JsonValue, JsonToken {
public static final @NotNull JsonNumber ZERO = new JsonNumber(0);
public static final @NotNull JsonNumber ONE = new JsonNumber(1);
/**
* Converts the given int to a JSON number.
* @param i an int
* @return a new JSON number
*/
public static @NotNull JsonNumber valueOf(int i) {
return new JsonNumber(i);
}
/**
* Converts the given long to a JSON number.
* @param l a long
* @return a new JSON number
* @throws IllegalArgumentException if conversion from {@code long} to {@code double} is lossy
*/
public static @NotNull JsonNumber valueOf(long l) {
if ((long) (double) l != l) {
throw new IllegalArgumentException("lossy conversion from long to double for value " + l);
}
return new JsonNumber(l);
}
/**
* Converts the given double to a JSON number.
* @param d a double
* @return a new JSON number
* @throws IllegalArgumentException if {@code d} is not {@linkplain Double#isFinite(double) finite}
*/
public static @NotNull JsonNumber valueOf(double d) {
return new JsonNumber(d);
}
/**
* Converts the given number to a JSON number using {@link Number#doubleValue()}. The conversion to a JSON number
* might be lossy.
* @param number a number
* @return a new JSON number
*/
@Contract("null -> null; !null -> !null")
public static @Nullable JsonNumber valueOf(@Nullable Number number) {
return number == null ? null : new JsonNumber(number.doubleValue());
}
@Override
public @NotNull String toString() {
return (long) value == value ? Long.toString((long) value) : Double.toString(value);
}
}
@@ -1,382 +0,0 @@
package eu.jonahbauer.json;
import eu.jonahbauer.json.exceptions.JsonConversionException;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Function;
/**
* Java representation of a JSON object.
* @param entries the object's entries
*/
@SuppressWarnings("unused")
public record JsonObject(@NotNull Map<@NotNull String, @Nullable JsonValue> entries) implements JsonValue, Map<@NotNull String, @Nullable JsonValue> {
public static final @NotNull JsonObject EMPTY = new JsonObject(Map.of());
public JsonObject {
Objects.requireNonNull(entries, "entries");
entries = Util.defensiveCopy(entries);
}
/**
* Returns a JSON object containing one mapping.
* @param k1 the key
* @param v1 the value
* @return a new JSON object containing the specified mapping
*/
public static @NotNull JsonObject of(
@NotNull String k1, @Nullable JsonValue v1
) {
var map = new LinkedHashMap<String, JsonValue>();
map.put(Objects.requireNonNull(k1), v1);
return new JsonObject(Util.trusted(Collections.unmodifiableSequencedMap(map)));
}
/**
* Returns a JSON object containing two mapping.
* @param k1 the first key
* @param v1 the first value
* @param k2 the second key
* @param v2 the second value
* @return a new JSON object containing the specified mappings
*/
public static @NotNull JsonObject of(
@NotNull String k1, @Nullable JsonValue v1,
@NotNull String k2, @Nullable JsonValue v2
) {
var map = new LinkedHashMap<String, JsonValue>();
map.put(Objects.requireNonNull(k1), v1);
map.put(Objects.requireNonNull(k2), v2);
return new JsonObject(Util.trusted(Collections.unmodifiableSequencedMap(map)));
}
/**
* Returns a JSON object containing three mapping.
* @param k1 the first key
* @param v1 the first value
* @param k2 the second key
* @param v2 the second value
* @param k3 the third key
* @param v3 the third value
* @return a new JSON object containing the specified mappings
*/
public static @NotNull JsonObject of(
@NotNull String k1, @Nullable JsonValue v1,
@NotNull String k2, @Nullable JsonValue v2,
@NotNull String k3, @Nullable JsonValue v3
) {
var map = new LinkedHashMap<String, JsonValue>();
map.put(Objects.requireNonNull(k1), v1);
map.put(Objects.requireNonNull(k2), v2);
map.put(Objects.requireNonNull(k3), v3);
return new JsonObject(Util.trusted(Collections.unmodifiableSequencedMap(map)));
}
/**
* Returns a JSON object containing four mapping.
* @param k1 the first key
* @param v1 the first value
* @param k2 the second key
* @param v2 the second value
* @param k3 the third key
* @param v3 the third value
* @param k4 the fourth key
* @param v4 the fourth value
* @return a new JSON object containing the specified mappings
*/
public static @NotNull JsonObject of(
@NotNull String k1, @Nullable JsonValue v1,
@NotNull String k2, @Nullable JsonValue v2,
@NotNull String k3, @Nullable JsonValue v3,
@NotNull String k4, @Nullable JsonValue v4
) {
var map = new LinkedHashMap<String, JsonValue>();
map.put(Objects.requireNonNull(k1), v1);
map.put(Objects.requireNonNull(k2), v2);
map.put(Objects.requireNonNull(k3), v3);
map.put(Objects.requireNonNull(k4), v4);
return new JsonObject(Util.trusted(Collections.unmodifiableSequencedMap(map)));
}
/**
* Creates a JSON object from the given map, using {@link JsonValue#valueOf(Object)} to convert the values to
* {@link JsonValue}s. The keys are expected to be instances of {@link CharSequence}.
* @param map a map
* @return a new JSON object
* @throws JsonConversionException if the given map or its entries cannot be converted to json
*/
@Contract("null -> null; !null -> !null")
public static @Nullable JsonObject valueOf(@Nullable Map<?, ?> map) {
if (map == null) return null;
if (map instanceof JsonObject json) return json;
var out = new LinkedHashMap<String, JsonValue>();
map.forEach((key, value) -> out.put(keyOf(key), JsonValue.valueOf(value)));
return new JsonObject(out);
}
private static @NotNull String keyOf(@Nullable Object object) {
return switch (object) {
case CharSequence chars -> chars.toString();
case null -> throw new JsonConversionException("cannot use null as json key");
default -> throw new JsonConversionException("cannot convert object of type " + object.getClass() + " to json key");
};
}
//<editor-fold desc="Map" defaultstate="collapsed">
@Override
public int size() {
return entries.size();
}
@Override
public boolean isEmpty() {
return entries.isEmpty();
}
@Override
public boolean containsKey(Object key) {
Objects.requireNonNull(key, "key");
return entries.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return entries.containsValue(value);
}
@Override
public @Nullable JsonValue get(Object key) {
Objects.requireNonNull(key, "key");
return entries.get(key);
}
@Override
public @Nullable JsonValue put(@NotNull String key, @Nullable JsonValue value) {
throw new UnsupportedOperationException();
}
@Override
public @Nullable JsonValue remove(Object key) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(@NotNull Map<? extends @NotNull String, ? extends @Nullable JsonValue> m) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public @NotNull Set<@NotNull String> keySet() {
return entries.keySet();
}
@Override
public @NotNull Collection<@Nullable JsonValue> values() {
return entries.values();
}
@Override
public @NotNull Set<Entry<@NotNull String, @Nullable JsonValue>> entrySet() {
return entries.entrySet();
}
//</editor-fold>
//<editor-fold desc="get...(String key)" defaultstate="collapsed">
/**
* {@return the value to which the specified key is mapped, or <code>null</code> if this object contains no mapping for the key}
* @throws ClassCastException if the value is not an instance of {@link JsonString}
* @throws NullPointerException if there is no mapping for the key or the key is mapped to {@code null}
*/
public @NotNull JsonString getString(@NotNull String key) {
return get(key, JsonString.class);
}
/**
* {@return the value to which the specified key is mapped, or <code>null</code> if this object contains no mapping for the key}
* @throws ClassCastException if the value is not an instance of {@link JsonNumber}
* @throws NullPointerException if there is no mapping for the key or the key is mapped to {@code null}
*/
public @NotNull JsonNumber getNumber(@NotNull String key) {
return get(key, JsonNumber.class);
}
/**
* {@return the value to which the specified key is mapped, or <code>null</code> if this object contains no mapping for the key}
* @throws ClassCastException if the value is not an instance of {@link JsonBoolean}
* @throws NullPointerException if there is no mapping for the key or the key is mapped to {@code null}
*/
public @NotNull JsonBoolean getBoolean(@NotNull String key) {
return get(key, JsonBoolean.class);
}
/**
* {@return the value to which the specified key is mapped, or <code>null</code> if this object contains no mapping for the key}
* @throws ClassCastException if the value is not an instance of {@link JsonArray}
* @throws NullPointerException if there is no mapping for the key or the key is mapped to {@code null}
*/
public @NotNull JsonArray getArray(@NotNull String key) {
return get(key, JsonArray.class);
}
/**
* {@return the value to which the specified key is mapped, or <code>null</code> if this object contains no mapping for the key}
* @throws ClassCastException if the value is not an instance of {@link JsonObject}
* @throws NullPointerException if there is no mapping for the key or the key is mapped to {@code null}
*/
public @NotNull JsonObject getObject(@NotNull String key) {
return get(key, JsonObject.class);
}
private <T extends JsonValue> @NotNull T get(@NotNull String key, @NotNull Class<T> type) {
var value = entries().get(key);
if (value == null) {
if (entries().containsKey(key)) {
throw new NullPointerException("Key " + key + " is mapped to null.");
} else {
throw new NullPointerException("No mapping for key " + key);
}
} else {
return type.cast(value);
}
}
//</editor-fold>
//<editor-fold desc="get...OrDefault(String key, ...)" defaultstate="collapsed">
// getStringOrDefault(String, JsonString) omitted because JsonString implements CharSequence
/**
* Returns the value to which the specified key is mapped, or
* {@link JsonString#valueOf(CharSequence) JsonString.valueOf(defaultValue)} if this object contains no mapping for
* the key or the key is mapped to a value that is not an instance of {@link JsonString}.
* @return the value to which the specified key is mapped falling back to the default value
*/
public @Nullable JsonString getStringOrDefault(@NotNull String key, @Nullable CharSequence defaultValue) {
return getOrDefault(key, defaultValue, JsonString.class, JsonString::valueOf);
}
/**
* Returns the value to which the specified key is mapped, or {@code defaultValue} if this object contains no mapping
* for the key or the key is mapped to a value that is not an instance of {@link JsonNumber}.
* @return the value to which the specified key is mapped falling back to the default value
*/
public @Nullable JsonNumber getNumberOrDefault(@NotNull String key, @Nullable JsonNumber defaultValue) {
return getOrDefault(key, defaultValue, JsonNumber.class, Function.identity());
}
/**
* Returns the value to which the specified key is mapped, or
* {@link JsonNumber#valueOf(Number) JsonNumber.valueOf(defaultValue)} if this object contains no mapping for the
* key or the key is mapped to a value that is not an instance of {@link JsonNumber}.
* @return the value to which the specified key is mapped falling back to the default value
*/
public @Nullable JsonNumber getNumberOrDefault(@NotNull String key, @Nullable Number defaultValue) {
return getOrDefault(key, defaultValue, JsonNumber.class, JsonNumber::valueOf);
}
/**
* Returns the value to which the specified key is mapped, or {@code defaultValue} if this object contains no mapping
* for the key or the key is mapped to a value that is not an instance of {@link JsonBoolean}.
* @return the value to which the specified key is mapped falling back to the default value
*/
public @Nullable JsonBoolean getBooleanOrDefault(@NotNull String key, @Nullable JsonBoolean defaultValue) {
return getOrDefault(key, defaultValue, JsonBoolean.class, Function.identity());
}
/**
* Returns the value to which the specified key is mapped, or
* {@link JsonBoolean#valueOf(Boolean) JsonBoolean.valueOf(defaultValue)} if this object contains no mapping
* for the key or the key is mapped to a value that is not an instance of {@link JsonBoolean}.
* @return the value to which the specified key is mapped falling back to the default value
*/
public @Nullable JsonBoolean getBooleanOrDefault(@NotNull String key, @Nullable Boolean defaultValue) {
return getOrDefault(key, defaultValue, JsonBoolean.class, JsonBoolean::valueOf);
}
// getArrayOrDefault(String, JsonArray) omitted because JsonArray implements List<?>
/**
* Returns the value to which the specified key is mapped, or
* {@link JsonArray#valueOf(List) JsonArray.valueOf(defaultValue)} if this object contains no mapping for
* the key or the key is mapped to a value that is not an instance of {@link JsonArray}.
* @return the value to which the specified key is mapped falling back to the default value
*/
public @Nullable JsonArray getArrayOrDefault(@NotNull String key, @Nullable List<?> defaultValue) {
return getOrDefault(key, defaultValue, JsonArray.class, JsonArray::valueOf);
}
/**
* Returns the value to which the specified key is mapped, or
* {@link JsonArray#valueOf(Object...) JsonArray.valueOf(defaultValue)} if this object contains no mapping for the
* key or the key is mapped to a value that is not an instance of {@link JsonArray}.
* @return the value to which the specified key is mapped falling back to the default value
*/
public @Nullable JsonArray getArrayOrDefault(@NotNull String key, @Nullable Object @Nullable... defaultValue) {
return getOrDefault(key, defaultValue, JsonArray.class, JsonArray::valueOf);
}
// getObjectOrDefault(String, JsonObject) omitted because JsonObject implements Map<?, ?>
/**
* Returns the value to which the specified key is mapped, or
* {@link JsonObject#valueOf(Map) JsonObject.valueOf(defaultValue)} if this object contains no mapping for the key
* or the key is mapped to a value that is not an instance of {@link JsonObject}.
* @return the value to which the specified key is mapped falling back to the default value
*/
public @Nullable JsonObject getObjectOrDefault(@NotNull String key, @Nullable Map<?, ?> defaultValue) {
return getOrDefault(key, defaultValue, JsonObject.class, JsonObject::valueOf);
}
private <T extends JsonValue, S> @Nullable T getOrDefault(@NotNull String key, @Nullable S defaultValue, @NotNull Class<T> type, @NotNull Function<S, T> converter) {
var value = entries().get(key);
if (type.isInstance(value)) {
return type.cast(value);
} else {
return converter.apply(defaultValue);
}
}
//</editor-fold>
@Override
public @NotNull String toString() {
StringJoiner out = new StringJoiner(",", "{", "}");
entries.forEach((key, value) -> out.add(JsonString.quote(key) + ":" + JsonValue.toJsonString(value)));
return out.toString();
}
@Override
public @NotNull String toPrettyJsonString(int depth) {
StringJoiner out = new StringJoiner(",\n" + Util.PRETTY_PRINT_INDENT, "{\n" + Util.PRETTY_PRINT_INDENT, "\n}");
entries.forEach((key, value) -> {
var keyString = JsonString.quote(key);
var valueIdent = depth + Util.PRETTY_PRINT_INDENT.length() + keyString.length() + 3; // +3 for ": " and ","
var valueString = JsonValue.toPrettyJsonString(value, valueIdent);
out.add(keyString + ": " + indent(valueString));
});
return out.toString();
}
private static @NotNull String indent(@NotNull String string) {
if (string.isEmpty()) return "";
StringBuilder out = new StringBuilder();
Iterator<String> it = string.lines().iterator();
out.append(it.next());
while (it.hasNext()) {
String next = it.next();
out.append('\n').append(Util.PRETTY_PRINT_INDENT).append(next);
}
return out.toString();
}
}
@@ -1,37 +0,0 @@
package eu.jonahbauer.json;
import eu.jonahbauer.json.exceptions.JsonConversionException;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
@SuppressWarnings("preview")
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public final class JsonTemplateProcessor<T extends JsonValue> implements StringTemplate.Processor<T, RuntimeException> {
/**
* Parses the string template as a JSON using {@link JsonValue#parse(StringTemplate)}.
*/
public static final @NotNull JsonTemplateProcessor<JsonValue> JSON = new JsonTemplateProcessor<>(JsonValue.class, true);
/**
* Parses the string template as a JSON using {@link JsonValue#parse(StringTemplate)}. Throws a
* {@link JsonConversionException} if the result is not a JSON object.
*/
public static final @NotNull JsonTemplateProcessor<@NotNull JsonObject> JSON_OBJECT = new JsonTemplateProcessor<>(JsonObject.class, false);
/**
* Parses the string template as a JSON using {@link JsonValue#parse(StringTemplate)}. Throws a
* {@link JsonConversionException} if the result is not a JSON array.
*/
public static final @NotNull JsonTemplateProcessor<@NotNull JsonArray> JSON_ARRAY = new JsonTemplateProcessor<>(JsonArray.class, false);
private final @NotNull Class<T> clazz;
private final boolean allowNull;
@Override
public T process(@NotNull StringTemplate template) throws RuntimeException {
var result = JsonValue.parse(template);
if (result == null && !allowNull || result != null && !clazz.isInstance(result)) {
throw new JsonConversionException("The template does not represent a " + clazz.getSimpleName());
}
return clazz.cast(result);
}
}
@@ -1,146 +0,0 @@
package eu.jonahbauer.json;
import eu.jonahbauer.json.exceptions.JsonConversionException;
import eu.jonahbauer.json.exceptions.JsonParserException;
import eu.jonahbauer.json.parser.JsonParser;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public sealed interface JsonValue extends Serializable permits JsonObject, JsonArray, JsonBoolean, JsonNumber, JsonString {
/**
* {@return the JSON representation of this value}
*/
default @NotNull String toJsonString() {
return toString();
}
/**
* {@return a prettified JSON representation of this value}
*/
default @NotNull String toPrettyJsonString() {
return toPrettyJsonString(0);
}
/**
* {@return a prettified JSON representation of this value}
* @param depth indicates the depth at which this value appears (no formal definition given)
*/
default @NotNull String toPrettyJsonString(int depth) {
return toJsonString();
}
/**
* {@return the JSON representation of the given value}
* @param value a {@link JsonValue} (possibly {@code null})
*/
static @NotNull String toJsonString(@Nullable JsonValue value) {
return value == null ? "null" : value.toJsonString();
}
/**
* {@return the prettified JSON representation of the given value}
* @param value a {@link JsonValue} (possibly {@code null})
*/
static @NotNull String toPrettyJsonString(@Nullable JsonValue value) {
return toPrettyJsonString(value, 0);
}
/**
* {@return the prettified JSON representation of the given value}
* @param value a {@link JsonValue} (possibly {@code null})
* @param depth indicates the depth at which the value appears (no formal definition given)
*/
static @NotNull String toPrettyJsonString(@Nullable JsonValue value, int depth) {
return value == null ? "null" : value.toPrettyJsonString(depth);
}
/**
* Parses a string as a JSON. The string must adhere to the <a href="https://www.json.org">JSON grammar</a>.
* @param json a string
* @return the result of parsing the string as a JSON
* @throws JsonParserException if the string cannot be parsed as JSON
*/
static @Nullable JsonValue parse(@NotNull String json) {
var parser = new JsonParser(json);
return parser.parse();
}
/**
* Parses a string template as a JSON. The string template must adhere to the
* <a href="https://www.json.org">standard JSON grammar</a> with the following additional rules:
* <pre>{@code
* value
* placeholder
*
* member
* ws placeholder ws ':' element
*
* characters
* placeholder characters
* }</pre>
* where <code>placeholder</code> represents an object embedded in the string template.
* <p>
* A placeholder will be converted to JSON depending on the context it appears in.
* When used as a value, {@link JsonValue#valueOf(Object)} will be used.
* When used as an object key, only instances or {@link CharSequence} are allowed, which will be converted using
* {@link CharSequence#toString()}.
* When used in a string, {@link Objects#toString(Object)} will be used.
*
* @param json a string template
* @return the result of parsing the string template as a JSON
* @throws JsonParserException if the string template cannot be parsed as JSON
* @throws JsonConversionException if a placeholder cannot be converted to JSON
*/
@SuppressWarnings("preview")
static @Nullable JsonValue parse(@NotNull StringTemplate json) {
var parser = new JsonParser(json);
return parser.parse();
}
/**
* Converts an object to a JSON value:
* <ul>
* <li>{@link JsonValue}s and {@code null} are returned as is</li>
* <li>{@link Boolean}s are converted with {@link JsonBoolean#valueOf(Boolean)}</li>
* <li>{@link Number}s are converted with {@link JsonNumber#valueOf(Number)}</li>
* <li>{@link Character}s are converted with {@link JsonString#valueOf(Character)}</li>
* <li>{@link CharSequence}s are converted with {@link JsonString#valueOf(CharSequence)}</li>
* <li>{@link List}s are converted with {@link JsonArray#valueOf(List)}</li>
* <li>arrays are converted with the respective overload of {@link JsonArray#valueOf(Object...)}</li>
* <li>{@link Map}s are converted with {@link JsonObject#valueOf(Map)}</li>
* </ul>
* @param object an object
* @return a {@link JsonValue} representing the object
* @throws JsonConversionException if the object cannot be converted to a {@link JsonValue}
*/
@Contract("null -> null; !null -> !null")
static @Nullable JsonValue valueOf(@Nullable Object object) {
return switch (object) {
case JsonValue json -> json;
case Boolean bool -> JsonBoolean.valueOf(bool);
case Number number -> JsonNumber.valueOf(number);
case Character chr -> JsonString.valueOf(String.valueOf(chr));
case CharSequence chars -> JsonString.valueOf(chars);
case List<?> list -> JsonArray.valueOf(list);
case Object[] array -> JsonArray.valueOf(array);
case boolean[] array -> JsonArray.valueOf(array);
case byte[] array -> JsonArray.valueOf(array);
case short[] array -> JsonArray.valueOf(array);
case char[] array -> JsonArray.valueOf(array);
case int[] array -> JsonArray.valueOf(array);
case long[] array -> JsonArray.valueOf(array);
case float[] array -> JsonArray.valueOf(array);
case double[] array -> JsonArray.valueOf(array);
case Map<?, ?> map -> JsonObject.valueOf(map);
case null -> null;
default -> throw new JsonConversionException("cannot convert object of type " + object.getClass() + " to json value");
};
}
}
@@ -1,72 +0,0 @@
package eu.jonahbauer.json;
import lombok.experimental.Delegate;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import java.util.*;
@UtilityClass
class Util {
static final @NotNull String PRETTY_PRINT_INDENT = " ";
static final int PRETTY_PRINT_ARRAY_MULTILINE_THRESHOLD = 80;
private static final @NotNull Class<?> LIST_1_2 = List.of(1).getClass();
private static final @NotNull Class<?> LIST_N = List.of(1, 2, 3).getClass();
private static final @NotNull Class<?> SUB_LIST = List.of(1, 2, 3).subList(0, 2).getClass();
private static final @NotNull Class<?> MAP_1 = Map.of(1, 2).getClass();
private static final @NotNull Class<?> MAP_N = Map.of(1, 2, 3, 4).getClass();
/**
* Performs a defensive copy of the given list unless it is known to be an immutable list.
*/
public static <T> @NotNull List<T> defensiveCopy(@NotNull List<T> list) {
if (LIST_1_2.isInstance(list) || LIST_N.isInstance(list) || SUB_LIST.isInstance(list)) {
return list;
} else if (list instanceof TrustedList<T>(var delegate)) {
return delegate;
} else {
return list.stream().toList();
}
}
/**
* Performs a defensive copy of the given map unless it is known to be an immutable map.
* If the given map implements {@link SequencedMap} as will the returned one.
*/
public static <K, V> @NotNull Map<K, V> defensiveCopy(@NotNull Map<@NotNull K, V> map) {
if (MAP_1.isInstance(map) || MAP_N.isInstance(map)) {
return map;
} else if (map instanceof TrustedMap<K, V>(var delegate)) {
return delegate;
} else if (map instanceof SequencedMap<K,V> sequenced) {
var out = new LinkedHashMap<K, V>();
sequenced.forEach((key, value) -> out.put(Objects.requireNonNull(key, "key"), value));
return Collections.unmodifiableSequencedMap(out);
} else {
var out = new HashMap<K, V>();
map.forEach((key, value) -> out.put(Objects.requireNonNull(key, "key"), value));
return Collections.unmodifiableMap(out);
}
}
/**
* Returns a list that will be assumed immutable when given to {@link #defensiveCopy(List)}.
* {@return a list that will be assumed immutable}
*/
public static <T> @NotNull List<T> trusted(@NotNull List<T> list) {
return new TrustedList<>(list);
}
/**
* Returns a map that will be assumed immutable when given to {@link #defensiveCopy(Map)}.
* {@return a map that will be assumed immutable}
*/
public static <K, V> @NotNull Map<K, V> trusted(@NotNull Map<@NotNull K, V> map) {
return new TrustedMap<>(map);
}
private record TrustedList<T>(@Delegate @NotNull List<T> delegate) implements List<T> {}
private record TrustedMap<K, V>(@Delegate @NotNull Map<K, V> delegate) implements Map<K, V> {}
}
@@ -1,14 +0,0 @@
package eu.jonahbauer.json.exceptions;
import org.jetbrains.annotations.NotNull;
public class JsonParserException extends JsonReaderException {
public JsonParserException(int fragment, int line, int column, @NotNull String message) {
super(fragment, line, column, message);
}
public JsonParserException(int fragment, int line, int column, @NotNull String message, @NotNull Throwable cause) {
super(fragment, line, column, message, cause);
}
}
@@ -1,14 +0,0 @@
package eu.jonahbauer.json.exceptions;
/**
* Parent class for exceptions related to json processing.
*/
public abstract class JsonProcessingException extends RuntimeException {
public JsonProcessingException(String message) {
super(message);
}
public JsonProcessingException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -1,34 +0,0 @@
package eu.jonahbauer.json.exceptions;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
@Getter
abstract class JsonReaderException extends JsonProcessingException {
private final int fragment;
private final int lineNumber;
private final int columnNumber;
public JsonReaderException(int fragment, int line, int column, @NotNull String message) {
super(message);
this.fragment = fragment;
this.lineNumber = line;
this.columnNumber = column;
}
public JsonReaderException(int fragment, int line, int column, @NotNull String message, @NotNull Throwable cause) {
super(message, cause);
this.fragment = fragment;
this.lineNumber = line;
this.columnNumber = column;
}
@Override
public @NotNull String getMessage() {
if (getFragment() == 0) {
return super.getMessage() + " at line " + getLineNumber() + ", column " + getColumnNumber();
} else {
return super.getMessage() + " at fragment " + getFragment() + ", line " + getLineNumber() + ", column " + getColumnNumber();
}
}
}
@@ -1,273 +0,0 @@
package eu.jonahbauer.json.parser;
import eu.jonahbauer.json.*;
import eu.jonahbauer.json.exceptions.JsonConversionException;
import eu.jonahbauer.json.exceptions.JsonParserException;
import eu.jonahbauer.json.exceptions.JsonTokenizerException;
import eu.jonahbauer.json.tokenizer.JsonTokenizer;
import eu.jonahbauer.json.tokenizer.JsonTokenizerImpl;
import eu.jonahbauer.json.tokenizer.token.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.util.*;
public final class JsonParser {
private static final String TT_JSON_STRING = "JSON_STRING";
private static final String TT_JSON_NUMBER = "JSON_NUMBER";
private static final String TT_JSON_STRING_TEMPLATE = "JSON_STRING_TEMPLATE";
private static final String TT_JSON_PLACEHOLDER = "JSON_PLACEHOLDER";
private final @NotNull JsonTokenizer tokenizer;
@SuppressWarnings("preview")
public JsonParser(@NotNull StringTemplate json) {
this(new JsonTokenizerImpl(json));
}
public JsonParser(@NotNull String json) {
this(new JsonTokenizerImpl(json));
}
public JsonParser(@NotNull Reader reader) {
this(new JsonTokenizerImpl(reader));
}
public JsonParser(@NotNull JsonTokenizer tokenizer) {
this.tokenizer = Objects.requireNonNull(tokenizer);
}
/**
* Parses the input as a JSON value.
* @return the JSON value
* @throws JsonParserException if the input could not be parsed as JSON
* @throws UncheckedIOException if an I/O error occurs
*/
public @Nullable JsonValue parse() throws JsonParserException {
try {
var out = parse0();
if (tokenizer.next() != null) {
throw new JsonParserException(tokenizer.getFragment(), tokenizer.getLineNumber(), tokenizer.getColumnNumber(), "unexpected trailing entries");
}
return out;
} catch (IOException ex) {
throw new UncheckedIOException(ex);
} catch (JsonTokenizerException ex) {
throw new JsonParserException(ex.getFragment(), ex.getLineNumber(), ex.getColumnNumber(), ex.getMessage(), ex);
}
}
private @Nullable JsonValue parse0() throws IOException {
// Use a stack of Context objects to store the objects and arrays which are currently being parsed.
// This avoids the risk of stack overflow which would exist when having one nested calls to parse0.
var stack = new ArrayDeque<@NotNull Context>();
while (true) {
JsonValue value;
var token = tokenizer.next();
if (!stack.isEmpty() && token == stack.peek().end()) {
value = stack.pop().build();
} else {
// consume value separator
if (stack.peek() instanceof Context context && !context.isEmpty()) {
if (token != JsonPunctuation.VALUE_SEPARATOR) {
throw unexpectedToken(token, context.end(), JsonPunctuation.VALUE_SEPARATOR);
}
token = tokenizer.next();
}
// read object key and consume name separator
if (stack.peek() instanceof ObjectContext object) {
var key = parseJsonObjectKey(object, token);
if ((token = tokenizer.next()) != JsonPunctuation.NAME_SEPARATOR) {
throw unexpectedToken(token, JsonPunctuation.NAME_SEPARATOR);
}
token = tokenizer.next();
object.setKey(key);
}
if (token == JsonPunctuation.BEGIN_OBJECT) {
stack.push(new ObjectContext());
continue;
} else if (token == JsonPunctuation.BEGIN_ARRAY) {
stack.push(new ArrayContext());
continue;
} else {
// read json value
value = switch (token) {
case JsonPunctuation.BEGIN_ARRAY, JsonPunctuation.BEGIN_OBJECT -> throw new AssertionError();
case JsonNull.NULL -> null;
case JsonValue v -> v;
case JsonStringTemplate template -> template.asString();
case JsonPlaceholder(var object) -> JsonValue.valueOf(object);
case JsonPunctuation _ -> throw unexpectedStartOfValue(token);
case null -> throw unexpectedEndOfFile();
};
}
}
if (stack.peek() instanceof Context context) {
context.add(value);
} else {
return value;
}
}
}
private @NotNull String parseJsonObjectKey(@NotNull ObjectContext context, @Nullable JsonToken token) {
return switch (token) {
case JsonString string -> string.value();
case JsonStringTemplate template -> template.asString().value();
case JsonPlaceholder(var object) -> switch (object) {
case CharSequence chars -> chars.toString();
case null -> throw new JsonConversionException("cannot use null as json object key");
default -> throw new JsonConversionException("cannot convert object of type " + object.getClass() + " to json object key");
};
case null -> throw unexpectedEndOfFile();
default -> throw unexpectedStartOfKey(context, token);
};
}
private @NotNull JsonParserException unexpectedStartOfKey(@NotNull ObjectContext context, @Nullable JsonToken token) {
var expected = new ArrayList<>(4);
expected.add(TT_JSON_STRING);
if (context.isEmpty()) {
// if the context is not empty, we have already consumed a VALUE_SEPARATOR(,) and standard JSON does not
// allow trailing commas
expected.add(JsonPunctuation.END_OBJECT);
}
if (tokenizer.getFragment() != 0) {
// when the input is a StringTemplate, additional tokens are allowed
expected.add(TT_JSON_STRING_TEMPLATE);
expected.add(TT_JSON_PLACEHOLDER);
}
return unexpectedToken(token, expected.toArray());
}
private @NotNull JsonParserException unexpectedStartOfValue(@Nullable JsonToken token) {
var expected = new ArrayList<>(9);
expected.addAll(Arrays.asList(
JsonPunctuation.BEGIN_OBJECT, JsonPunctuation.BEGIN_ARRAY, JsonBoolean.TRUE, JsonBoolean.FALSE,
JsonNull.NULL, TT_JSON_STRING, TT_JSON_NUMBER
));
if (tokenizer.getFragment() != 0) {
expected.add(TT_JSON_STRING_TEMPLATE);
expected.add(TT_JSON_PLACEHOLDER);
}
throw unexpectedToken(token, expected);
}
private @NotNull JsonParserException unexpectedEndOfFile() {
return new JsonParserException(tokenizer.getFragment(), tokenizer.getLineNumber(), tokenizer.getColumnNumber(), "unexpected end of file");
}
private @NotNull JsonParserException unexpectedToken(@Nullable JsonToken token, @NotNull Object @NotNull... expected) {
if (expected.length == 1) {
throw new JsonParserException(
tokenizer.getFragment(), tokenizer.getLineNumber(), tokenizer.getColumnNumber(),
"unexpected token: " + token + " (expected " + expected[0] + ")"
);
} else {
throw new JsonParserException(
tokenizer.getFragment(), tokenizer.getLineNumber(), tokenizer.getColumnNumber(),
"unexpected token: " + token + " (expected one of " + Arrays.toString(expected) + ")"
);
}
}
/**
* Represents an JSON object or array that is currently being parsed.
*/
private sealed interface Context {
boolean isEmpty();
/**
* Adds a nested JSON value to this context
* @param value the value
*/
void add(@Nullable JsonValue value);
/**
* Builds and returns the JSON value represented by this context.
* @return a JSON value
*/
@NotNull JsonValue build();
/**
* {@return the token that indicates the end of this context}
*/
@NotNull JsonPunctuation end();
}
/**
* Represents a JSON object that is currently being parsed.
*/
private static final class ObjectContext implements Context {
private final @NotNull SequencedMap<@NotNull String, @Nullable JsonValue> map = new LinkedHashMap<>();
private @Nullable String key;
public void setKey(@Nullable String key) {
if (this.key != null) throw new IllegalStateException();
this.key = key;
}
@Override
public void add(@Nullable JsonValue value) {
if (key == null) throw new IllegalStateException();
map.put(key, value);
key = null;
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public @NotNull JsonValue build() {
if (key != null) throw new IllegalStateException();
return new JsonObject(map);
}
@Override
public @NotNull JsonPunctuation end() {
return JsonPunctuation.END_OBJECT;
}
}
/**
* Represents a JSON array that is currently being parsed.
*/
private static final class ArrayContext implements Context {
private final @NotNull List<@Nullable JsonValue> list = new ArrayList<>();
@Override
public void add(@Nullable JsonValue value) {
list.add(value);
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public @NotNull JsonArray build() {
return new JsonArray(list);
}
@Override
public @NotNull JsonPunctuation end() {
return JsonPunctuation.END_ARRAY;
}
}
}
@@ -1,89 +0,0 @@
package eu.jonahbauer.json.tokenizer;
import eu.jonahbauer.json.tokenizer.token.JsonToken;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public interface JsonTokenizer extends Iterable<JsonToken> {
/**
* {@return the next token or <code>null</code> if the end of the input has been reached}
* @throws IOException if an I/O error occurs
*/
@Nullable JsonToken next() throws IOException;
/**
* {@return the fragment at which the previously read token appeared}
*/
@Range(from = 0, to = Integer.MAX_VALUE) int getFragment();
/**
* {@return the line at which the previously read token appeared}
*/
@Range(from = 1, to = Integer.MAX_VALUE) int getLineNumber();
/**
* {@return the column at which the previously read token appeared}
*/
@Range(from = 1, to = Integer.MAX_VALUE) int getColumnNumber();
/**
* {@return an iterator over the tokens} The {@link Iterator#next()} and {@link Iterator#hasNext()} methods
* may throw an {@link UncheckedIOException} if an I/O error occurs.
*/
@Override
default @NotNull Iterator<@NotNull JsonToken> iterator() {
class JsonTokenizerIterator implements Iterator<@NotNull JsonToken> {
private @Nullable JsonToken next;
private boolean isValid = false;
private void ensureValid() {
if (isValid) return;
try {
next = JsonTokenizer.this.next();
isValid = true;
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
@Override
public boolean hasNext() {
ensureValid();
return next != null;
}
@Override
public @NotNull JsonToken next() {
ensureValid();
if (next == null) {
throw new NoSuchElementException();
}
var token = next;
next = null;
isValid = false;
return token;
}
}
return new JsonTokenizerIterator();
}
/**
* {@return a stream of tokens} When an I/O error occurs, the corresponding {@link IOException} is wrapped in an
* {@link UncheckedIOException} exception.
*/
default @NotNull Stream<@NotNull JsonToken> stream() {
return StreamSupport.stream(this.spliterator(), false);
}
}
@@ -1,53 +0,0 @@
package eu.jonahbauer.json.tokenizer.reader;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
import java.io.IOException;
public interface PushbackReader extends AutoCloseable {
int EOF = -1;
int PLACEHOLDER = -2;
/**
* Reads the next character.
* @return the next character or {@link #EOF} if the end of the stream has been reached
* @throws IOException if an I/O error occurs
*/
@Range(from = -2, to = Character.MAX_VALUE) int read() throws IOException;
/**
* Pushes the reader back, making {@link #read()} return the same character as before.
* @throws IllegalStateException when called before the first read or more than once after each read
*/
void pushback();
/**
* {@return the index of the fragment the current character belongs to} Returns 0 unless a {@code StringTemplate} is
* being read.
* @throws IllegalStateException when called before the first read
*/
@Range(from = 0, to = Integer.MAX_VALUE) int getFragment();
/**
* {@return the line number of the current character}
* @throws IllegalStateException when called before the first read
*/
@Range(from = 1, to = Integer.MAX_VALUE) int getLineNumber();
/**
* {@return the column number of the current character}
* @throws IllegalStateException when called before the first read
*/
@Range(from = 1, to = Integer.MAX_VALUE) int getColumnNumber();
/**
* Return the object that has been read after {@link #read()} returned {@link #PLACEHOLDER}.
* @throws IllegalStateException the previous call to read did not return {@link #PLACEHOLDER}
* @return the object that has been read
*/
@Nullable Object getObject();
@Override
void close() throws IOException;
}
@@ -1,158 +0,0 @@
package eu.jonahbauer.json.tokenizer.reader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.*;
public class PushbackReaderImpl implements PushbackReader {
private @Nullable Input input;
private @Nullable Reader reader;
private int current = Integer.MIN_VALUE;
private Object object;
private int fragment;
private int lineNumber;
private int columnNumber;
private boolean pushback;
public PushbackReaderImpl(@NotNull Reader reader) {
this.input = new Input(List.of(reader).iterator(), Collections.emptyIterator());
this.fragment = -1; // fragment will be incremented to 0 when the first character is read
}
@SuppressWarnings("preview")
public PushbackReaderImpl(@NotNull StringTemplate template) {
this.input = new Input(
template.fragments().stream().<Reader>map(StringReader::new).iterator(),
template.values().iterator()
);
this.fragment = 0; // fragment will be incremented to 1 when the first character is read
}
@Override
public int getFragment() {
if (current == Integer.MIN_VALUE) {
throw new IllegalStateException("No character has been read so far.");
}
return fragment;
}
@Override
public int getLineNumber() {
if (current == Integer.MIN_VALUE) {
throw new IllegalStateException("No character has been read so far.");
}
return lineNumber;
}
@Override
public int getColumnNumber() {
if (current == Integer.MIN_VALUE) {
throw new IllegalStateException("No character has been read so far.");
}
return columnNumber;
}
@Override
public void pushback() {
if (current == Integer.MIN_VALUE) {
throw new IllegalStateException("No character has been read so far.");
} else if (pushback) {
throw new IllegalStateException("Cannot push back more than one character at a time.");
}
pushback = true;
}
@Override
public int read() throws IOException {
if (pushback) {
pushback = false;
return current;
}
var input = this.input;
if (input == null) {
throw new IOException("closed");
}
var reader = this.reader;
if (reader == null && input.readers().hasNext()) {
this.reader = reader = input.readers().next();
fragment++;
lineNumber = 1;
columnNumber = 0;
} else if (reader == null) {
return EOF;
}
int result = reader.read();
if (current == '\n' && result != '\n' || current == '\r' && result != '\n' && result != '\r') {
lineNumber++;
columnNumber = 1;
} else {
columnNumber++;
}
if (result == EOF) {
reader.close();
this.reader = null;
if (input.values().hasNext()) {
this.object = input.values().next();
return this.current = PLACEHOLDER;
}
}
this.object = null;
return this.current = result;
}
@Override
public @Nullable Object getObject() {
if (current != PLACEHOLDER) {
throw new IllegalStateException("Currently not at a placeholder.");
}
return object;
}
@Override
public void close() throws IOException {
var input = this.input;
var reader = this.reader;
this.input = null;
this.reader = null;
close(Arrays.asList(input, reader).iterator());
}
private static void close(@NotNull Iterator<? extends @Nullable AutoCloseable> closeables) throws ClosingException {
ClosingException exception = null;
while (closeables.hasNext()) {
var closeable = closeables.next();
if (closeable == null) continue;
try {
closeable.close();
} catch (Throwable t) {
if (exception == null) exception = new ClosingException();
exception.addSuppressed(t);
}
}
if (exception != null) throw exception;
}
private record Input(@NotNull Iterator<@NotNull Reader> readers, @NotNull Iterator<Object> values) implements AutoCloseable {
@Override
public void close() throws ClosingException {
PushbackReaderImpl.close(readers);
}
}
private static final class ClosingException extends IOException { }
}
@@ -1,16 +0,0 @@
package eu.jonahbauer.json.tokenizer.token;
import org.jetbrains.annotations.NotNull;
/**
* The JSON token {@code null}.
*/
@SuppressWarnings("java:S6548")
public enum JsonNull implements JsonToken {
NULL;
@Override
public @NotNull String toString() {
return "null";
}
}
@@ -1,6 +0,0 @@
package eu.jonahbauer.json.tokenizer.token;
import org.jetbrains.annotations.Nullable;
public record JsonPlaceholder(@Nullable Object object) implements JsonToken {
}
@@ -1,10 +0,0 @@
package eu.jonahbauer.json.tokenizer.token;
import eu.jonahbauer.json.JsonBoolean;
import eu.jonahbauer.json.JsonNumber;
import eu.jonahbauer.json.JsonString;
/**
* Represents a JSON token.
*/
public sealed interface JsonToken permits JsonBoolean, JsonNumber, JsonString, JsonNull, JsonPlaceholder, JsonPunctuation, JsonStringTemplate { }
@@ -1 +0,0 @@
["日ш"]
@@ -1 +0,0 @@
[""]
@@ -1 +0,0 @@
[""]
@@ -1 +0,0 @@
[""]
@@ -1 +0,0 @@
[""]
@@ -1 +0,0 @@
[""]
@@ -1 +0,0 @@
[""]
@@ -1 +0,0 @@
[a]
@@ -1 +0,0 @@
[]
+8 -5
View File
@@ -1,11 +1,14 @@
[versions]
annotations = "24.1.0"
junit = "5.12.1"
junit-launcher = "1.12.1"
lombok = "1.18.32"
junit = "5.10.1"
lombok = "1.18.30"
[libraries]
annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junit-launcher" }
lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" }
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" }
[bundles]
junit = ["junit-jupiter", "junit-jupiter-api", "junit-jupiter-params"]
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
-35
View File
@@ -1,35 +0,0 @@
plugins {
`java-library`
}
group = "eu.jonahbauer.chat"
version = "1.0-SNAPSHOT"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(22)
}
}
dependencies {
api(project(":core"))
api(libs.annotations)
compileOnly(libs.lombok)
annotationProcessor(libs.lombok)
testImplementation(libs.junit.jupiter)
testRuntimeOnly(libs.junit.platform.launcher)
}
tasks {
withType<JavaCompile> {
options.encoding = "UTF-8"
options.compilerArgs.add("--enable-preview")
}
withType<Test> {
useJUnitPlatform()
jvmArgs("--enable-preview")
}
}
File diff suppressed because it is too large Load Diff
@@ -1,33 +0,0 @@
package eu.jonahbauer.json.query;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import eu.jonahbauer.json.query.parser.JQParser;
import eu.jonahbauer.json.query.parser.ast.JQProgram;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.UncheckedIOException;
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class JsonQuery {
private final @NotNull JQProgram programm;
public static @NotNull JsonQuery parse(@NotNull String query) {
try {
var parser = new JQParser(query);
var programm = parser.parseTopLevel();
if (programm.expression() == null) throw new IllegalArgumentException();
return new JsonQuery(programm);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
public @NotNull Generator<@NotNull JsonValue> run(@Nullable JsonValue value) {
return programm.run(value);
}
}
@@ -1,11 +0,0 @@
package eu.jonahbauer.json.query;
public class JsonQueryException extends RuntimeException {
public JsonQueryException(String message) {
super(message);
}
public JsonQueryException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -1,27 +0,0 @@
package eu.jonahbauer.json.query;
import eu.jonahbauer.json.JsonString;
import eu.jonahbauer.json.JsonValue;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@Getter
public class JsonQueryHaltException extends JsonQueryException {
private final @Nullable JsonValue value;
private final int statusCode;
public JsonQueryHaltException(@Nullable JsonValue value, int statusCode) {
super(getMessage(value));
this.value = value;
this.statusCode = statusCode;
}
private static @NotNull String getMessage(@Nullable JsonValue value) {
return switch (value) {
case JsonString(var string) -> string;
case null -> "";
default -> JsonValue.toJsonString(value);
};
}
}
@@ -1,28 +0,0 @@
package eu.jonahbauer.json.query;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
@Getter
public class JsonQueryParserException extends JsonQueryException {
private final int line;
private final int column;
public JsonQueryParserException(int line, int column, @NotNull String message) {
super(message);
if (line < 0 || column < 0) throw new IllegalArgumentException();
this.line = line;
this.column = column;
}
public JsonQueryParserException(int line, int column, @NotNull String message, @NotNull Throwable cause) {
this(line, column, message);
initCause(cause);
}
@Override
public @NotNull String getMessage() {
return STR."\{super.getMessage()} at line \{line}, column \{column}";
}
}
@@ -1,13 +0,0 @@
package eu.jonahbauer.json.query;
import org.jetbrains.annotations.NotNull;
public class JsonQueryTokenizerException extends JsonQueryParserException {
public JsonQueryTokenizerException(int line, int column, @NotNull String message) {
super(line, column, message);
}
public JsonQueryTokenizerException(int line, int column, @NotNull String message, @NotNull Throwable cause) {
super(line, column, message, cause);
}
}
@@ -1,23 +0,0 @@
package eu.jonahbauer.json.query;
import eu.jonahbauer.json.JsonString;
import eu.jonahbauer.json.JsonValue;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
@Getter
public class JsonQueryUserException extends JsonQueryException {
private final @NotNull JsonValue value;
public JsonQueryUserException(@NotNull JsonValue value) {
super(getMessage(value));
this.value = value;
}
private static @NotNull String getMessage(@NotNull JsonValue value) {
return switch (value) {
case JsonString(var string) -> string;
case JsonValue v -> "(not a string): " + v.toJsonString();
};
}
}
@@ -1,155 +0,0 @@
package eu.jonahbauer.json.query;
import eu.jonahbauer.json.JsonValue;
import java.util.LinkedHashMap;
import java.util.SequencedMap;
import static eu.jonahbauer.json.JsonTemplateProcessor.JSON;
public class Main {
public static void main(String[] args) {
System.out.println();
System.out.println("Object Identifier-Index: .foo, .foo.bar");
JsonQuery.parse("{\"foo\": 1, \"bar\": 2} | reduce . as {(\"foo\", \"bar\"): $item} (0; . + $item, 2 * . * $item)")
.run(null)
.forEach(System.out::println);
JsonQuery.parse(".[] as {$a, $b, c: {$d, $e}} ?// {$a, $b, c: {$d, $e}} | {$a, $b, $d, $e}")
.run(JsonValue.parse("[{\"a\": 1, \"b\": 2, \"c\": {\"d\": 3, \"e\": 4}}, {\"a\": 1, \"b\": 2, \"c\": [{\"d\": 3, \"e\": 4}]}]"))
.forEach(System.out::println);
JsonQuery.parse(".foo")
.run(JSON."{\"foo\": 42, \"bar\": \"less interesting data\"}")
.forEach(System.out::println);
JsonQuery.parse(".foo")
.run(JSON."{\"notfoo\": true, \"alsonotfoo\": false}")
.forEach(System.out::println);
JsonQuery.parse(".[\"foo\"]")
.run(JSON."{\"foo\": 42}")
.forEach(System.out::println);
JsonQuery.parse(".foo?")
.run(JSON."{\"foo\": 42, \"bar\": \"less interesting data\"}")
.forEach(System.out::println);
System.out.println();
System.out.println("Optional Object Identifier-Index: .foo?");
JsonQuery.parse(".foo?")
.run(JSON."{\"notfoo\": true, \"alsonotfoo\": false}")
.forEach(System.out::println);
JsonQuery.parse(".[\"foo\"]?")
.run(JSON."{\"foo\": 42}")
.forEach(System.out::println);
JsonQuery.parse("[.foo?]")
.run(JSON."[1, 2]")
.forEach(System.out::println);
System.out.println();
System.out.println("Array Index: .[<number>]");
JsonQuery.parse(".[0]")
.run(JSON."[{\"name\":\"JSON\", \"good\":true}, {\"name\":\"XML\", \"good\":false}]")
.forEach(System.out::println);
JsonQuery.parse(".[2]")
.run(JSON."[{\"name\":\"JSON\", \"good\":true}, {\"name\":\"XML\", \"good\":false}]")
.forEach(System.out::println);
JsonQuery.parse(".[-2]")
.run(JSON."[1, 2, 3]")
.forEach(System.out::println);
System.out.println();
System.out.println("Array/String Slice: .[<number>:<number>]");
JsonQuery.parse(".[2:4]")
.run(JSON."[\"a\",\"b\",\"c\",\"d\",\"e\"]")
.forEach(System.out::println);
JsonQuery.parse(".[2:4]")
.run(JSON."\"abcdefghi\"")
.forEach(System.out::println);
JsonQuery.parse(".[:3]")
.run(JSON."[\"a\",\"b\",\"c\",\"d\",\"e\"]")
.forEach(System.out::println);
JsonQuery.parse(".[-2:]")
.run(JSON."[\"a\",\"b\",\"c\",\"d\",\"e\"]")
.forEach(System.out::println);
System.out.println();
System.out.println("Array/Object Value Iterator: .[]");
JsonQuery.parse(".[]")
.run(JSON."[{\"name\":\"JSON\", \"good\":true}, {\"name\":\"XML\", \"good\":false}]")
.forEach(System.out::println);
JsonQuery.parse(".[]")
.run(JSON."[]")
.forEach(System.out::println);
JsonQuery.parse(".foo[]")
.run(JSON."{\"foo\":[1,2,3]}")
.forEach(System.out::println);
JsonQuery.parse(".[]")
.run(JSON."{\"a\": 1, \"b\": 1}")
.forEach(System.out::println);
// var root = JsonValue.valueOf(List.of(
// List.of(
// List.of(10, 20),
// List.of(30, 40)
// ),
// List.of(
// List.of(1, 2),
// List.of(3, 4)
// )
// ));
//
// var key = new JQCommaExpression(new JQConstant(new JsonNumber(0)), new JQConstant(new JsonNumber(1)));
//
// var pattern = new ArrayPattern(List.of(
// new JQAsExpression.Pattern.ObjectPattern(of(
// key, new JQAsExpression.Pattern.ObjectPattern(of(
// key,
// new JQAsExpression.Pattern.ValuePattern("$x")
// ))
// )),
// new JQAsExpression.Pattern.ObjectPattern(of(
// key, new JQAsExpression.Pattern.ObjectPattern(of(
// key,
// new JQAsExpression.Pattern.ValuePattern("$y")
// ))
// ))
// ));
//
// var expression = new JQBinaryExpression(
// new JQVariableExpression("$x"),
// new JQVariableExpression("$y"),
// JQBinaryExpression.Operator.ADD
// );
//
// var as = new JQAsExpression(new JQConstant(root), List.of(pattern), expression);
// as.evaluate(new JQExpression.Context(null)).forEach(System.out::println);
//
// var parser = new JQParser("[{(0, 1): {(0,1): $x}}, {(0, 1): {(0, 1): $y}}]");
// var pattern2 = parser.parsePattern();
// System.out.println(pattern);
// System.out.println(pattern2);
}
private static <K, V> SequencedMap<K, V> of(K key1, V value1) {
var map = new LinkedHashMap<K, V>();
map.put(key1, value1);
return map;
}
}
@@ -1,42 +0,0 @@
package eu.jonahbauer.json.query.impl;
import org.jetbrains.annotations.NotNull;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Queue;
final class ConcatGenerator<T> implements Generator<T> {
private final @NotNull Queue<@NotNull Generator<? extends T>> generators;
public ConcatGenerator(@NotNull List<@NotNull Generator<? extends T>> generators) {
this.generators = new LinkedList<>(generators);
}
@Override
public boolean hasNext() {
while (!generators.isEmpty()) {
var current = generators.peek();
if (!current.hasNext()) {
generators.remove();
} else {
return true;
}
}
return false;
}
@Override
public T next() {
while (!generators.isEmpty()) {
var current = generators.peek();
if (!current.hasNext()) {
generators.remove();
} else {
return current.next();
}
}
throw new NoSuchElementException();
}
}
@@ -1,17 +0,0 @@
package eu.jonahbauer.json.query.impl;
import java.util.NoSuchElementException;
enum EmptyGenerator implements Generator<Object> {
INSTANCE;
@Override
public boolean hasNext() {
return false;
}
@Override
public Object next() {
throw new NoSuchElementException();
}
}
@@ -1,49 +0,0 @@
package eu.jonahbauer.json.query.impl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.NoSuchElementException;
import java.util.function.Predicate;
final class FilterGenerator<T> implements Generator<T> {
private final @NotNull Generator<T> generator;
private final @NotNull Predicate<? super T> filter;
private @Nullable T value;
private boolean hasValue;
public FilterGenerator(@NotNull Generator<T> generator, @NotNull Predicate<? super T> filter) {
this.generator = generator;
this.filter = filter;
}
public boolean advance() {
if (hasValue) return true;
while (generator.hasNext()) {
var value = generator.next();
if (filter.test(value)) {
this.value = value;
this.hasValue = true;
return true;
}
}
this.value = null;
this.hasValue = false;
return false;
}
@Override
public boolean hasNext() {
return advance();
}
@Override
public T next() {
if (!advance()) throw new NoSuchElementException();
var out = value;
hasValue = false;
value = null;
return out;
}
}
@@ -1,32 +0,0 @@
package eu.jonahbauer.json.query.impl;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
final class FlatMappingGenerator<T, S> implements Generator<S> {
private final @NotNull Generator<T> source;
private final @NotNull Function<? super T, ? extends Generator<? extends S>> function;
private @NotNull Generator<? extends S> current = Generator.empty();
public FlatMappingGenerator(@NotNull Generator<T> source, @NotNull Function<? super T, ? extends Generator<? extends S>> function) {
this.source = source;
this.function = function;
}
@Override
public boolean hasNext() {
while (!current.hasNext() && source.hasNext()) {
current = function.apply(source.next());
}
return current.hasNext();
}
@Override
public S next() {
while (!current.hasNext() && source.hasNext()) {
current = function.apply(source.next());
}
return current.next();
}
}
@@ -1,96 +0,0 @@
package eu.jonahbauer.json.query.impl;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.function.*;
import java.util.stream.Stream;
public interface Generator<T> {
boolean hasNext();
T next() throws NoSuchElementException;
default void forEach(@NotNull Consumer<? super T> action) {
while (hasNext()) {
action.accept(next());
}
}
default T reduce(T seed, @NotNull BinaryOperator<T> operator) {
while (hasNext()) {
seed = operator.apply(seed, next());
}
return seed;
}
default boolean anyMatch(@NotNull Predicate<? super T> predicate) {
while (hasNext()) {
if (predicate.test(next())) return true;
}
return false;
}
default boolean allMatch(@NotNull Predicate<? super T> predicate) {
while (hasNext()) {
if (!predicate.test(next())) return false;
}
return true;
}
default @NotNull List<T> toList() {
var out = new ArrayList<T>();
while (hasNext()) {
out.add(next());
}
return Collections.unmodifiableList(out);
}
default @NotNull Generator<T> filter(@NotNull Predicate<? super T> predicate) {
return new FilterGenerator<>(this, predicate);
}
default @NotNull Generator<T> limit(int count) {
return new LimitGenerator<>(this, count);
}
default <S> @NotNull Generator<S> map(@NotNull Function<? super T, ? extends S> function) {
return new MappingGenerator<>(this, function);
}
default <S> @NotNull Generator<S> flatMap(@NotNull Function<? super T, ? extends Generator<? extends S>> function) {
return new FlatMappingGenerator<>(this, function);
}
default <S> @NotNull Generator<S> mapMulti(@NotNull BiConsumer<? super T, ? super Consumer<S>> function) {
return new MapMultiGenerator<>(this, function);
}
@SuppressWarnings("unchecked")
static <T> @NotNull Generator<T> empty() {
return (Generator<T>) EmptyGenerator.INSTANCE;
}
static <T> @NotNull Generator<T> concat(@NotNull Generator<? extends T> first, @NotNull Generator<? extends T> second) {
return new ConcatGenerator<>(List.of(first, second));
}
static <T> @NotNull Generator<T> of(T value) {
return new SingletonGenerator<>(value);
}
static <T> @NotNull Generator<T> of(@NotNull Supplier<T> value) {
return new SupplierGenerator<>(value);
}
static <T> @NotNull Generator<T> from(@NotNull Iterable<T> value) {
return new IteratorGenerator<>(value.iterator());
}
static <T> @NotNull Generator<T> from(@NotNull Iterator<T> value) {
return new IteratorGenerator<>(value);
}
static <T> @NotNull Generator<T> from(@NotNull Stream<T> value) {
return new IteratorGenerator<>(value.iterator());
}
}
@@ -1,17 +0,0 @@
package eu.jonahbauer.json.query.impl;
import org.jetbrains.annotations.NotNull;
import java.util.Iterator;
public record IteratorGenerator<T>(@NotNull Iterator<T> iterator) implements Generator<T> {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public T next() {
return iterator.next();
}
}
@@ -1,29 +0,0 @@
package eu.jonahbauer.json.query.impl;
import org.jetbrains.annotations.NotNull;
import java.util.NoSuchElementException;
import java.util.Objects;
final class LimitGenerator<T> implements Generator<T> {
private final @NotNull Generator<T> delegate;
private int count;
public LimitGenerator(@NotNull Generator<T> delegate, int count) {
if (count < 0) throw new IllegalArgumentException();
this.delegate = Objects.requireNonNull(delegate);
this.count = count;
}
@Override
public boolean hasNext() {
return count != 0 && delegate.hasNext();
}
@Override
public T next() {
if (count == 0 || !delegate.hasNext()) throw new NoSuchElementException();
count--;
return delegate.next();
}
}
@@ -1,39 +0,0 @@
package eu.jonahbauer.json.query.impl;
import org.jetbrains.annotations.NotNull;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Queue;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
final class MapMultiGenerator<T, S> implements Generator<S> {
private final @NotNull Generator<T> generator;
private final @NotNull BiConsumer<? super T, ? super Consumer<S>> mapping;
private final @NotNull Queue<S> queue = new LinkedList<>();
public MapMultiGenerator(@NotNull Generator<T> generator, @NotNull BiConsumer<? super T, ? super Consumer<S>> mapping) {
this.generator = Objects.requireNonNull(generator);
this.mapping = Objects.requireNonNull(mapping);
}
private boolean advance() {
while (queue.isEmpty() && generator.hasNext()) {
mapping.accept(generator.next(), (Consumer<S>) queue::add);
}
return !queue.isEmpty();
}
@Override
public boolean hasNext() {
return advance();
}
@Override
public S next() {
if (!advance()) throw new NoSuchElementException();
return queue.remove();
}
}
@@ -1,26 +0,0 @@
package eu.jonahbauer.json.query.impl;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.function.Function;
final class MappingGenerator<T, S> implements Generator<S> {
private final @NotNull Generator<T> delegate;
private final @NotNull Function<? super T, ? extends S> function;
public MappingGenerator(@NotNull Generator<T> delegate, @NotNull Function<? super T, ? extends S> function) {
this.delegate = Objects.requireNonNull(delegate);
this.function = Objects.requireNonNull(function);
}
@Override
public boolean hasNext() {
return delegate.hasNext();
}
@Override
public S next() {
return function.apply(delegate.next());
}
}
@@ -1,27 +0,0 @@
package eu.jonahbauer.json.query.impl;
import java.util.NoSuchElementException;
final class SingletonGenerator<T> implements Generator<T> {
private boolean done;
private T value;
public SingletonGenerator(T value) {
this.value = value;
}
@Override
public boolean hasNext() {
return !done;
}
@Override
public T next() {
if (done) throw new NoSuchElementException();
var out = value;
done = true;
value = null;
return out;
}
}
@@ -1,31 +0,0 @@
package eu.jonahbauer.json.query.impl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Supplier;
final class SupplierGenerator<T> implements Generator<T> {
private @Nullable Supplier<? extends T> value;
public SupplierGenerator(@NotNull Supplier<? extends T> supplier) {
this.value = Objects.requireNonNull(supplier);
}
@Override
public boolean hasNext() {
return value != null;
}
@Override
public T next() {
if (value != null) {
var out = value.get();
value = null;
return out;
}
throw new NoSuchElementException();
}
}
@@ -1,668 +0,0 @@
package eu.jonahbauer.json.query.parser;
import eu.jonahbauer.json.*;
import eu.jonahbauer.json.query.JsonQueryParserException;
import eu.jonahbauer.json.query.parser.ast.*;
import eu.jonahbauer.json.query.parser.tokenizer.JQToken;
import eu.jonahbauer.json.query.parser.tokenizer.JQTokenKind;
import eu.jonahbauer.json.query.parser.tokenizer.JQTokenizer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.Reader;
import java.util.*;
import java.util.function.BinaryOperator;
public class JQParser {
private final @NotNull JQTokenizer tokenizer;
private final @NotNull Queue<JQToken> pushback = new LinkedList<>();
public JQParser(@NotNull String string) {
this.tokenizer = new JQTokenizer(string);
}
public JQParser(@NotNull Reader reader) {
this.tokenizer = new JQTokenizer(reader);
}
public @NotNull JQProgram parseTopLevel() throws IOException {
var module = parseModule();
var imports = parseImports();
var functions = parseFuncDefs();
var expr = peek() == null ? null : parseExpression();
return new JQProgram(module, imports, functions, expr);
}
private @Nullable JQModule parseModule() throws IOException {
if (!tryConsume(JQTokenKind.MODULE)) return null;
var metadata = parseExpression();
consume(JQTokenKind.SEMICOLON);
return new JQModule(metadata);
}
private @NotNull List<@NotNull JQImport> parseImports() throws IOException {
var out = new ArrayList<JQImport>();
while (peek(JQTokenKind.IMPORT) || peek(JQTokenKind.INCLUDE)) {
out.add(parseImport());
}
return out;
}
private @NotNull JQImport parseImport() throws IOException {
var include = tryConsume(JQTokenKind.INCLUDE);
if (!include) consume(JQTokenKind.IMPORT);
var path = parseString();
String as;
if (include) {
as = null;
} else {
consume(JQTokenKind.AS);
var dollar = tryConsume(JQTokenKind.DOLLAR);
var ident = consume(JQTokenKind.IDENT);
as = (dollar ? "$" : "") + ident.text();
}
if (tryConsume(JQTokenKind.SEMICOLON)) {
return new JQImport(path, as, null);
} else {
var metadata = parseExpression();
consume(JQTokenKind.SEMICOLON);
return new JQImport(path, as, metadata);
}
}
private @NotNull List<@NotNull JQFunction> parseFuncDefs() throws IOException {
var out = new ArrayList<JQFunction>();
while (peek(JQTokenKind.DEF)) {
out.add(parseFuncDef());
}
return out;
}
private @NotNull JQFunction parseFuncDef() throws IOException {
consume(JQTokenKind.DEF);
var name = consume(JQTokenKind.IDENT).text();
var params = new ArrayList<String>();
if (tryConsume(JQTokenKind.LPAREN)) {
do {
var dollar = tryConsume(JQTokenKind.DOLLAR);
var param = consume(JQTokenKind.IDENT).text();
params.add((dollar ? "$" : "") + param);
} while (tryConsume(JQTokenKind.SEMICOLON));
consume(JQTokenKind.RPAREN);
}
consume(JQTokenKind.COLON);
var body = parseExpression();
consume(JQTokenKind.SEMICOLON);
return new JQFunction(name, params, body);
}
public @NotNull JQExpression parseExpression() throws IOException {
if (peek(JQTokenKind.DEF)) {
var function = parseFuncDef();
var expr = parsePipeExpression();
return new JQFunctionDefinition(function, expr);
} else if (peek(JQTokenKind.LABEL)) {
} else {
return parsePipeExpression();
}
throw new UnsupportedOperationException("not yet implemented");
}
private @NotNull JQExpression parseNonAssocBinaryExpression(
@NotNull Map<@NotNull JQTokenKind, @NotNull BinaryOperator<@NotNull JQExpression>> operations,
@NotNull Downstream next
) throws IOException {
var first = next.get();
var operator = peek();
var operation = operator == null ? null : operations.get(operator.kind());
if (operation != null) {
next();
var second = next.get();
first = operation.apply(first, second);
}
return first;
}
private @NotNull JQExpression parseLeftAssocBinaryExpression(
@NotNull Map<@NotNull JQTokenKind, @NotNull BinaryOperator<@NotNull JQExpression>> operations,
@NotNull Downstream next
) throws IOException {
var first = next.get();
while (true) {
var operator = peek();
var operation = operator == null ? null : operations.get(operator.kind());
if (operation == null) break;
next();
var second = next.get();
first = operation.apply(first, second);
}
return first;
}
private interface Downstream {
@NotNull
JQExpression get() throws IOException;
}
private @NotNull JQExpression parsePipeExpression() throws IOException {
return parseLeftAssocBinaryExpression(Map.of(
JQTokenKind.PIPE, JQPipeExpression::new
), this::parseCommaExpression);
}
private @NotNull JQExpression parseCommaExpression() throws IOException {
return parseLeftAssocBinaryExpression(Map.of(
JQTokenKind.COMMA, JQCommaExpression::new
), this::parseAlternativeExpression);
}
private @NotNull JQExpression parseAlternativeExpression() throws IOException {
return parseLeftAssocBinaryExpression(Map.of(
JQTokenKind.DEFINEDOR, JQAlternativeExpression::new
), this::parseAssignment);
}
private @NotNull JQExpression parseAssignment() throws IOException {
return parseNonAssocBinaryExpression(Map.of(
JQTokenKind.ASSIGN, JQAssignment::new,
JQTokenKind.SETPLUS, JQAssignment::add,
JQTokenKind.SETMINUS, JQAssignment::sub,
JQTokenKind.SETMULT, JQAssignment::mul,
JQTokenKind.SETDIV, JQAssignment::div,
JQTokenKind.SETMOD, JQAssignment::mod,
JQTokenKind.SETPIPE, JQAssignmentPipe::new,
JQTokenKind.SETDEFINEDOR, JQAssignmentCoerce::new
), this::parseBooleanOrExpression);
}
private @NotNull JQExpression parseBooleanOrExpression() throws IOException {
return parseLeftAssocBinaryExpression(Map.of(
JQTokenKind.OR, JQBooleanOrExpression::new
), this::parseBooleanAndExpression);
}
private @NotNull JQExpression parseBooleanAndExpression() throws IOException {
return parseLeftAssocBinaryExpression(Map.of(
JQTokenKind.AND, JQBooleanAndExpression::new
), this::parseComparisonExpression);
}
private @NotNull JQExpression parseComparisonExpression() throws IOException {
return parseNonAssocBinaryExpression(Map.of(
JQTokenKind.EQ, JQBinaryExpression::eq,
JQTokenKind.NEQ, JQBinaryExpression::neq,
JQTokenKind.LESS, JQBinaryExpression::lt,
JQTokenKind.GREATER, JQBinaryExpression::gt,
JQTokenKind.LESSEQ, JQBinaryExpression::leq,
JQTokenKind.GREATEREQ, JQBinaryExpression::geq
), this::parseAdditiveExpression);
}
private @NotNull JQExpression parseAdditiveExpression() throws IOException {
return parseLeftAssocBinaryExpression(Map.of(
JQTokenKind.PLUS, JQBinaryExpression::add,
JQTokenKind.MINUS, JQBinaryExpression::sub
), this::parseMultiplicativeExpression);
}
private @NotNull JQExpression parseMultiplicativeExpression() throws IOException {
return parseLeftAssocBinaryExpression(Map.of(
JQTokenKind.MULT, JQBinaryExpression::mul,
JQTokenKind.DIV, JQBinaryExpression::div,
JQTokenKind.MOD, JQBinaryExpression::mod
), this::parseTryCatch);
}
private @NotNull JQExpression parseTryCatch() throws IOException {
if (tryConsume(JQTokenKind.TRY)) {
var expr = parseNegation();
var fallback = tryConsume(JQTokenKind.CATCH) ? parseNegation() : null;
return new JQTryExpression(expr, fallback);
} else {
return parseNegation();
}
}
private @NotNull JQExpression parseNegation() throws IOException {
if (tryConsume(JQTokenKind.MINUS)) {
var expr = parseErrorSuppression();
return new JQNegation(expr);
} else {
return parseErrorSuppression();
}
}
private @NotNull JQExpression parseErrorSuppression() throws IOException {
var expression = parseControlFlow();
if (tryConsume(JQTokenKind.QUESTION_MARK)) {
return new JQTryExpression(expression);
} else {
return expression;
}
}
private @NotNull JQExpression parseControlFlow() throws IOException {
if (tryConsume(JQTokenKind.REDUCE)) {
var expr = parseTerm();
consume(JQTokenKind.AS);
var patterns = parsePatterns();
consume(JQTokenKind.LPAREN);
var init = parseExpression();
consume(JQTokenKind.SEMICOLON);
var update = parseExpression();
consume(JQTokenKind.RPAREN);
return new JQReduceExpression(expr, patterns, init, update);
} else if (tryConsume(JQTokenKind.FOREACH)) {
var expr = parseTerm();
consume(JQTokenKind.AS);
var patterns = parsePatterns();
consume(JQTokenKind.LPAREN);
var init = parseExpression();
consume(JQTokenKind.SEMICOLON);
var update = parseExpression();
var extract = tryConsume(JQTokenKind.SEMICOLON) ? parseExpression() : new JQRootExpression();
consume(JQTokenKind.RPAREN);
return new JQForEachExpression(expr, patterns, init, update, extract);
} else if (tryConsume(JQTokenKind.IF)) {;
var conds = new ArrayList<JQExpression>();
var thens = new ArrayList<JQExpression>();
do {
conds.add(parseExpression());
consume(JQTokenKind.THEN);
thens.add(parseExpression());
} while (tryConsume(JQTokenKind.ELSE_IF));
var otherwise = tryConsume(JQTokenKind.ELSE) ? parseExpression() : null;
consume(JQTokenKind.END);
var out = new JQIfExpression(conds.removeLast(), thens.removeLast(), otherwise);
while (!conds.isEmpty()) {
out = new JQIfExpression(conds.removeLast(), thens.removeLast(), out);
}
return out;
} else {
var term = parseTerm();
if (tryConsume(JQTokenKind.AS)) {
var patterns = parsePatterns();
consume(JQTokenKind.PIPE);
var expr = parseExpression();
return new JQAsExpression(term, patterns, expr);
}
return term;
}
}
private @NotNull JQPatterns parsePatterns() throws IOException {
var patterns = new ArrayList<JQPatterns.Pattern>();
patterns.add(parsePattern());
while (tryConsume(JQTokenKind.ALTERNATION)) {
patterns.add(parsePattern());
}
return new JQPatterns(patterns);
}
private @NotNull JQPatterns.Pattern parsePattern() throws IOException {
if (tryConsume(JQTokenKind.LBRACKET)) {
var patterns = new ArrayList<JQPatterns.Pattern>();
do {
patterns.add(parsePattern());
} while (tryConsume(JQTokenKind.COMMA));
consume(JQTokenKind.RBRACKET);
return new JQPatterns.Pattern.ArrayPattern(patterns);
} else if (tryConsume(JQTokenKind.LBRACE)) {
var patterns = new LinkedHashMap<JQExpression, JQPatterns.Pattern>();
do {
if (tryConsume(JQTokenKind.DOLLAR)) {
var ident = consume(JQTokenKind.IDENT).text();
var pattern = new JQPatterns.Pattern.ValuePattern("$" + ident);
patterns.put(new JQConstant(JsonString.valueOf(ident)), pattern);
} else if (peek(JQTokenKind.IDENT)) {
var ident = consume(JQTokenKind.IDENT).text();
consume(JQTokenKind.COLON);
var pattern = parsePattern();
patterns.put(new JQConstant(JsonString.valueOf(ident)), pattern);
} else if (tryConsume(JQTokenKind.LPAREN)) {
var expr = parseExpression();
consume(JQTokenKind.RPAREN);
consume(JQTokenKind.COLON);
var pattern = parsePattern();
patterns.put(new JQParenthesizedExpression(expr), pattern);
} else {
var key = parseString();
consume(JQTokenKind.COLON);
var pattern = parsePattern();
patterns.put(key, pattern);
}
} while (tryConsume(JQTokenKind.COMMA));
consume(JQTokenKind.RBRACE);
return new JQPatterns.Pattern.ObjectPattern(patterns);
} else {
consume(JQTokenKind.DOLLAR);
var ident = consume(JQTokenKind.IDENT).text();
return new JQPatterns.Pattern.ValuePattern("$" + ident);
}
}
private @NotNull JQExpression parseTerm() throws IOException {
var next = peek();
var term = switch (next == null ? null : next.kind()) {
case NUMBER -> new JQConstant(new JsonNumber(Objects.requireNonNull(next().nval())));
case LPAREN -> parseParenthesizedExpression();
case LBRACKET -> parseArrayConstructionExpression();
case LBRACE -> parseObjectConstructionExpression();
case QQSTRING_START -> parseString();
case FORMAT -> throw new UnsupportedOperationException("not yet implemented");
case BREAK -> {
consume(JQTokenKind.IDENT);
consume(JQTokenKind.DOLLAR);
var label = consume(JQTokenKind.IDENT).text();
throw new UnsupportedOperationException("not yet implemented");
}
case IDENT -> switch (Objects.requireNonNull(peek()).text()) {
case "null" -> {
consume(JQTokenKind.IDENT);
yield new JQConstant(null);
}
case "true" -> {
consume(JQTokenKind.IDENT);
yield new JQConstant(JsonBoolean.TRUE);
}
case "false" -> {
consume(JQTokenKind.IDENT);
yield new JQConstant(JsonBoolean.FALSE);
}
default -> parseFunctionInvocation();
};
case LOC -> {
var loc = consume(JQTokenKind.LOC);
yield new JQLocExpression("<top-level>", loc.line());
}
case DOLLAR -> parseVariableExpression();
case FIELD -> {
var name = consume(JQTokenKind.FIELD).text().substring(1);
var optional = tryConsume(JQTokenKind.QUESTION_MARK);
yield new JQIndexExpression(new JQRootExpression(), name, optional);
}
case DOT -> {
consume(JQTokenKind.DOT);
if (peek(JQTokenKind.QQSTRING_START)) {
var name = parseString();
var optional = tryConsume(JQTokenKind.QUESTION_MARK);
yield new JQIndexExpression(new JQRootExpression(), name, optional);
} else {
yield new JQRootExpression();
}
}
case REC -> {
consume(JQTokenKind.REC);
yield new JQRecursionExpression();
}
case null -> throw new JsonQueryParserException(-1, -1, "unexpected end of file");
default -> {
var token = Objects.requireNonNull(peek());
throw new JsonQueryParserException(token.line(), token.column(), "unexpected token " + token.kind());
}
};
while (peek(JQTokenKind.LBRACKET) || peek(JQTokenKind.DOT) || peek(JQTokenKind.FIELD)) {
term = parseIndexingExpression(term);
}
return term;
}
private @NotNull JQExpression parseFunctionInvocation() throws IOException {
var name = consume(JQTokenKind.IDENT).text();
var args = new ArrayList<JQExpression>();
if (tryConsume(JQTokenKind.LPAREN)) {
do {
args.add(parseExpression());
} while (tryConsume(JQTokenKind.SEMICOLON));
consume(JQTokenKind.RPAREN);
}
return new JQFunctionInvocation(name, args);
}
private @NotNull JQVariableExpression parseVariableExpression() throws IOException {
consume(JQTokenKind.DOLLAR);
var ident = consume(JQTokenKind.IDENT).text();
return new JQVariableExpression("$" + ident);
}
private @NotNull JQExpression parseParenthesizedExpression() throws IOException {
consume(JQTokenKind.LPAREN);
var expression = parseExpression();
consume(JQTokenKind.RPAREN);
return new JQParenthesizedExpression(expression);
}
private @NotNull JQExpression parseArrayConstructionExpression() throws IOException {
consume(JQTokenKind.LBRACKET);
if (tryConsume(JQTokenKind.RBRACKET)) {
return new JQConstant(JsonArray.EMPTY);
} else {
var expression = parseExpression();
consume(JQTokenKind.RBRACKET);
return new JQArrayConstructionExpression(expression);
}
}
private @NotNull JQExpression parseObjectConstructionExpression() throws IOException {
consume(JQTokenKind.LBRACE);
if (tryConsume(JQTokenKind.RBRACE)) {
return new JQConstant(JsonObject.EMPTY);
}
var entries = new ArrayList<JQObjectConstructionExpression.Entry>();
while (!tryConsume(JQTokenKind.RBRACE)) {
if (!entries.isEmpty()) {
consume(JQTokenKind.COMMA);
}
if (peek(JQTokenKind.LOC)) {
var loc = consume(JQTokenKind.LOC);
entries.add(new JQObjectConstructionExpression.Entry(
new JQConstant(JsonValue.valueOf("__loc__")),
new JQLocExpression("<top-level>", loc.line())
));
continue;
}
var next = peek();
JQExpression key;
JQExpression value = null;
switch (next == null ? null : next.kind()) {
case IDENT -> {
key = new JQConstant(JsonString.valueOf(consume(JQTokenKind.IDENT).text()));
value = new JQIndexExpression(new JQRootExpression(), key, false);
}
case QQSTRING_START -> {
key = parseString();
value = new JQIndexExpression(new JQRootExpression(), key, false);
}
case DOLLAR -> {
var var = parseVariableExpression();
key = new JQConstant(JsonString.valueOf(var.name().substring(1)));
value = var;
}
case LPAREN -> {
key = parseParenthesizedExpression();
}
case AS, IMPORT, INCLUDE, MODULE, DEF, IF, THEN, ELSE, ELSE_IF, AND, OR, END, REDUCE, FOREACH, TRY,
CATCH, LABEL, BREAK -> {
key = new JQConstant(JsonString.valueOf(consume(next.kind()).text()));
value = key;
}
case null -> throw new JsonQueryParserException(-1, -1, "unexpected end of file");
default -> {
var token = Objects.requireNonNull(next);
throw new JsonQueryParserException(token.line(),
token.column(),
"unexpected token " + token.kind()
);
}
}
if (value == null) {
consume(JQTokenKind.COLON);
} else if (!tryConsume(JQTokenKind.COLON)) {
entries.add(new JQObjectConstructionExpression.Entry(key, value));
continue;
}
value = parseLeftAssocBinaryExpression(Map.of(JQTokenKind.PIPE, JQPipeExpression::new), this::parseNegation);
entries.add(new JQObjectConstructionExpression.Entry(key, value));
}
return new JQObjectConstructionExpression(entries);
}
private @NotNull JQExpression parseIndexingExpression(@NotNull JQExpression root) throws IOException {
if (tryConsume(JQTokenKind.LBRACKET)) {
if (tryConsume(JQTokenKind.RBRACKET)) {
var optional = tryConsume(JQTokenKind.QUESTION_MARK);
return new JQIterateExpression(root, optional);
} else if (tryConsume(JQTokenKind.COLON)) {
var end = parseExpression();
consume(JQTokenKind.RBRACKET);
var optional = tryConsume(JQTokenKind.QUESTION_MARK);
return new JQSliceExpression(root, null, end, optional);
} else {
var expr = parseExpression();
if (tryConsume(JQTokenKind.COLON)) {
var end = peek(JQTokenKind.RBRACKET) ? null : parseExpression();
consume(JQTokenKind.RBRACKET);
var optional = tryConsume(JQTokenKind.QUESTION_MARK);
return new JQSliceExpression(root, expr, end, optional);
} else {
consume(JQTokenKind.RBRACKET);
var optional = tryConsume(JQTokenKind.QUESTION_MARK);
return new JQIndexExpression(root, expr, optional);
}
}
} else if (peek(JQTokenKind.FIELD)) {
var name = consume(JQTokenKind.FIELD).text().substring(1);
var optional = tryConsume(JQTokenKind.QUESTION_MARK);
return new JQIndexExpression(root, name, optional);
} else {
consume(JQTokenKind.DOT);
var name = parseString();
var optional = tryConsume(JQTokenKind.QUESTION_MARK);
return new JQIndexExpression(root, name, optional);
}
}
private @NotNull JQStringInterpolation parseString() throws IOException {
var format = peek(JQTokenKind.FORMAT) ? next().text() : null;
var current = new StringBuilder();
var fragments = new ArrayList<String>();
var values = new ArrayList<JQExpression>();
consume(JQTokenKind.QQSTRING_START);
current.setLength(0);
while (peek(JQTokenKind.QQSTRING_TEXT)) {
current.append(next().sval());
}
fragments.add(current.toString());
while (tryConsume(JQTokenKind.QQSTRING_INTERP_START)) {
values.add(parseExpression());
consume(JQTokenKind.QQSTRING_INTERP_END);
current.setLength(0);
while (peek(JQTokenKind.QQSTRING_TEXT)) {
current.append(next().sval());
}
fragments.add(current.toString());
}
consume(JQTokenKind.QQSTRING_END);
return new JQStringInterpolation(format, fragments, values);
}
private void consume(@NotNull String ident) throws IOException {
var token = next();
if (token.kind() != JQTokenKind.IDENT || !Objects.equals(ident, token.text())) {
throw new JsonQueryParserException(0, 0, "");
}
}
private @NotNull JQToken consume(@NotNull JQTokenKind kind) throws IOException {
var token = next();
if (token.kind() != kind) throw new JsonQueryParserException(token.line(), token.column(), "unexpected " + token.kind() + " expected " + kind);
return token;
}
private boolean tryConsume(@NotNull String ident) throws IOException {
var out = peek(ident);
if (out) next();
return out;
}
private boolean tryConsume(@NotNull JQTokenKind kind) throws IOException {
var out = peek(kind);
if (out) next();
return out;
}
private boolean peek(@NotNull String ident) throws IOException {
var token = peek();
return token != null && token.kind() == JQTokenKind.IDENT && Objects.equals(ident, token.text());
}
private boolean peek(@NotNull JQTokenKind kind) throws IOException {
var token = peek();
return token != null && token.kind() == kind;
}
private @Nullable JQToken peek() throws IOException {
if (pushback.isEmpty()) {
var next = tokenizer.next();
pushback(next);
return next;
} else {
return pushback.peek();
}
}
private @NotNull JQToken next() throws IOException {
if (pushback.isEmpty()) {
var next = tokenizer.next();
if (next == null) throw new JsonQueryParserException(0, 0, "unexpected $end");
return next;
} else {
return pushback.remove();
}
}
private void pushback(@Nullable JQToken token) {
pushback.add(token);
}
}
@@ -1,73 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.NoSuchElementException;
import java.util.Objects;
@SuppressWarnings("preview")
public record JQAlternativeExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression {
public JQAlternativeExpression {
Objects.requireNonNull(first);
Objects.requireNonNull(second);
}
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
var first = this.first.evaluate(context);
var second = this.second.evaluate(context);
return new Generator<>() {
private boolean empty = true;
private JsonValue value;
private boolean hasValue;
private boolean advance() {
if (hasValue) return true;
while (first.hasNext()) {
var value = first.next();
if (JsonMath.isTruthy(value)) {
this.empty = false;
this.value = value;
this.hasValue = true;
return true;
}
}
if (empty && second.hasNext()) {
this.value = second.next();
this.hasValue = true;
return true;
}
return false;
}
@Override
public boolean hasNext() {
return advance();
}
@Override
public @Nullable JsonValue next() throws NoSuchElementException {
if (!advance()) throw new NoSuchElementException();
var out = value;
hasValue = false;
value = null;
return out;
}
};
}
@Override
public boolean isConstant() {
return first.isConstant() && second.isConstant();
}
@Override
public @NotNull String toString() {
return first + " // " + second;
}
}
@@ -1,30 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonArray;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public record JQArrayConstructionExpression(@NotNull JQExpression expression) implements JQExpression {
public JQArrayConstructionExpression {
Objects.requireNonNull(expression);
}
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return Generator.of(() -> new JsonArray(expression.evaluate(context).toList()));
}
@Override
public boolean isConstant() {
return expression.isConstant();
}
@Override
public @NotNull String toString() {
return "[" + expression + "]";
}
}
@@ -1,36 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public record JQAsExpression(
@NotNull JQExpression variable, @NotNull JQPatterns patterns, @NotNull JQExpression expression
) implements JQExpression {
public JQAsExpression {
Objects.requireNonNull(variable);
Objects.requireNonNull(expression);
Objects.requireNonNull(patterns);
}
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return variable.evaluate(context).flatMap(value -> patterns.bind(context, value, expression::evaluate));
}
@Override
public boolean isConstant() {
return variable.isConstant() && expression.isConstant();
}
@Override
public @NotNull String toString() {
return variable + " as " + patterns + " | " + expression;
}
}
@@ -1,73 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.impl.Generator;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.function.BinaryOperator;
import java.util.stream.Stream;
public record JQAssignment(@NotNull JQExpression target, @NotNull JQExpression value, @NotNull Operator operator) implements JQExpression {
public static @NotNull JQAssignment add(@NotNull JQExpression target, @NotNull JQExpression value) {
return new JQAssignment(target, value, Operator.ADD);
}
public static @NotNull JQAssignment sub(@NotNull JQExpression target, @NotNull JQExpression value) {
return new JQAssignment(target, value, Operator.SUB);
}
public static @NotNull JQAssignment mul(@NotNull JQExpression target, @NotNull JQExpression value) {
return new JQAssignment(target, value, Operator.MUL);
}
public static @NotNull JQAssignment div(@NotNull JQExpression target, @NotNull JQExpression value) {
return new JQAssignment(target, value, Operator.DIV);
}
public static @NotNull JQAssignment mod(@NotNull JQExpression target, @NotNull JQExpression value) {
return new JQAssignment(target, value, Operator.MOD);
}
public JQAssignment {
Objects.requireNonNull(target);
Objects.requireNonNull(value);
Objects.requireNonNull(operator);
}
public JQAssignment(@NotNull JQExpression target, @NotNull JQExpression value) {
this(target, value, Operator.ID);
}
@Override
public @NotNull Generator<JsonValue> evaluate(@NotNull Context context) {
throw new UnsupportedOperationException("not yet implemented");
}
@Override
public boolean isConstant() {
throw new UnsupportedOperationException("not yet implemented");
}
@Override
public @NotNull String toString() {
return STR."\{target} \{operator.symbol} \{value}";
}
@RequiredArgsConstructor
public enum Operator {
ID((_, value) -> value, "="),
ADD(JsonMath::add, "+="),
SUB(JsonMath::sub, "-="),
MUL(JsonMath::mul, "*="),
DIV(JsonMath::div, "/="),
MOD(JsonMath::mod, "%="),
;
private final @NotNull BinaryOperator<@Nullable JsonValue> operator;
private final @NotNull String symbol;
}
}
@@ -1,31 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.stream.Stream;
public record JQAssignmentCoerce(@NotNull JQExpression target, @NotNull JQExpression value) implements JQExpression {
public JQAssignmentCoerce {
Objects.requireNonNull(target);
Objects.requireNonNull(value);
}
@Override
public @NotNull Generator<JsonValue> evaluate(@NotNull Context context) {
throw new UnsupportedOperationException("not yet implemented");
}
@Override
public boolean isConstant() {
throw new UnsupportedOperationException("not yet implemented");
}
@Override
public @NotNull String toString() {
return target + " //= " + value;
}
}
@@ -1,30 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public record JQAssignmentPipe(@NotNull JQExpression target, @NotNull JQExpression value) implements JQExpression {
public JQAssignmentPipe {
Objects.requireNonNull(target);
Objects.requireNonNull(value);
}
@Override
public @NotNull Generator<JsonValue> evaluate(@NotNull Context context) {
throw new UnsupportedOperationException("not yet implemented");
}
@Override
public boolean isConstant() {
throw new UnsupportedOperationException("not yet implemented");
}
@Override
public @NotNull String toString() {
return target + " |= " + value;
}
}
@@ -1,105 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.impl.Generator;
import eu.jonahbauer.json.query.util.Util;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Objects;
import java.util.function.BinaryOperator;
public record JQBinaryExpression(@NotNull JQExpression first, @NotNull JQExpression second, @NotNull Operator operator) implements JQExpression {
public static @NotNull JQBinaryExpression add(@NotNull JQExpression first, @NotNull JQExpression second) {
return new JQBinaryExpression(first, second, Operator.ADD);
}
public static @NotNull JQBinaryExpression sub(@NotNull JQExpression first, @NotNull JQExpression second) {
return new JQBinaryExpression(first, second, Operator.SUB);
}
public static @NotNull JQBinaryExpression mul(@NotNull JQExpression first, @NotNull JQExpression second) {
return new JQBinaryExpression(first, second, Operator.MUL);
}
public static @NotNull JQBinaryExpression div(@NotNull JQExpression first, @NotNull JQExpression second) {
return new JQBinaryExpression(first, second, Operator.DIV);
}
public static @NotNull JQBinaryExpression mod(@NotNull JQExpression first, @NotNull JQExpression second) {
return new JQBinaryExpression(first, second, Operator.MOD);
}
public static @NotNull JQBinaryExpression eq(@NotNull JQExpression first, @NotNull JQExpression second) {
return new JQBinaryExpression(first, second, Operator.EQ);
}
public static @NotNull JQBinaryExpression neq(@NotNull JQExpression first, @NotNull JQExpression second) {
return new JQBinaryExpression(first, second, Operator.NEQ);
}
public static @NotNull JQBinaryExpression lt(@NotNull JQExpression first, @NotNull JQExpression second) {
return new JQBinaryExpression(first, second, Operator.LT);
}
public static @NotNull JQBinaryExpression gt(@NotNull JQExpression first, @NotNull JQExpression second) {
return new JQBinaryExpression(first, second, Operator.GT);
}
public static @NotNull JQBinaryExpression leq(@NotNull JQExpression first, @NotNull JQExpression second) {
return new JQBinaryExpression(first, second, Operator.LEQ);
}
public static @NotNull JQBinaryExpression geq(@NotNull JQExpression first, @NotNull JQExpression second) {
return new JQBinaryExpression(first, second, Operator.GEQ);
}
public JQBinaryExpression {
Objects.requireNonNull(first);
Objects.requireNonNull(second);
Objects.requireNonNull(operator);
}
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return Util.crossReversed(List.of(first, second), context)
.map(values -> operator.apply(values.getFirst(), values.getLast()));
}
@Override
public boolean isConstant() {
return first.isConstant() && second.isConstant();
}
@Override
public @NotNull String toString() {
return STR."\{first} \{operator.symbol} \{second}";
}
@RequiredArgsConstructor
public enum Operator implements BinaryOperator<@Nullable JsonValue> {
ADD(JsonMath::add, "+"),
SUB(JsonMath::sub, "-"),
MUL(JsonMath::mul, "*"),
DIV(JsonMath::div, "/"),
MOD(JsonMath::mod, "%"),
EQ(JsonMath::eq, "=="),
NEQ(JsonMath::neq, "!="),
LT(JsonMath::lt, "<"),
GT(JsonMath::gt, ">"),
LEQ(JsonMath::leq, "<="),
GEQ(JsonMath::geq, ">="),
;
private final @NotNull BinaryOperator<@Nullable JsonValue> operator;
private final @NotNull String symbol;
@Override
public @Nullable JsonValue apply(@Nullable JsonValue value, @Nullable JsonValue value2) {
return operator.apply(value, value2);
}
}
}
@@ -1,36 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonBoolean;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public record JQBooleanAndExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression {
public JQBooleanAndExpression {
Objects.requireNonNull(first);
Objects.requireNonNull(second);
}
@Override
public @NotNull Generator<JsonValue> evaluate(@NotNull Context context) {
return first.evaluate(context)
.flatMap(value -> JsonMath.isFalsy(value)
? Generator.of(JsonBoolean.FALSE)
: second.evaluate(context).map(JsonMath::isTruthy).map(JsonBoolean::valueOf)
);
}
@Override
public boolean isConstant() {
return first.isConstant() && second.isConstant();
}
@Override
public @NotNull String toString() {
return first + " and " + second;
}
}
@@ -1,37 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonBoolean;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.stream.Stream;
public record JQBooleanOrExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression {
public JQBooleanOrExpression {
Objects.requireNonNull(first);
Objects.requireNonNull(second);
}
@Override
public @NotNull Generator<JsonValue> evaluate(@NotNull Context context) {
return first.evaluate(context)
.flatMap(value -> JsonMath.isTruthy(value)
? Generator.of(JsonBoolean.TRUE)
: second.evaluate(context).map(JsonMath::isTruthy).map(JsonBoolean::valueOf)
);
}
@Override
public boolean isConstant() {
return first.isConstant() && second.isConstant();
}
@Override
public @NotNull String toString() {
return first + " or " + second;
}
}
@@ -1,402 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.*;
import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.JsonQueryException;
import eu.jonahbauer.json.query.impl.Generator;
import eu.jonahbauer.json.query.parser.JQParser;
import eu.jonahbauer.json.query.parser.ast.JQExpression.Context;
import eu.jonahbauer.json.query.util.Util;
import lombok.SneakyThrows;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
public enum JQBuiltIn implements JQInvocable {
ABS(0, Implementation.map$V(JsonMath::abs)),
ADD(0, (context, _) -> context.stream().map(value -> JsonMath.values(value).reduce(null, JsonMath::add))),
NOT(0, Implementation.map$V(JsonMath::not)),
// error handling
ERROR$0(0, (context, _) -> context.stream().flatMap(JsonMath::error)),
ERROR$1(1, (context, args) -> args.getFirst().evaluate(context).flatMap(JsonMath::error)),
HALT(0, (_, _) -> Generator.of((JsonValue) null).flatMap(_ -> JsonMath.halt())),
HALT_ERROR$0(0, (context, _) -> context.stream().flatMap(JsonMath::halt)),
HALT_ERROR$1(1, (context, args) -> args.getFirst().evaluate(context).flatMap(value -> JsonMath.halt(context.root(), value))),
// stream operations
EMPTY(0, (_, _) -> Generator.empty()),
RANGE$1(1, (context, args) -> args.getFirst().evaluate(context).flatMap(JsonMath::range)),
RANGE$2(2, (context, args) -> Util.cross(args, context).flatMap(bounds -> JsonMath.range(bounds.get(0), bounds.get(1)))),
RANGE$3(3, (context, args) -> Util.cross(args, context).flatMap(bounds -> JsonMath.range(bounds.get(0), bounds.get(1), bounds.get(2)))),
LIMIT(2, (context, args) -> args.getFirst().evaluate(context).flatMap(limit -> JsonMath.limit(args.getLast().evaluate(context), limit))),
FIRST$1(1, (context, args) -> JsonMath.first(args.getFirst().evaluate(context))),
LAST$1(1, (context, args) -> JsonMath.last(args.getFirst().evaluate(context))),
NTH$2(2, (context, args) -> args.getFirst().evaluate(context).flatMap(n -> JsonMath.nth(args.getLast().evaluate(context), n))),
SELECT(1, Implementation.mapF$F(JsonMath::select)),
// loops
RECURSE$0(0, Implementation.map$F(JsonMath::recurse)),
RECURSE$1(1, Implementation.mapF$F(JsonMath::recurse)),
RECURSE$2(2, Implementation.mapFF$F(JsonMath::recurse)),
WALK$1(1, Implementation.mapF$F(JsonMath::walk)),
WHILE(2, Implementation.mapFF$F(JsonMath::while_)),
UNTIL(2, Implementation.mapFF$F(JsonMath::until)),
// iterable operations
MAP(1, Implementation.mapF$V(JsonMath::map)),
MAP_VALUES(1, Implementation.mapF$V(JsonMath::mapValues)),
KEYS(0, Implementation.map$V(JsonMath::keys)),
KEYS_UNSORTED(0, Implementation.map$V(JsonMath::keysUnsorted)),
HAS(1, Implementation.mapV$V(JsonMath::has)),
IN(1, Implementation.mapV$V(JsonMath::in)),
FIRST$0(0, Implementation.map$V(JsonMath::first)),
LAST$0(0, Implementation.map$V(JsonMath::last)),
NTH$1(1, Implementation.mapV$V(JsonMath::index)),
ANY$0(0, Implementation.map$V(JsonMath::any)),
ANY$1(1, Implementation.mapF$V(JsonMath::any)),
ANY$2(2, Implementation.mapFF$V((root, gen, filter) -> JsonMath.any(gen.apply(root), filter))),
ALL$0(0, Implementation.map$V(JsonMath::all)),
ALL$1(1, Implementation.mapF$V(JsonMath::all)),
ALL$2(2, Implementation.mapFF$V((root, gen, filter) -> JsonMath.all(gen.apply(root), filter))),
FLATTEN$0(0, Implementation.map$V(JsonMath::flatten)),
FLATTEN$1(1, Implementation.mapV$V(JsonMath::flatten)),
SORT(0, Implementation.map$V(JsonMath::sort)),
SORT_BY(1, Implementation.mapF$V(JsonMath::sort)),
MIN(0, Implementation.map$V(JsonMath::min)),
MIN_BY(1, Implementation.mapF$V(JsonMath::min)),
MAX(0, Implementation.map$V(JsonMath::max)),
MAX_BY(1, Implementation.mapF$V(JsonMath::max)),
UNIQUE(0, Implementation.map$V(JsonMath::unique)),
UNIQUE_BY(1, Implementation.mapF$V(JsonMath::unique)),
GROUP_BY(1, Implementation.mapF$V(JsonMath::group)),
REVERSE(0, Implementation.map$V(JsonMath::reverse)),
CONTAINS(1, Implementation.mapV$V(JsonMath::contains)),
INDICES(1, Implementation.mapV$V(JsonMath::indices)),
INDEX(1, Implementation.mapV$V(JsonMath::firstindex)),
RINDEX(1, Implementation.mapV$V(JsonMath::lastindex)),
INSIDE(1, Implementation.mapV$V(JsonMath::inside)),
COMBINATIONS$0(0, Implementation.map$F(JsonMath::combinations)),
COMBINATIONS$1(1, Implementation.mapV$F(JsonMath::combinations)),
BSEARCH(1, Implementation.mapV$V(JsonMath::bsearch)),
TRANSPOSE(0, Implementation.map$V(JsonMath::transpose)),
TO_ENTRIES(0, Implementation.map$V(JsonMath::toEntries)),
FROM_ENTRIES(0, Implementation.map$V(JsonMath::fromEntries)),
WITH_ENTRIES(1, Implementation.mapF$V(JsonMath::withEntries)),
// filters
ARRAYS(0, Implementation.filter(JsonMath::isArray0)),
OBJECTS(0, Implementation.filter(JsonMath::isObject0)),
ITERABLES(0, Implementation.filter(JsonMath::isIterable0)),
BOOLEANS(0, Implementation.filter(JsonMath::isBoolean0)),
NUMBERS(0, Implementation.filter(JsonMath::isNumber0)),
NORMALS(0, Implementation.filter(JsonMath::isNormal0)),
FINITES(0, Implementation.filter(JsonMath::isFinite0)),
STRINGS(0, Implementation.filter(JsonMath::isString0)),
NULLS(0, Implementation.filter(JsonMath::isNull0)),
VALUES(0, Implementation.filter(JsonMath::isValue0)),
SCALARS(0, Implementation.filter(JsonMath::isScalar0)),
// checks
ISINFINITE(0, Implementation.map$V(JsonMath::isInfinite)),
ISNAN(0, Implementation.map$V(JsonMath::isNan)),
ISFINITE(0, Implementation.map$V(JsonMath::isFinite)),
ISNORMAL(0, Implementation.map$V(JsonMath::isNormal)),
ISEMPTY(1, (context, args) -> Generator.of(() -> JsonMath.isEmpty(args.getFirst().evaluate(context)))),
// string operations
TRIM(0, Implementation.map$V(JsonMath::trim)),
LTRIM(0, Implementation.map$V(JsonMath::ltrim)),
RTRIM(0, Implementation.map$V(JsonMath::rtrim)),
LTRIMSTR(1, Implementation.mapV$V(JsonMath::ltrimstr)),
RTRIMSTR(1, Implementation.mapV$V(JsonMath::rtrimstr)),
SPLIT$1(1, Implementation.mapV$V(JsonMath::split)),
JOIN(1, Implementation.mapV$V(JsonMath::join)),
IMPLODE(0, Implementation.map$V(JsonMath::implode)),
EXPLODE(0, Implementation.map$V(JsonMath::explode)),
ASCII_UPCASE(0, Implementation.map$V(JsonMath::asciiUpcase)),
ASCII_DOWNCASE(0, Implementation.map$V(JsonMath::asciiDowncase)),
UTF8BYTELENGTH(0, Implementation.map$V(JsonMath::utf8ByteLength)),
STARTSWITH(1, Implementation.mapV$V(JsonMath::startswith)),
ENDSWITH(1, Implementation.mapV$V(JsonMath::endswith)),
// regex
TEST$1(1, JsonMath::test),
TEST$2(2, JsonMath::test),
MATCH$1(1, JsonMath::match),
MATCH$2(2, JsonMath::match),
CAPTURE$1(1, JsonMath::capture),
CAPTURE$2(2, JsonMath::capture),
SCAN$1(1, JsonMath::scan),
SCAN$2(2, JsonMath::scan),
SPLIT$2(2, JsonMath::split),
SPLITS$1(1, JsonMath::splits),
SPLITS$2(2, JsonMath::splits),
SUB$2(2, JsonMath::sub),
SUB$3(3, JsonMath::sub),
GSUB$2(2, JsonMath::gsub),
GSUB$3(3, JsonMath::gsub),
// conversions
TYPE(0, Implementation.map$V(JsonMath::type)),
TOJSON(0, Implementation.map$V(JsonMath::tojson)),
TOSTRING(0, Implementation.map$V(JsonMath::tostring)),
TONUMBER(0, Implementation.map$V(JsonMath::tonumber)),
FROMJSON(0, Implementation.map$V(JsonMath::fromjson)),
// time
FROMDATEISO8601(0, Implementation.map$V(JsonMath::fromdateiso8601)),
FROMDATE(0, Implementation.map$V(JsonMath::fromdate)),
TODATEISO8601(0, Implementation.map$V(JsonMath::todateiso8601)),
TODATE(0, Implementation.map$V(JsonMath::todate)),
NOW(0, Implementation.map$V(_ -> JsonNumber.valueOf(System.currentTimeMillis()))),
// paths
GETPATH(1, Implementation.mapV$V(JsonMath::getpath)),
PATHS$0(0, Implementation.map$F(JsonMath::paths)),
PATHS$1(1, Implementation.mapF$F(JsonMath::paths)),
// misc
ENV(0, Implementation.map$V(_ -> JsonMath.env())),
LENGTH(0, Implementation.map$V(JsonMath::length)),
REPEAT(1, (context, args) -> JsonMath.repeat(() -> args.getFirst().evaluate(context))),
// math library
INFINITE(0, Implementation.map$V(_ -> JsonNumber.valueOf(Double.POSITIVE_INFINITY))),
NAN(0, Implementation.map$V(_ -> JsonNumber.valueOf(Double.NaN))),
ACOS(0, Implementation.map$V(JsonMath::acos)),
ACOSH(0, Implementation.map$V(JsonMath::acosh)),
ASIN(0, Implementation.map$V(JsonMath::asin)),
ASINH(0, Implementation.map$V(JsonMath::asinh)),
ATAN(0, Implementation.map$V(JsonMath::atan)),
ATANH(0, Implementation.map$V(JsonMath::atanh)),
CBRT(0, Implementation.map$V(JsonMath::cbrt)),
CEIL(0, Implementation.map$V(JsonMath::ceil)),
COS(0, Implementation.map$V(JsonMath::cos)),
COSH(0, Implementation.map$V(JsonMath::cosh)),
ERF(0, Implementation.map$V(JsonMath::erf)),
ERFC(0, Implementation.map$V(JsonMath::erfc)),
EXP(0, Implementation.map$V(JsonMath::exp)),
EXP10(0, Implementation.map$V(JsonMath::exp10)),
EXP2(0, Implementation.map$V(JsonMath::exp2)),
EXPM1(0, Implementation.map$V(JsonMath::expm1)),
FABS(0, Implementation.map$V(JsonMath::fabs)),
FLOOR(0, Implementation.map$V(JsonMath::floor)),
GAMMA(0, Implementation.map$V(JsonMath::gamma)),
J0(0, Implementation.map$V(JsonMath::j0)),
J1(0, Implementation.map$V(JsonMath::j1)),
LGAMMA(0, Implementation.map$V(JsonMath::lgamma)),
LOG(0, Implementation.map$V(JsonMath::log)),
LOG10(0, Implementation.map$V(JsonMath::log10)),
LOG1P(0, Implementation.map$V(JsonMath::log1p)),
LOG2(0, Implementation.map$V(JsonMath::log2)),
LOGB(0, Implementation.map$V(JsonMath::logb)),
NEARBYINT(0, Implementation.map$V(JsonMath::nearbyint)),
RINT(0, Implementation.map$V(JsonMath::rint)),
ROUND(0, Implementation.map$V(JsonMath::round)),
SIGNIFICAND(0, Implementation.map$V(JsonMath::significand)),
SIN(0, Implementation.map$V(JsonMath::sin)),
SINH(0, Implementation.map$V(JsonMath::sinh)),
SQRT(0, Implementation.map$V(JsonMath::sqrt)),
TAN(0, Implementation.map$V(JsonMath::tan)),
TANH(0, Implementation.map$V(JsonMath::tanh)),
TGAMMA(0, Implementation.map$V(JsonMath::tgamma)),
TRUNC(0, Implementation.map$V(JsonMath::trunc)),
Y0(0, Implementation.map$V(JsonMath::y0)),
Y1(0, Implementation.map$V(JsonMath::y1)),
ATAN2(2, Implementation.mapVV$V((_, a, b) -> JsonMath.atan2(a, b))),
COPYSIGN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.copysign(a, b))),
DREM(2, Implementation.mapVV$V((_, a, b) -> JsonMath.drem(a, b))),
FDIM(2, Implementation.mapVV$V((_, a, b) -> JsonMath.fdim(a, b))),
FMAX(2, Implementation.mapVV$V((_, a, b) -> JsonMath.fmax(a, b))),
FMIN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.fmin(a, b))),
FMOD(2, Implementation.mapVV$V((_, a, b) -> JsonMath.fmod(a, b))),
FREXP(2, Implementation.mapVV$V((_, a, b) -> JsonMath.frexp(a, b))),
HYPOT(2, Implementation.mapVV$V((_, a, b) -> JsonMath.hypot(a, b))),
JN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.jn(a, b))),
LDEXP(2, Implementation.mapVV$V((_, a, b) -> JsonMath.ldexp(a, b))),
MODF(2, Implementation.mapVV$V((_, a, b) -> JsonMath.modf(a, b))),
NEXTAFTER(2, Implementation.mapVV$V((_, a, b) -> JsonMath.nextafter(a, b))),
NEXTTOWARD(2, Implementation.mapVV$V((_, a, b) -> JsonMath.nexttoward(a, b))),
POW(2, Implementation.mapVV$V((_, a, b) -> JsonMath.pow(a, b))),
REMAINDER(2, Implementation.mapVV$V((_, a, b) -> JsonMath.remainder(a, b))),
SCALB(2, Implementation.mapVV$V((_, a, b) -> JsonMath.scalb(a, b))),
SCALBLN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.scalbln(a, b))),
YN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.yn(a, b))),
FMA(3, (context, args) -> Util.cross(args, context).map(values -> JsonMath.fma(values.get(0), values.get(1), values.get(2))))
;
public static final @NotNull Map<@NotNull String, @NotNull JQInvocable> ALL_BUILTINS = Arrays.stream(JQBuiltIn.values())
.collect(Collectors.toMap(
JQInvocable::reference,
Function.identity()
));
private final @NotNull JQInvocable implementation;
JQBuiltIn(@NotNull String body) {
this(List.of(), parse(body));
}
JQBuiltIn(@NotNull String arg0, @NotNull String body) {
this(List.of(arg0), parse(body));
}
JQBuiltIn(@NotNull String arg0, @NotNull String arg1, @NotNull String body) {
this(List.of(arg0, arg1), parse(body));
}
JQBuiltIn(@NotNull String arg0, @NotNull String arg1, @NotNull String arg2, @NotNull String body) {
this(List.of(arg0, arg1, arg2), parse(body));
}
JQBuiltIn(@NotNull List<@NotNull String> params, @NotNull JQExpression body) {
this.implementation = new JQFunction(getIdentifier(name()), params, body);
}
JQBuiltIn(int arity, @NotNull Implementation implementation) {
this.implementation = new InvocableDelegate(getIdentifier(name()), arity, implementation);
}
@SneakyThrows(IOException.class)
private static @NotNull JQExpression parse(@NotNull String expression) {
return new JQParser(expression).parseExpression();
}
private static @NotNull String getIdentifier(@NotNull String name) {
var identifier = name.toLowerCase(Locale.ROOT);
var idx = identifier.lastIndexOf("$");
if (idx != -1) identifier = identifier.substring(0, idx);
return identifier;
}
@Override
public @NotNull Generator<@Nullable JsonValue> invoke(@NotNull Context context, @NotNull List<@NotNull JQExpression> args) {
return implementation.invoke(context, args);
}
@Override
public @NotNull String identifier() {
return implementation.identifier();
}
@Override
public int arity() {
return implementation.arity();
}
private record InvocableDelegate(
@NotNull String identifier, int arity,
@NotNull Implementation delegate
) implements JQInvocable {
@Override
public @NotNull Generator<@Nullable JsonValue> invoke(@NotNull Context context, @NotNull List<@NotNull JQExpression> args) {
if (args.size() != arity) throw new JsonQueryException("invalid argument count");
return delegate.invoke(context, args);
}
}
@FunctionalInterface
private interface Implementation {
@NotNull Generator<@Nullable JsonValue> invoke(@NotNull Context context, @NotNull List<@NotNull JQExpression> args);
// format: map<args>$<return>
// where <args> and <return> use V for value, F for filter
/**
* Produces a zero-arg implementation applying the function to the current value.
*/
static @NotNull Implementation map$F(@NotNull Function<? super @Nullable JsonValue, ? extends @NotNull Generator<? extends @Nullable JsonValue>> operator) {
return (context, _) -> context.stream().flatMap(operator);
}
/**
* Produces a zero-arg implementation applying the function to the current value.
*/
static @NotNull Implementation map$V(@NotNull Function<? super @Nullable JsonValue, ? extends @Nullable JsonValue> operator) {
return (context, _) -> context.stream().map(operator);
}
/**
* Produces a one-arg implementation applying the function to the current value and the first argument
* (passed by value).
*/
static @NotNull Implementation mapV$V(@NotNull BiFunction<? super @Nullable JsonValue, ? super @Nullable JsonValue, ? extends @Nullable JsonValue> function) {
return (context, args) -> {
var arg = args.getFirst();
return context.stream().flatMap(root -> arg.evaluate(context).map(value -> function.apply(root, value)));
};
}
/**
* Produces a one-arg implementation applying the function to the current value and the first argument
* (passed by value).
*/
static @NotNull Implementation mapV$F(@NotNull BiFunction<? super @Nullable JsonValue, ? super @Nullable JsonValue, ? extends @NotNull Generator<? extends @Nullable JsonValue>> function) {
return (context, args) -> {
var arg = args.getFirst();
return context.stream().flatMap(root -> arg.evaluate(context).flatMap(value -> function.apply(root, value)));
};
}
/**
* Produces a one-arg implementation applying the function to the current value and the first argument
* (passed as filter).
*/
static @NotNull Implementation mapF$F(@NotNull BiFunction<? super @Nullable JsonValue, ? super @NotNull JQFilter, ? extends @NotNull Generator<? extends @Nullable JsonValue>> function) {
return (context, args) -> {
var arg = args.getFirst().bind(context);
return context.stream().flatMap(root -> function.apply(root, arg));
};
}
/**
* Produces a one-arg implementation applying the function to the current value and the first argument
* (passed as filter).
*/
static @NotNull Implementation mapF$V(@NotNull BiFunction<? super @Nullable JsonValue, ? super @NotNull JQFilter, ? extends @Nullable JsonValue> function) {
return (context, args) -> {
var arg = args.getFirst().bind(context);
return context.stream().map(root -> function.apply(root, arg));
};
}
static @NotNull Implementation mapVV$V(@NotNull TriFunction<? super @Nullable JsonValue, ? super @Nullable JsonValue, ? super @Nullable JsonValue, ? extends @Nullable JsonValue> function) {
return (context, args) -> {
var arg0 = args.get(0);
var arg1 = args.get(1);
return context.stream().flatMap(root -> arg0.evaluate(context).flatMap(value0 -> arg1.evaluate(context).map(value1 -> function.apply(root, value0, value1))));
};
}
static @NotNull Implementation mapFF$V(@NotNull TriFunction<? super @Nullable JsonValue, ? super @NotNull JQFilter, ? super @NotNull JQFilter, ? extends @Nullable JsonValue> function) {
return (context, args) -> {
var arg0 = args.get(0).bind(context);
var arg1 = args.get(1).bind(context);
return context.stream().map(root -> function.apply(root, arg0, arg1));
};
}
static @NotNull Implementation mapFF$F(@NotNull TriFunction<? super @Nullable JsonValue, ? super @NotNull JQFilter, ? super @NotNull JQFilter, ? extends @NotNull Generator<? extends @Nullable JsonValue>> function) {
return (context, args) -> {
var arg0 = args.get(0).bind(context);
var arg1 = args.get(1).bind(context);
return context.stream().flatMap(root -> function.apply(root, arg0, arg1));
};
}
static @NotNull Implementation filter(@NotNull Predicate<@Nullable JsonValue> predicate) {
return (context, _) -> context.stream().filter(predicate);
}
}
@FunctionalInterface
private interface TriFunction<S, T, U, R> {
R apply(S s, T t, U u);
}
}
@@ -1,30 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public record JQCommaExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression {
public JQCommaExpression {
Objects.requireNonNull(first);
Objects.requireNonNull(second);
}
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return Generator.concat(first.evaluate(context), second.evaluate(context));
}
@Override
public boolean isConstant() {
return first.isConstant() && second.isConstant();
}
@Override
public @NotNull String toString() {
return first + ", " + second;
}
}
@@ -1,24 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public record JQConstant(@Nullable JsonValue value) implements JQExpression {
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return Generator.of(value);
}
@Override
public boolean isConstant() {
return true;
}
@Override
public @NotNull String toString() {
return JsonValue.toJsonString(value);
}
}
@@ -1,88 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.JsonQueryException;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public interface JQExpression {
@NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context);
boolean isConstant();
@NotNull String toString();
default @NotNull JQFilter bind(@NotNull Context context) {
return value -> evaluate(context.withRoot(value));
}
record Context(
@Nullable JsonValue root,
@NotNull Map<@NotNull String, @Nullable JsonValue> variables,
@NotNull Map<@NotNull String, @NotNull JQInvocable> functions
) {
public Context(@Nullable JsonValue root) {
this(root, Map.of("$ENV", JsonMath.env()), JQBuiltIn.ALL_BUILTINS);
}
public Context {
var map = new LinkedHashMap<String, @Nullable JsonValue>();
variables.forEach((key, value) -> {
Objects.requireNonNull(key);
if (!key.startsWith("$")) throw new IllegalArgumentException();
map.put(key, value);
});
variables = Collections.unmodifiableMap(map);
functions = Map.copyOf(functions);
}
public @NotNull JQInvocable function(@NotNull String name, int arity) {
var out = functions.get(name + "/" + arity);
if (out == null) throw new JsonQueryException(name + "/" + arity + " is not defined.");
return out;
}
public @Nullable JsonValue variable(@NotNull String name) {
if (!variables.containsKey(name)) throw new JsonQueryException(name + " is not defined.");
return variables.get(name);
}
public @NotNull Context withRoot(@Nullable JsonValue root) {
return new Context(root, variables, functions);
}
public @NotNull Context withFunction(@NotNull JQInvocable function) {
var f = new HashMap<>(functions);
f.put(function.reference(), function);
return new Context(root, variables, f);
}
public @NotNull Context withFunctions(@NotNull List<? extends @NotNull JQInvocable> functions) {
var f = new HashMap<>(this.functions);
functions.forEach(func -> f.put(func.reference(), func));
return new Context(root, variables, f);
}
public @NotNull Context withVariable(@NotNull String name, @Nullable JsonValue variable) {
var v = new HashMap<>(variables);
v.put(name, variable);
return new Context(root, v, functions);
}
public @NotNull Context withVariables(@NotNull Map<@NotNull String, @Nullable JsonValue> variables) {
var v = new HashMap<>(this.variables);
v.putAll(variables);
return new Context(root, v, functions);
}
public @NotNull Generator<@Nullable JsonValue> stream() {
return Generator.of(root());
}
}
}
@@ -1,12 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Function;
@FunctionalInterface
public interface JQFilter extends Function<@Nullable JsonValue, @NotNull Generator<@Nullable JsonValue>> {
}
@@ -1,42 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.NoSuchElementException;
public record JQForEachExpression(
@NotNull JQExpression expression,
@NotNull JQPatterns patterns,
@NotNull JQExpression init,
@NotNull JQExpression update,
@NotNull JQExpression extract
) implements JQExpression {
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return init.evaluate(context).flatMap(initial -> {
var state = new Object() {
private JsonValue state = initial;
};
return expression.evaluate(context).flatMap(value -> patterns.bind(context, value, ctx -> {
return update.evaluate(ctx.withRoot(state.state)).map(v -> {
state.state = v;
return ctx.withRoot(v);
}).flatMap(extract::evaluate);
}));
});
}
@Override
public boolean isConstant() {
return expression.isConstant() && init.isConstant() && update.isConstant();
}
@Override
public @NotNull String toString() {
return "foreach " + expression + " as " + patterns + " (" + init + "; " + update + "; " + extract + ")";
}
}
@@ -1,52 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonQueryException;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public record JQFunction(@NotNull String identifier, @NotNull List<@NotNull String> params, @NotNull JQExpression body) implements JQInvocable {
public JQFunction {
Objects.requireNonNull(identifier);
Objects.requireNonNull(body);
params = List.copyOf(params);
}
@Override
public @NotNull Generator<@Nullable JsonValue> invoke(@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> arguments) {
if (arguments.size() != params.size()) throw new JsonQueryException("invalid argument count");
var expression = body;
var functions = new ArrayList<JQFunction>();
for (int i = params.size() - 1; i >= 0; i--) {
String param = params.get(i);
if (param.startsWith("$")) {
expression = new JQAsExpression(
new JQFunctionInvocation(param.substring(1), List.of()),
new JQPatterns(List.of(new JQPatterns.Pattern.ValuePattern(param))),
expression
);
param = param.substring(1);
}
functions.add(new JQFunction(param, List.of(), arguments.get(i)));
}
return expression.evaluate(context.withFunctions(functions));
}
@Override
public int arity() {
return params().size();
}
@Override
public @NotNull String toString() {
return "def " + identifier + (params.isEmpty() ? "" : String.join("; ", params)) + ": " + body + ";";
}
}
@@ -1,18 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public record JQFunctionDefinition(@NotNull JQFunction function, @NotNull JQExpression expression) implements JQExpression {
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return expression.evaluate(context.withFunction(function));
}
@Override
public boolean isConstant() {
return expression.isConstant();
}
}
@@ -1,36 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;
public record JQFunctionInvocation(@NotNull String name, @NotNull List<@NotNull JQExpression> args) implements JQExpression {
public JQFunctionInvocation {
Objects.requireNonNull(name);
args = List.copyOf(args);
}
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
var function = context.function(name, args.size());
return function.invoke(context, args);
}
@Override
public boolean isConstant() {
return false;
}
@Override
public @NotNull String toString() {
var out = new StringJoiner("; ", name + "(", ")");
out.setEmptyValue(name);
for (var arg : args) out.add(arg.toString());
return out.toString();
}
}
@@ -1,44 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public record JQIfExpression(@NotNull JQExpression condition, @NotNull JQExpression then, @Nullable JQExpression otherwise) implements JQExpression {
public JQIfExpression {
Objects.requireNonNull(condition);
Objects.requireNonNull(then);
}
public JQIfExpression(@NotNull JQExpression condition, @NotNull JQExpression then) {
this(condition, then, null);
}
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return condition.evaluate(context).flatMap(result -> JsonMath.isTruthy(result)
? then.evaluate(context)
: (otherwise != null ? otherwise.evaluate(context) : Generator.empty())
);
}
@Override
public boolean isConstant() {
return condition.isConstant() && then.isConstant() && (otherwise == null || otherwise.isConstant());
}
@Override
public @NotNull String toString() {
if (otherwise instanceof JQIfExpression) {
return "if " + condition + " then " + then + " el" + otherwise;
} else if (otherwise != null) {
return "if " + condition + " then " + then + " else " + otherwise + " end";
} else {
return "if " + condition + " then " + then + " end";
}
}
}
@@ -1,11 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public record JQImport(
@NotNull JQStringInterpolation path,
@Nullable String as,
@Nullable JQExpression metadata
) {
}
@@ -1,52 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonNumber;
import eu.jonahbauer.json.JsonString;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.JsonQueryException;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public record JQIndexExpression(@NotNull JQExpression expression, @NotNull JQExpression index, boolean optional) implements JQExpression {
public JQIndexExpression {
Objects.requireNonNull(expression);
Objects.requireNonNull(index);
}
public JQIndexExpression(@NotNull JQExpression expression, @Nullable JsonValue index, boolean optional) {
this(expression, new JQConstant(index), optional);
}
public JQIndexExpression(@NotNull JQExpression expression, double index, boolean optional) {
this(expression, new JsonNumber(index), optional);
}
public JQIndexExpression(@NotNull JQExpression expression, @NotNull String index, boolean optional) {
this(expression, new JsonString(index), optional);
}
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return expression.evaluate(context).flatMap(value -> index.evaluate(context).mapMulti((index, downstream) -> {
try {
downstream.accept(JsonMath.index(value, index));
} catch (JsonQueryException ex) {
if (!optional) throw ex;
}
}));
}
@Override
public boolean isConstant() {
return expression.isConstant() && index.isConstant();
}
@Override
public @NotNull String toString() {
return expression + "[" + index + "]" + (optional ? "?" : "");
}
}
@@ -1,19 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public interface JQInvocable {
@NotNull
Generator<@Nullable JsonValue> invoke(@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args);
int arity();
@NotNull String identifier();
default @NotNull String reference() {
return identifier() + "/" + arity();
}
}
@@ -1,30 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public record JQIterateExpression(@NotNull JQExpression expression, boolean optional) implements JQExpression {
public JQIterateExpression {
Objects.requireNonNull(expression);
}
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return expression.evaluate(context).flatMap(value -> JsonMath.values(value, optional));
}
@Override
public boolean isConstant() {
return expression.isConstant();
}
@Override
public @NotNull String toString() {
return expression + "[]" + (optional ? "?" : "");
}
}
@@ -1,29 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonNumber;
import eu.jonahbauer.json.JsonObject;
import eu.jonahbauer.json.JsonString;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public record JQLocExpression(@NotNull String file, int line) implements JQExpression {
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return Generator.of(JsonObject.of(
"file", new JsonString(file),
"line", new JsonNumber(line)
));
}
@Override
public boolean isConstant() {
return true;
}
@Override
public @NotNull String toString() {
return "$__loc__";
}
}
@@ -1,11 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public record JQModule(@NotNull JQExpression metadata) {
public JQModule {
Objects.requireNonNull(metadata);
}
}
@@ -1,31 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.stream.Stream;
public record JQNegation(@NotNull JQExpression expression) implements JQExpression {
public JQNegation {
Objects.requireNonNull(expression);
}
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return expression.evaluate(context).map(JsonMath::neg);
}
@Override
public boolean isConstant() {
return expression.isConstant();
}
@Override
public @NotNull String toString() {
return "-" + expression;
}
}
@@ -1,52 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonObject;
import eu.jonahbauer.json.JsonString;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonQueryException;
import eu.jonahbauer.json.query.impl.Generator;
import eu.jonahbauer.json.query.util.Util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.StringJoiner;
public record JQObjectConstructionExpression(@NotNull List<@NotNull Entry> entries) implements JQExpression {
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
var generator = Generator.of(new LinkedHashMap<@NotNull String, @Nullable JsonValue>());
for (var entry : entries) {
generator = generator.flatMap(map -> entry.key().evaluate(context)
.flatMap(key -> {
if (!(key instanceof JsonString(var string))) {
throw new JsonQueryException("Cannot use " + Util.type(key) + "(" + Util.value(key) + ") as object key.");
}
return entry.value().evaluate(context)
.map(value -> {
var out = new LinkedHashMap<>(map);
out.put(string, value);
return out;
});
})
);
}
return generator.map(JsonObject::new);
}
@Override
public boolean isConstant() {
return false;
}
@Override
public @NotNull String toString() {
var out = new StringJoiner(", ", "{", "}");
entries.forEach(entry -> out.add(entry.key() + ": " + entry.value()));
return out.toString();
}
public record Entry(@NotNull JQExpression key, @NotNull JQExpression value) { }
}
@@ -1,30 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public record JQParenthesizedExpression(@NotNull JQExpression expression) implements JQExpression {
public JQParenthesizedExpression {
Objects.requireNonNull(expression);
}
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return expression.evaluate(context);
}
@Override
public boolean isConstant() {
return expression.isConstant();
}
@Override
public @NotNull String toString() {
return "(" + expression + ")";
}
}
@@ -1,206 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonNumber;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.JsonQueryException;
import eu.jonahbauer.json.query.impl.Generator;
import eu.jonahbauer.json.query.parser.ast.JQExpression.Context;
import eu.jonahbauer.json.query.util.Util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public record JQPatterns(@NotNull List<@NotNull Pattern> patterns) {
public JQPatterns {
patterns = List.copyOf(patterns);
if (patterns.isEmpty()) throw new IllegalArgumentException();
}
public @NotNull Generator<@Nullable JsonValue> bind(@NotNull Context context, @Nullable JsonValue value, Function<@NotNull Context, @NotNull Generator<@Nullable JsonValue>> downstream) {
return new PatternGeneratorImpl(patterns, context, value, downstream);
}
private static final class PatternGeneratorImpl implements Generator<@Nullable JsonValue> {
// one generator per pattern
private final @NotNull Generator<@NotNull Generator<@NotNull Map<@NotNull String, @Nullable JsonValue>>> patterns;
private final @NotNull Context context;
private final @NotNull Function<@NotNull Context, @NotNull Generator<@Nullable JsonValue>> downstream;
// the generator for the current pattern
private @Nullable Generator<@Nullable JsonValue> current;
public PatternGeneratorImpl(
@NotNull List<@NotNull Pattern> patterns, @NotNull Context context, @Nullable JsonValue value,
@NotNull Function<@NotNull Context, @NotNull Generator<@Nullable JsonValue>> downstream
) {
this.context = context;
this.downstream = downstream;
var variables = new HashMap<String, JsonValue>();
patterns.stream().map(Pattern::variables).flatMap(Collection::stream).forEach(key -> variables.put(key, null));
this.patterns = Generator.from(patterns).map(pattern -> pattern.bind(context, value).map(vars -> {
var out = new HashMap<>(variables);
out.putAll(vars);
return out;
}));
}
private <T> T advance(@NotNull Function<Generator<@Nullable JsonValue>, T> function) {
var ex = new JsonQueryException("no match");
while (true) {
if (current == null) {
// find the next matching pattern
while (patterns.hasNext()) {
try {
var match = patterns.next();
current = match.map(context::withVariables).flatMap(downstream);
break;
} catch (JsonQueryException e) {
ex = e;
}
}
// no matching pattern; propagate exception from last match attempt
if (current == null) {
throw ex;
}
}
try {
return function.apply(current);
} catch (JsonQueryException e) {
// exception during execution; try the next pattern
current = null;
ex = e;
}
}
}
@Override
public boolean hasNext() {
return advance(Generator::hasNext);
}
@Override
public @Nullable JsonValue next() throws NoSuchElementException {
return advance(Generator::next);
}
}
@Override
public @NotNull String toString() {
return patterns.stream().map(Objects::toString).collect(Collectors.joining(" ?// "));
}
public sealed interface Pattern {
@NotNull
Set<@NotNull String> variables();
@NotNull
Generator<Map<@NotNull String, @Nullable JsonValue>> bind(@NotNull Context context, @Nullable JsonValue value);
record ValuePattern(@NotNull String name) implements Pattern {
public ValuePattern {
Objects.requireNonNull(name);
if (!name.startsWith("$")) throw new IllegalArgumentException();
}
@Override
public @NotNull Set<@NotNull String> variables() {
return Set.of(name);
}
@Override
public @NotNull Generator<Map<@NotNull String, @Nullable JsonValue>> bind(@NotNull Context context, @Nullable JsonValue value) {
var map = new HashMap<String, JsonValue>();
map.put(name, value);
return Generator.of(Collections.unmodifiableMap(map));
}
@Override
public @NotNull String toString() {
return name;
}
}
record ArrayPattern(@NotNull List<@NotNull Pattern> patterns) implements Pattern {
public ArrayPattern {
patterns = List.copyOf(patterns);
if (patterns.isEmpty()) throw new IllegalArgumentException();
}
@Override
public @NotNull Set<@NotNull String> variables() {
var out = new HashSet<String>();
patterns.forEach(p -> out.addAll(p.variables()));
return Collections.unmodifiableSet(out);
}
@Override
public @NotNull Generator<Map<@NotNull String, @Nullable JsonValue>> bind(@NotNull Context context, @Nullable JsonValue value) {
var streams = new ArrayList<Supplier<Generator<Map<String, JsonValue>>>>();
for (int i = 0; i < patterns.size(); i++) {
var k = new JsonNumber(i);
var v = JsonMath.index(value, k);
var pattern = patterns.get(i);
streams.add(() -> pattern.bind(context, v));
}
return Util.crossReversed(streams).map(list -> {
var map = new HashMap<String, JsonValue>();
list.forEach(map::putAll);
return map;
});
}
@Override
public @NotNull String toString() {
return patterns.stream().map(Objects::toString).collect(Collectors.joining(", ", "[", "]"));
}
}
record ObjectPattern(@NotNull SequencedMap<@NotNull JQExpression, @NotNull Pattern> patterns) implements Pattern {
public ObjectPattern {
patterns = Collections.unmodifiableSequencedMap(new LinkedHashMap<>(patterns));
if (patterns.isEmpty()) throw new IllegalArgumentException();
}
@Override
public @NotNull Set<@NotNull String> variables() {
var out = new HashSet<String>();
patterns.values().forEach(p -> out.addAll(p.variables()));
return Collections.unmodifiableSet(out);
}
@Override
public @NotNull Generator<Map<@NotNull String, @Nullable JsonValue>> bind(@NotNull Context context, @Nullable JsonValue value) {
var streams = new ArrayList<Supplier<Generator<Map<String, JsonValue>>>>();
this.patterns.reversed().forEach((keyExpression, pattern) -> streams.add(() -> {
var keyStream = keyExpression.evaluate(context);
return keyStream.flatMap(key -> pattern.bind(context, JsonMath.index(value, key)));
}));
return Util.cross(streams).map(list -> {
var map = new HashMap<String, JsonValue>();
list.reversed().forEach(map::putAll);
return map;
});
}
@Override
public @NotNull String toString() {
var out = new StringJoiner(", ", "{", "}");
patterns.forEach((key, pattern) -> out.add(key + ": " + pattern));
return out.toString();
}
}
}
}
@@ -1,31 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.stream.Stream;
public record JQPipeExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression {
public JQPipeExpression {
Objects.requireNonNull(first);
Objects.requireNonNull(second);
}
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return first.evaluate(context).flatMap(value -> second.evaluate(context.withRoot(value)));
}
@Override
public boolean isConstant() {
return first.isConstant() && second.isConstant();
}
@Override
public @NotNull String toString() {
return first + " | " + second;
}
}
@@ -1,20 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public record JQProgram(
@Nullable JQModule module,
@NotNull List<@NotNull JQImport> imports,
@NotNull List<@NotNull JQFunction> functions,
@Nullable JQExpression expression
) {
public @NotNull Generator<@Nullable JsonValue> run(@Nullable JsonValue value) {
if (expression == null) return Generator.empty();
return expression.evaluate(new JQExpression.Context(value).withFunctions(functions));
}
}
@@ -1,28 +0,0 @@
package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonArray;
import eu.jonahbauer.json.JsonObject;
import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public record JQRecursionExpression() implements JQExpression {
@Override
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return recurse(context.root());
}
private @NotNull Generator<@Nullable JsonValue> recurse(@Nullable JsonValue value) {
return switch (value) {
case JsonArray array -> Generator.concat(Generator.of(array), Generator.from(array).flatMap(this::recurse));
case JsonObject object -> Generator.concat(Generator.of(object), Generator.from(object.values()).flatMap(this::recurse));
case null, default -> Generator.of(value);
};
}
@Override
public boolean isConstant() {
return false;
}
}

Some files were not shown because too many files have changed in this diff Show More