diff --git a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/AddressableStructuredConfigEntry.java b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/AddressableStructuredConfigEntry.java index 7cc5c8f..8c55cbe 100644 --- a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/AddressableStructuredConfigEntry.java +++ b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/AddressableStructuredConfigEntry.java @@ -9,5 +9,5 @@ public interface AddressableStructuredConfigEntry extends StructuredConfigEnt return this; } - Object get(T value, String key); + Object get(T value, String dataKey); } diff --git a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/CollectionConfigEntry.java b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/CollectionConfigEntry.java index 0adf2f6..92d59d7 100644 --- a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/CollectionConfigEntry.java +++ b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/CollectionConfigEntry.java @@ -37,10 +37,10 @@ public interface CollectionConfigEntry> 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++; } diff --git a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntryValueVisitor.java b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntryValueVisitor.java index d92e75a..785dc0a 100644 --- a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntryValueVisitor.java +++ b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntryValueVisitor.java @@ -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 void leaveStructuredEntry(StructuredConfigEntry entry, T value) { diff --git a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/MutableStructuredConfigEntry.java b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/MutableStructuredConfigEntry.java index fd45936..a2d19f7 100644 --- a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/MutableStructuredConfigEntry.java +++ b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/MutableStructuredConfigEntry.java @@ -12,5 +12,5 @@ public interface MutableStructuredConfigEntry extends AddressableStructuredCo } @NonNull T instantiateValue(); - void set(T value, String key, Object subValue); + void set(T value, String dataKey, Object subValue); } diff --git a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/SubEntryKey.java b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/SubEntryKey.java new file mode 100644 index 0000000..a320f08 --- /dev/null +++ b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/SubEntryKey.java @@ -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); + } +} diff --git a/tweed5/core/src/main/java/de/siphalor/tweed5/core/impl/entry/NullableConfigEntryImpl.java b/tweed5/core/src/main/java/de/siphalor/tweed5/core/impl/entry/NullableConfigEntryImpl.java index 2ffe214..257b208 100644 --- a/tweed5/core/src/main/java/de/siphalor/tweed5/core/impl/entry/NullableConfigEntryImpl.java +++ b/tweed5/core/src/main/java/de/siphalor/tweed5/core/impl/entry/NullableConfigEntryImpl.java @@ -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 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 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); } diff --git a/tweed5/core/src/main/java/de/siphalor/tweed5/core/impl/entry/StaticMapCompoundConfigEntryImpl.java b/tweed5/core/src/main/java/de/siphalor/tweed5/core/impl/entry/StaticMapCompoundConfigEntryImpl.java index 89646fc..cb899b4 100644 --- a/tweed5/core/src/main/java/de/siphalor/tweed5/core/impl/entry/StaticMapCompoundConfigEntryImpl.java +++ b/tweed5/core/src/main/java/de/siphalor/tweed5/core/impl/entry/StaticMapCompoundConfigEntryImpl.java @@ -29,15 +29,15 @@ public class StaticMapCompoundConfigEntryImpl { - if (visitor.enterAddressableStructuredSubEntry(key, key, key)) { + SubEntryKey subEntryKey = SubEntryKey.addressable(key, key, key); + if (visitor.enterSubEntry(subEntryKey)) { //noinspection unchecked ((ConfigEntry) entry).visitInOrder(visitor, value.get(key)); - visitor.leaveAddressableStructuredSubEntry(key, key, key); + visitor.leaveSubEntry(subEntryKey); } }); visitor.leaveStructuredEntry(this, value); diff --git a/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTracking.java b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTracking.java index f07ba0d..78ba775 100644 --- a/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTracking.java +++ b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTracking.java @@ -9,7 +9,11 @@ public interface PathTracking { void pushPathPart(String pathPart); + void pushEmptyPathPart(); + void popPathPart(); String currentPath(); + + String[] currentPathParts(); } diff --git a/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingConfigEntryValueVisitor.java b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingConfigEntryValueVisitor.java index e54f3f6..b9e2ef3 100644 --- a/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingConfigEntryValueVisitor.java +++ b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingConfigEntryValueVisitor.java @@ -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(); } diff --git a/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/ValuePathTracking.java b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/ValuePathTracking.java index 7f1f9b2..1109852 100644 --- a/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/ValuePathTracking.java +++ b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/ValuePathTracking.java @@ -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(); + } } diff --git a/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PathTrackingImpl.java b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PathTrackingImpl.java index e0b1844..15ef95f 100644 --- a/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PathTrackingImpl.java +++ b/tweed5/default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PathTrackingImpl.java @@ -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 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]); + } } diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StaticPojoCompoundConfigEntry.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StaticPojoCompoundConfigEntry.java index b3ef8cb..99c9c1e 100644 --- a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StaticPojoCompoundConfigEntry.java +++ b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StaticPojoCompoundConfigEntry.java @@ -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 extends BaseConfigEntry 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 extends BaseConfigEntry 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) 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 + "\""); }