Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a5d1492a5 | |||
| 92a20c82f8 | |||
| 375382f04e |
@@ -1,27 +1,2 @@
|
|||||||
# json
|
# 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!"
|
|
||||||
// }
|
|
||||||
```
|
|
||||||
@@ -8,18 +8,20 @@ description = "json"
|
|||||||
|
|
||||||
java {
|
java {
|
||||||
toolchain {
|
toolchain {
|
||||||
languageVersion = JavaLanguageVersion.of(22)
|
version = 21
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(libs.annotations)
|
api(libs.annotations)
|
||||||
|
|
||||||
compileOnly(libs.lombok)
|
compileOnly(libs.lombok)
|
||||||
annotationProcessor(libs.lombok)
|
annotationProcessor(libs.lombok)
|
||||||
|
testImplementation(libs.bundles.junit)
|
||||||
testImplementation(libs.junit.jupiter)
|
|
||||||
testRuntimeOnly(libs.junit.platform.launcher)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
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 @@
|
|||||||
["��"]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
["������"]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
["������"]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
["��"]
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[a�]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[�]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[123�]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[1e1�]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[0�]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[1e�]
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
{"�":"0",}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
["\u�"]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
["\�"]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
�{}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
�
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
�
|
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
[versions]
|
[versions]
|
||||||
annotations = "24.1.0"
|
annotations = "24.1.0"
|
||||||
junit = "5.12.1"
|
junit = "5.10.1"
|
||||||
junit-launcher = "1.12.1"
|
lombok = "1.18.30"
|
||||||
lombok = "1.18.32"
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" }
|
annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" }
|
||||||
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
|
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" }
|
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
|
||||||
lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" }
|
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
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-30
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-52
@@ -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) { }
|
|
||||||
}
|
|
||||||
-30
@@ -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
Reference in New Issue
Block a user