refactor!(core): Simplify ConfigEntryValueVisitor by introducing SubEntryKey instead of passing values separately

This commit is contained in:
2026-04-13 23:26:40 +02:00
parent 2f4011c144
commit 52e2a52468
12 changed files with 135 additions and 57 deletions

View File

@@ -9,5 +9,5 @@ public interface AddressableStructuredConfigEntry<T> extends StructuredConfigEnt
return this;
}
Object get(T value, String key);
Object get(T value, String dataKey);
}

View File

@@ -37,10 +37,10 @@ public interface CollectionConfigEntry<E, T extends Collection<E>> extends Struc
if (visitor.enterStructuredEntry(this, value)) {
int index = 0;
for (E item : value) {
String indexString = Integer.toString(index);
if (visitor.enterAddressableStructuredSubEntry("element", indexString, null)) {
SubEntryKey subEntryKey = SubEntryKey.structured("element", Integer.toString(index));
if (visitor.enterSubEntry(subEntryKey)) {
elementEntry().visitInOrder(visitor, item);
visitor.leaveAddressableStructuredSubEntry("element", indexString, null);
visitor.leaveSubEntry(subEntryKey);
}
index++;
}

View File

@@ -8,18 +8,11 @@ public interface ConfigEntryValueVisitor {
return true;
}
default boolean enterAddressableStructuredSubEntry(String entryKey, String valueKey, String dataKey) {
default boolean enterSubEntry(SubEntryKey subEntryKey) {
return true;
}
default boolean enterStructuredSubEntry(String entryKey, String valueKey) {
return true;
}
default void leaveAddressableStructuredSubEntry(String entryKey, String valueKey, String dataKey) {
}
default void leaveStructuredSubEntry(String entryKey, String valueKey) {
default void leaveSubEntry(SubEntryKey subEntryKey) {
}
default <T> void leaveStructuredEntry(StructuredConfigEntry<T> entry, T value) {

View File

@@ -12,5 +12,5 @@ public interface MutableStructuredConfigEntry<T> extends AddressableStructuredCo
}
@NonNull T instantiateValue();
void set(T value, String key, Object subValue);
void set(T value, String dataKey, Object subValue);
}

View File

@@ -0,0 +1,53 @@
package de.siphalor.tweed5.core.api.entry;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Value;
import org.jspecify.annotations.Nullable;
@Value
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class SubEntryKey {
String entry;
@Nullable String value;
@Nullable String data;
/**
* Models the key of a transparent entry. Transparent entries are sub entries that only exist in the entry tree
* but are not actually present in the data.
* @param entryKey the key of the entry in the entry tree
* @apiNote The resulting key is not addressable.
*/
public static SubEntryKey transparent(String entryKey) {
return new SubEntryKey(entryKey, null, null);
}
/**
* Models the key of a transparent entry with a data key for use with {@link AddressableStructuredConfigEntry}s.
* @param entryKey the key of the entry in the entry tree
* @param dataKey the "address" of the data in the {@link AddressableStructuredConfigEntry}
*/
public static SubEntryKey transparentAddressable(String entryKey, String dataKey) {
return new SubEntryKey(entryKey, null, dataKey);
}
/**
* A "normal" sub entry key.
* @param entryKey the key of the entry in the entry tree
* @param valueKey a potentially differing key for the user-facing data tree
*/
public static SubEntryKey structured(String entryKey, String valueKey) {
return new SubEntryKey(entryKey, valueKey, null);
}
/**
* A sub entry key for {@link AddressableStructuredConfigEntry}s.
* @param entryKey the key of the entry in the entry tree
* @param valueKey a potentially differing key for the user-facing data tree
* @param dataKey the "address" of the data in the {@link AddressableStructuredConfigEntry}
* @apiNote {@code valueKey} and {@code dataKey} are usually a 1:1 mapping.
*/
public static SubEntryKey addressable(String entryKey, String valueKey, String dataKey) {
return new SubEntryKey(entryKey, valueKey, dataKey);
}
}

View File

@@ -5,6 +5,7 @@ import de.siphalor.tweed5.core.api.entry.BaseConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.entry.NullableConfigEntry;
import de.siphalor.tweed5.core.api.entry.SubEntryKey;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
@@ -25,7 +26,7 @@ public class NullableConfigEntryImpl<T extends @Nullable Object> extends BaseCon
}
@Override
public T get(T value, String key) {
public T get(T value, String dataKey) {
return value;
}
@@ -38,9 +39,10 @@ public class NullableConfigEntryImpl<T extends @Nullable Object> extends BaseCon
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
if (value != null) {
if (visitor.enterStructuredEntry(this, value)) {
if (visitor.enterAddressableStructuredSubEntry(NON_NULL_KEY, NON_NULL_KEY, NON_NULL_KEY)) {
SubEntryKey subEntryKey = SubEntryKey.transparentAddressable(NON_NULL_KEY, NON_NULL_KEY);
if (visitor.enterSubEntry(subEntryKey)) {
nonNullEntry.visitInOrder(visitor, value);
visitor.leaveAddressableStructuredSubEntry(NON_NULL_KEY, NON_NULL_KEY, NON_NULL_KEY);
visitor.leaveSubEntry(subEntryKey);
}
visitor.leaveStructuredEntry(this, value);
}

View File

@@ -29,15 +29,15 @@ public class StaticMapCompoundConfigEntryImpl<T extends @NonNull Map<String, Obj
}
@Override
public void set(T compoundValue, String key, Object value) {
requireKey(key);
compoundValue.put(key, value);
public void set(T compoundValue, String dataKey, Object value) {
requireKey(dataKey);
compoundValue.put(dataKey, value);
}
@Override
public Object get(T compoundValue, String key) {
requireKey(key);
return compoundValue.get(key);
public Object get(T compoundValue, String dataKey) {
requireKey(dataKey);
return compoundValue.get(dataKey);
}
private void requireKey(String key) {
@@ -55,10 +55,11 @@ public class StaticMapCompoundConfigEntryImpl<T extends @NonNull Map<String, Obj
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
if (visitor.enterStructuredEntry(this, value)) {
compoundEntries.forEach((key, entry) -> {
if (visitor.enterAddressableStructuredSubEntry(key, key, key)) {
SubEntryKey subEntryKey = SubEntryKey.addressable(key, key, key);
if (visitor.enterSubEntry(subEntryKey)) {
//noinspection unchecked
((ConfigEntry<Object>) entry).visitInOrder(visitor, value.get(key));
visitor.leaveAddressableStructuredSubEntry(key, key, key);
visitor.leaveSubEntry(subEntryKey);
}
});
visitor.leaveStructuredEntry(this, value);

View File

@@ -9,7 +9,11 @@ public interface PathTracking {
void pushPathPart(String pathPart);
void pushEmptyPathPart();
void popPathPart();
String currentPath();
String[] currentPathParts();
}

View File

@@ -3,6 +3,7 @@ package de.siphalor.tweed5.defaultextensions.pather.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.entry.StructuredConfigEntry;
import de.siphalor.tweed5.core.api.entry.SubEntryKey;
import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.Nullable;
@@ -22,32 +23,21 @@ public class PathTrackingConfigEntryValueVisitor implements ConfigEntryValueVisi
}
@Override
public boolean enterAddressableStructuredSubEntry(String entryKey, String valueKey, String dataKey) {
boolean enter = delegate.enterAddressableStructuredSubEntry(entryKey, valueKey, dataKey);
public boolean enterSubEntry(SubEntryKey subEntryKey) {
boolean enter = delegate.enterSubEntry(subEntryKey);
if (enter) {
pathTracking.pushPathPart(entryKey, valueKey);
if (subEntryKey.value() != null) {
pathTracking.pushPathPart(subEntryKey.entry(), subEntryKey.value());
} else {
pathTracking.pushEmptyValuePathPart(subEntryKey.entry());
}
}
return enter;
}
@Override
public boolean enterStructuredSubEntry(String entryKey, String valueKey) {
boolean enter = delegate.enterStructuredSubEntry(entryKey, valueKey);
if (enter) {
pathTracking.pushPathPart(entryKey, valueKey);
}
return enter;
}
@Override
public void leaveAddressableStructuredSubEntry(String entryKey, String valueKey, String dataKey) {
delegate.leaveAddressableStructuredSubEntry(entryKey, valueKey, dataKey);
pathTracking.popPathPart();
}
@Override
public void leaveStructuredSubEntry(String entryKey, String valueKey) {
delegate.leaveStructuredSubEntry(entryKey, valueKey);
public void leaveSubEntry(SubEntryKey subEntryKey) {
delegate.leaveSubEntry(subEntryKey);
pathTracking.popPathPart();
}

View File

@@ -17,6 +17,17 @@ public final class ValuePathTracking implements PathTracking {
valuePathTracking.pushPathPart(valuePathPart);
}
@Override
public void pushEmptyPathPart() {
entryPathTracking.pushEmptyPathPart();
valuePathTracking.pushEmptyPathPart();
}
public void pushEmptyValuePathPart(String entryPathPart) {
entryPathTracking.pushPathPart(entryPathPart);
valuePathTracking.pushEmptyPathPart();
}
@Override
public void popPathPart() {
valuePathTracking.popPathPart();
@@ -31,4 +42,13 @@ public final class ValuePathTracking implements PathTracking {
public String currentEntryPath() {
return entryPathTracking.currentPath();
}
@Override
public String[] currentPathParts() {
return valuePathTracking.currentPathParts();
}
public String[] currentEntryPathParts() {
return entryPathTracking.currentPathParts();
}
}

View File

@@ -1,13 +1,14 @@
package de.siphalor.tweed5.defaultextensions.pather.impl;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTracking;
import org.jspecify.annotations.Nullable;
import java.util.ArrayDeque;
import java.util.Deque;
public class PathTrackingImpl implements PathTracking {
private final StringBuilder pathBuilder = new StringBuilder(256);
private final Deque<String> pathParts = new ArrayDeque<>(50);
private final Deque<@Nullable String> pathParts = new ArrayDeque<>(50);
@Override
public void pushPathPart(String entryPathPart) {
@@ -15,11 +16,18 @@ public class PathTrackingImpl implements PathTracking {
pathBuilder.append(".").append(entryPathPart);
}
@Override
public void pushEmptyPathPart() {
pathParts.push(null);
}
@Override
public void popPathPart() {
if (!pathParts.isEmpty()) {
String poppedPart = pathParts.pop();
pathBuilder.setLength(pathBuilder.length() - poppedPart.length() - 1);
if (poppedPart != null) {
pathBuilder.setLength(pathBuilder.length() - poppedPart.length() - 1);
}
}
}
@@ -27,4 +35,9 @@ public class PathTrackingImpl implements PathTracking {
public String currentPath() {
return pathBuilder.toString();
}
@Override
public String[] currentPathParts() {
return pathParts.toArray(new String[0]);
}
}

View File

@@ -5,6 +5,7 @@ import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.BaseConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.entry.SubEntryKey;
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry;
import java.util.Collections;
@@ -40,30 +41,30 @@ public class StaticPojoCompoundConfigEntry<T> extends BaseConfigEntry<T> impleme
}
@Override
public void set(T compoundValue, String key, Object value) {
SubEntry subEntry = subEntries.get(key);
public void set(T compoundValue, String dataKey, Object value) {
SubEntry subEntry = subEntries.get(dataKey);
if (subEntry == null) {
throw new IllegalArgumentException("Unknown config entry: " + key);
throw new IllegalArgumentException("Unknown config entry: " + dataKey);
}
try {
subEntry.setter().invoke(compoundValue, value);
} catch (Throwable e) {
throw new IllegalStateException("Failed to set value for config entry \"" + key + "\"", e);
throw new IllegalStateException("Failed to set value for config entry \"" + dataKey + "\"", e);
}
}
@Override
public Object get(T compoundValue, String key) {
SubEntry subEntry = subEntries.get(key);
public Object get(T compoundValue, String dataKey) {
SubEntry subEntry = subEntries.get(dataKey);
if (subEntry == null) {
throw new IllegalArgumentException("Unknown config entry: " + key);
throw new IllegalArgumentException("Unknown config entry: " + dataKey);
}
try {
return subEntry.getter().invoke(compoundValue);
} catch (Throwable e) {
throw new IllegalStateException("Failed to get value for config entry \"" + key + "\"", e);
throw new IllegalStateException("Failed to get value for config entry \"" + dataKey + "\"", e);
}
}
@@ -80,12 +81,13 @@ public class StaticPojoCompoundConfigEntry<T> extends BaseConfigEntry<T> impleme
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
if (visitor.enterStructuredEntry(this, value)) {
subEntries.forEach((key, entry) -> {
if (visitor.enterAddressableStructuredSubEntry(key, key, key)) {
SubEntryKey subEntryKey = SubEntryKey.addressable(key, key, key);
if (visitor.enterSubEntry(subEntryKey)) {
try {
Object subValue = entry.getter().invoke(value);
//noinspection unchecked
((ConfigEntry<Object>) entry.configEntry()).visitInOrder(visitor, subValue);
visitor.leaveAddressableStructuredSubEntry(key, key, key);
visitor.leaveSubEntry(subEntryKey);
} catch (Throwable e) {
throw new RuntimeException("Failed to get compound sub entry value \"" + key + "\"");
}