diff --git a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/dynamic/TaggedDynamic.java b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/dynamic/TaggedDynamic.java
index fe308ed..49277a9 100644
--- a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/dynamic/TaggedDynamic.java
+++ b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/dynamic/TaggedDynamic.java
@@ -179,4 +179,68 @@ public TypeReference type() {
public Dynamic> value() {
return this.value;
}
+
+ /**
+ * Indicates whether some other object is "equal to" this one.
+ *
+ *
Two {@code TaggedDynamic} instances are considered equal if they have the same type reference and the same
+ * dynamic value.
+ * This means that both the type and the data must match for two tagged dynamics to be considered equal.
+ *
+ *
Example
+ *
{@code
+ * TaggedDynamic tagged1 = new TaggedDynamic(typeRef, dynamicValue);
+ * TaggedDynamic tagged2 = new TaggedDynamic(typeRef, dynamicValue);
+ *
+ * // tagged1 and tagged2 are equal because they have the same type and value
+ * boolean isEqual = tagged1.equals(tagged2); // true
+ * }
+ *
+ * @param obj the reference object with which to compare
+ * @return {@code true} if this object is the same as the obj argument; {@code false} otherwise
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+
+ final TaggedDynamic that = (TaggedDynamic) obj;
+ return this.type.equals(that.type) && this.value.equals(that.value);
+ }
+
+ /**
+ * Returns a hash code value for the object.
+ *
+ *
The hash code is computed based on both the type reference and the dynamic value. This ensures that equal
+ * {@code TaggedDynamic} instances will have the same hash code, which is important for correct behavior in
+ * hash-based collections.
+ *
+ * @return a hash code value for this object
+ */
+ @Override
+ public int hashCode() {
+ int result = this.type.hashCode();
+ result = 31 * result + this.value.hashCode();
+ return result;
+ }
+
+ /**
+ * Returns a string representation of the object.
+ *
+ *
The string representation includes the type reference and the dynamic value, providing a human-readable
+ * summary of the tagged dynamic's contents. This can be useful for debugging and logging purposes.
+ *
+ * @return a string representation of the object
+ */
+ @Override
+ public String toString() {
+ return "TaggedDynamic{" +
+ "type=" + this.type +
+ ", value=" + this.value +
+ '}';
+ }
}
diff --git a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/fix/Fixes.java b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/fix/Fixes.java
index f811ed3..b5b8f14 100644
--- a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/fix/Fixes.java
+++ b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/fix/Fixes.java
@@ -121,7 +121,7 @@ private Fixes() {
* @param rewrite the function that transforms the typed value; must not be {@code null}
* @return a type rewrite rule that applies the transformation to matching types; never {@code null}
* @throws NullPointerException if any parameter is {@code null}
- * @see #fixTypeEverywhere(String, Type, Function)
+ * @see #fixTypeEverywhere(String, DynamicOps, Type, Function)
*/
@NotNull
public static TypeRewriteRule fixTypeEverywhereTyped(@NotNull final String name,
@@ -156,22 +156,44 @@ public String toString() {
/**
* Creates a rule that transforms the dynamic representation of a type.
*
- * @param name the fix name
- * @param type the type to transform
- * @param rewrite the dynamic transformation function
- * @return a type rewrite rule
+ *
This method creates a {@link TypeRewriteRule} that matches values of the specified
+ * type and transforms them by encoding to {@link Dynamic}, applying the rewrite function, and decoding back. The
+ * rule only matches types with the same {@link de.splatgames.aether.datafixers.api.TypeReference} as the target
+ * type.
+ *
+ * @param the dynamic type
+ * @param name the fix name; must not be {@code null}
+ * @param ops the dynamic ops for encoding/decoding; must not be {@code null}
+ * @param type the type to transform; must not be {@code null}
+ * @param rewrite the dynamic transformation function; must not be {@code null}
+ * @return a type rewrite rule; never {@code null}
+ * @throws NullPointerException if any parameter is {@code null}
+ * @see #fixTypeEverywhereTyped(String, Type, Type, Function)
*/
@NotNull
- public static TypeRewriteRule fixTypeEverywhere(@NotNull final String name,
- @NotNull final Type> type,
- @NotNull final Function, Dynamic>> rewrite) {
+ public static TypeRewriteRule fixTypeEverywhere(@NotNull final String name,
+ @NotNull final DynamicOps ops,
+ @NotNull final Type> type,
+ @NotNull final Function, Dynamic>> rewrite) {
Preconditions.checkNotNull(name, "name must not be null");
+ Preconditions.checkNotNull(ops, "ops must not be null");
Preconditions.checkNotNull(type, "type must not be null");
Preconditions.checkNotNull(rewrite, "rewrite must not be null");
return new TypeRewriteRule() {
@NotNull
@Override
+ @SuppressWarnings({"unchecked", "rawtypes"})
public Optional> rewrite(@NotNull final Type> inputType,
@NotNull final Typed> input) {
Preconditions.checkNotNull(inputType, "inputType must not be null");
@@ -180,9 +202,11 @@ public Optional> rewrite(@NotNull final Type> inputType,
return Optional.empty();
}
- // We need to get DynamicOps from somewhere - this requires the caller to provide it
- // For now, we'll use a simplified approach
- return Optional.of(input);
+ final DataResult> encodeResult = input.encode(ops);
+ return encodeResult.flatMap(dynamic -> {
+ final Dynamic> transformed = rewrite.apply(dynamic);
+ return ((Type) type).read(transformed);
+ }).map(newValue -> new Typed<>((Type) type, newValue)).result();
}
@Override
diff --git a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/optic/Finder.java b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/optic/Finder.java
index e212560..8d0f26b 100644
--- a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/optic/Finder.java
+++ b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/optic/Finder.java
@@ -179,8 +179,10 @@ public Dynamic> set(@NotNull final Dynamic> root,
* Creates a finder that navigates to an element by index in a list/array structure.
*
*
The returned finder extracts and modifies the element at the specified
- * index position. If the index is out of bounds (negative or beyond the list size), {@link #get} returns
- * {@code null} and {@link #set} returns the root unchanged.
+ * index position. A negative {@code index} is rejected immediately by throwing
+ * {@link IllegalArgumentException} at construction time. If the index is non-negative
+ * but beyond the list size, {@link #get} returns {@code null} and {@link #set} returns
+ * the root unchanged.
*
*
Example
*
{@code
@@ -195,16 +197,21 @@ public Dynamic> set(@NotNull final Dynamic> root,
* Dynamic> updated = firstScoreFinder.set(data, data.createInt(100));
* // updated: {"scores": [100, 92, 78]}
*
- * // Out of bounds access
+ * // Out-of-range (positive) index — get returns null, set returns root unchanged
* Finder> outOfBounds = scoresFinder.then(Finder.index(10));
* Dynamic> missing = outOfBounds.get(data); // null
+ *
+ * // Negative index — throws IllegalArgumentException immediately
+ * Finder.index(-1); // throws IllegalArgumentException
* }
*
- * @param index the zero-based index of the element to focus on
+ * @param index the zero-based index of the element to focus on; must not be negative
+ * @throws IllegalArgumentException if {@code index} is negative
* @return a finder that navigates to the element at the specified index, never {@code null}
*/
@NotNull
static Finder