commit 3ee456bd8ff924bc3d9c5fca726cb9a45d8c5bfb Author: jbb01 <32650546+jbb01@users.noreply.github.com> Date: Sat Apr 12 16:27:57 2025 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c02962 --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +# ---> Gradle +.gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Avoid ignore Gradle wrappper properties +!gradle-wrapper.properties + +# Cache of project +.gradletasknamecache + +# Eclipse Gradle plugin generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# ---> Java +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + diff --git a/README.md b/README.md new file mode 100644 index 0000000..8f20f2c --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# json + diff --git a/core/build.gradle.kts b/core/build.gradle.kts new file mode 100644 index 0000000..06e9fe9 --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + `java-library` +} + +group = "eu.jonahbauer" +version = "1.0-SNAPSHOT" +description = "json" + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(22) + } +} + +dependencies { + api(libs.annotations) + + compileOnly(libs.lombok) + annotationProcessor(libs.lombok) +} + +tasks { + withType { + options.encoding = "UTF-8" + options.compilerArgs.add("--enable-preview") + } +} \ No newline at end of file diff --git a/core/src/main/java/eu/jonahbauer/json/JsonArray.java b/core/src/main/java/eu/jonahbauer/json/JsonArray.java new file mode 100644 index 0000000..20fb659 --- /dev/null +++ b/core/src/main/java/eu/jonahbauer/json/JsonArray.java @@ -0,0 +1,394 @@ +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 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()); + } + + // + /** + * 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(); + 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(); + 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(); + 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(); + 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(); + 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()); + } + // + + // + @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) elements.iterator(); + } + + @Override + public Object @NotNull[] toArray() { + return elements.toArray(); + } + + @Override + public 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 c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(int index, @NotNull Collection 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) elements.listIterator(); + } + + @Override + @SuppressWarnings("unchecked") // elements is immutable + public @NotNull ListIterator<@Nullable JsonValue> listIterator(int index) { + return (ListIterator) 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())); + } + + // + + // + /** + * {@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 @NotNull T get(int index, @NotNull Class type) { + var value = elements().get(index); + if (value == null) { + throw new NullPointerException("Value at index" + index + " is null."); + } else { + return type.cast(value); + } + } + // + + @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() { + if (size() < 2) return toJsonString(); + + var out = new StringJoiner(",\n", "[\n", "\n]"); + elements().forEach(e -> out.add(indent(JsonValue.toPrettyJsonString(e)))); + return out.toString(); + } + + private static @NotNull String indent(@NotNull String string) { + if (string.isEmpty()) return ""; + + StringBuilder out = new StringBuilder(); + + Iterator it = string.lines().iterator(); + while (it.hasNext()) { + out.append(" ").append(it.next()); + if (it.hasNext()) out.append('\n'); + } + + return out.toString(); + } +} diff --git a/core/src/main/java/eu/jonahbauer/json/JsonBoolean.java b/core/src/main/java/eu/jonahbauer/json/JsonBoolean.java new file mode 100644 index 0000000..ad14f77 --- /dev/null +++ b/core/src/main/java/eu/jonahbauer/json/JsonBoolean.java @@ -0,0 +1,46 @@ +package eu.jonahbauer.json; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Java representation of a JSON boolean. + */ +@Getter +@Accessors(fluent = true) +@RequiredArgsConstructor +public enum JsonBoolean implements JsonValue { + TRUE(true), + FALSE(false), + ; + + /** + * Converts the given boolean to a JSON boolean. + * @param bool a boolean + * @return a JSON boolean + */ + public static @NotNull JsonBoolean valueOf(boolean bool) { + return bool ? TRUE : FALSE; + } + + /** + * Converts the given boolean to a JSON boolean. + * @param bool a boolean + * @return a JSON boolean + */ + @Contract("null -> null; !null -> !null") + public static @Nullable JsonBoolean valueOf(@Nullable Boolean bool) { + return bool == null ? null : valueOf((boolean) bool); + } + + private final boolean value; + + @Override + public @NotNull String toString() { + return String.valueOf(value); + } +} diff --git a/core/src/main/java/eu/jonahbauer/json/JsonNumber.java b/core/src/main/java/eu/jonahbauer/json/JsonNumber.java new file mode 100644 index 0000000..d104266 --- /dev/null +++ b/core/src/main/java/eu/jonahbauer/json/JsonNumber.java @@ -0,0 +1,63 @@ +package eu.jonahbauer.json; + +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 { + public JsonNumber { + if (!Double.isFinite(value)) throw new IllegalArgumentException("value must be finite"); + } + + /** + * 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); + } +} diff --git a/core/src/main/java/eu/jonahbauer/json/JsonObject.java b/core/src/main/java/eu/jonahbauer/json/JsonObject.java new file mode 100644 index 0000000..11423ff --- /dev/null +++ b/core/src/main/java/eu/jonahbauer/json/JsonObject.java @@ -0,0 +1,377 @@ +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(); + 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(); + 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(); + 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(); + 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(); + 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"); + }; + } + + // + @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 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> entrySet() { + return entries.entrySet(); + } + // + + // + /** + * {@return the value to which the specified key is mapped, or null 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 null 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 null 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 null 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 null 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 @NotNull T get(@NotNull String key, @NotNull Class 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); + } + } + // + + // + // 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 @Nullable T getOrDefault(@NotNull String key, @Nullable S defaultValue, @NotNull Class type, @NotNull Function converter) { + var value = entries().get(key); + if (type.isInstance(value)) { + return type.cast(value); + } else { + return converter.apply(defaultValue); + } + } + // + + @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() { + StringJoiner out = new StringJoiner(",\n ", "{\n ", "\n}"); + entries.forEach((key, value) -> out.add(JsonString.quote(key) + ": " + indent(JsonValue.toPrettyJsonString(value)))); + return out.toString(); + } + + private static @NotNull String indent(@NotNull String string) { + if (string.isEmpty()) return ""; + + StringBuilder out = new StringBuilder(); + + Iterator it = string.lines().iterator(); + out.append(it.next()); + + while (it.hasNext()) { + String next = it.next(); + out.append('\n').append(it.hasNext() ? " " : " ").append(next); + } + + return out.toString(); + } +} diff --git a/core/src/main/java/eu/jonahbauer/json/JsonString.java b/core/src/main/java/eu/jonahbauer/json/JsonString.java new file mode 100644 index 0000000..2042709 --- /dev/null +++ b/core/src/main/java/eu/jonahbauer/json/JsonString.java @@ -0,0 +1,127 @@ +package eu.jonahbauer.json; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.stream.IntStream; + +@SuppressWarnings("unused") +public record JsonString(@NotNull String value) implements JsonValue, CharSequence { + public static final @NotNull JsonString EMPTY = new JsonString(""); + + public JsonString { + Objects.requireNonNull(value, "value"); + } + + /** + * Converts the given character to a JSON string. + * @param chr a character + * @return a new JSON string + */ + public static @NotNull JsonString valueOf(char chr) { + return new JsonString(String.valueOf(chr)); + } + + /** + * Converts the given character to a JSON string. + * @param chr a character + * @return a new JSON string + */ + @Contract("null -> null; !null -> !null") + public static @Nullable JsonString valueOf(@Nullable Character chr) { + return chr == null ? null : new JsonString(String.valueOf(chr)); + } + + /** + * Converts the given string to a JSON string. + * @param chars a string + * @return a new JSON string + */ + @Contract("null -> null; !null -> !null") + public static @Nullable JsonString valueOf(@Nullable CharSequence chars) { + return switch (chars) { + case JsonString json -> json; + case null -> null; + default -> { + @SuppressWarnings("java:S2259") + var result = new JsonString(chars.toString()); + yield result; + } + }; + } + + /** + * Quotes the given {@link String} and encodes all invalid characters as an escape sequence. + * @param value a string value + * @return a JSON representation of the given {@code value} + */ + public static @NotNull String quote(@NotNull String value) { + StringBuilder out = new StringBuilder(value.length() + 2); + out.append('"'); + for (int i = 0, length = value.length(); i < length; i++) { + char chr = value.charAt(i); + switch (chr) { + case '"' -> out.append("\\\""); + case '\\' -> out.append("\\\\"); + case '\b' -> out.append("\\b"); + case '\f' -> out.append("\\f"); + case '\n' -> out.append("\\n"); + case '\r' -> out.append("\\r"); + case '\t' -> out.append("\\t"); + default -> { + if (chr < 32) { + out.append("\\u%04x".formatted((int) chr)); + } else { + out.append(chr); + } + } + } + } + out.append('"'); + return out.toString(); + } + + // + @Override + public int length() { + return value.length(); + } + + @Override + public char charAt(int index) { + return value.charAt(index); + } + + @Override + public @NotNull JsonString subSequence(int start, int end) { + return new JsonString(value.substring(start, end)); + } + + @Override + public boolean isEmpty() { + return value.isEmpty(); + } + + @Override + public @NotNull IntStream chars() { + return value.chars(); + } + + @Override + public @NotNull IntStream codePoints() { + return value.codePoints(); + } + + @Override + public @NotNull String toString() { + return value; + } + // + + @Override + public @NotNull String toJsonString() { + return quote(value()); + } +} diff --git a/core/src/main/java/eu/jonahbauer/json/JsonValue.java b/core/src/main/java/eu/jonahbauer/json/JsonValue.java new file mode 100644 index 0000000..2411032 --- /dev/null +++ b/core/src/main/java/eu/jonahbauer/json/JsonValue.java @@ -0,0 +1,83 @@ +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.io.Serializable; +import java.util.List; +import java.util.Map; + +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 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 value == null ? "null" : value.toPrettyJsonString(); + } + + /** + * Converts an object to a JSON value: + *
    + *
  • {@link JsonValue}s and {@code null} are returned as is
  • + *
  • {@link Boolean}s are converted with {@link JsonBoolean#valueOf(Boolean)}
  • + *
  • {@link Number}s are converted with {@link JsonNumber#valueOf(Number)}
  • + *
  • {@link Character}s are converted with {@link JsonString#valueOf(Character)}
  • + *
  • {@link CharSequence}s are converted with {@link JsonString#valueOf(CharSequence)}
  • + *
  • {@link List}s are converted with {@link JsonArray#valueOf(List)}
  • + *
  • arrays are converted with the respective overload of {@link JsonArray#valueOf(Object...)}
  • + *
  • {@link Map}s are converted with {@link JsonObject#valueOf(Map)}
  • + *
+ * @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"); + }; + } +} diff --git a/core/src/main/java/eu/jonahbauer/json/Util.java b/core/src/main/java/eu/jonahbauer/json/Util.java new file mode 100644 index 0000000..2f3f9d4 --- /dev/null +++ b/core/src/main/java/eu/jonahbauer/json/Util.java @@ -0,0 +1,69 @@ +package eu.jonahbauer.json; + +import lombok.experimental.Delegate; +import lombok.experimental.UtilityClass; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +@UtilityClass +class Util { + 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 @NotNull List defensiveCopy(@NotNull List list) { + if (LIST_1_2.isInstance(list) || LIST_N.isInstance(list) || SUB_LIST.isInstance(list)) { + return list; + } else if (list instanceof TrustedList(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 @NotNull Map defensiveCopy(@NotNull Map<@NotNull K, V> map) { + if (MAP_1.isInstance(map) || MAP_N.isInstance(map)) { + return map; + } else if (map instanceof TrustedMap(var delegate)) { + return delegate; + } else if (map instanceof SequencedMap sequenced) { + var out = new LinkedHashMap(); + sequenced.forEach((key, value) -> out.put(Objects.requireNonNull(key, "key"), value)); + return Collections.unmodifiableSequencedMap(out); + } else { + var out = new HashMap(); + 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 @NotNull List trusted(@NotNull List 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 @NotNull Map trusted(@NotNull Map<@NotNull K, V> map) { + return new TrustedMap<>(map); + } + + private record TrustedList(@Delegate @NotNull List delegate) implements List {} + + private record TrustedMap(@Delegate @NotNull Map delegate) implements Map {} +} diff --git a/core/src/main/java/eu/jonahbauer/json/exceptions/JsonConversionException.java b/core/src/main/java/eu/jonahbauer/json/exceptions/JsonConversionException.java new file mode 100644 index 0000000..ee3edad --- /dev/null +++ b/core/src/main/java/eu/jonahbauer/json/exceptions/JsonConversionException.java @@ -0,0 +1,16 @@ +package eu.jonahbauer.json.exceptions; + +import org.jetbrains.annotations.NotNull; + +/** + * An exception that indicates problems with conversion between java objects and json. + */ +public class JsonConversionException extends JsonProcessingException { + public JsonConversionException(@NotNull String message) { + super(message); + } + + public JsonConversionException(@NotNull String message, @NotNull Throwable cause) { + super(message, cause); + } +} diff --git a/core/src/main/java/eu/jonahbauer/json/exceptions/JsonProcessingException.java b/core/src/main/java/eu/jonahbauer/json/exceptions/JsonProcessingException.java new file mode 100644 index 0000000..aca8420 --- /dev/null +++ b/core/src/main/java/eu/jonahbauer/json/exceptions/JsonProcessingException.java @@ -0,0 +1,14 @@ +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); + } +} diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java new file mode 100644 index 0000000..1ebc43a --- /dev/null +++ b/core/src/main/java/module-info.java @@ -0,0 +1,7 @@ +module eu.jonahbauer.json { + requires static lombok; + requires static org.jetbrains.annotations; + + exports eu.jonahbauer.json; + exports eu.jonahbauer.json.exceptions; +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..1883bb8 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,7 @@ +[versions] +annotations = "24.1.0" +lombok = "1.18.32" + +[libraries] +annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" } +lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..d64cd49 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..37f853b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1aa94a4 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..911a2e1 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,11 @@ + +@Suppress("UnstableApiUsage") +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + mavenCentral() + } +} + +rootProject.name = "json" +include("core")