refactor!: Refactored type hierarchy and methods of StructuredConfigEntry

This commit is contained in:
2026-03-16 21:42:47 +01:00
parent d69eb85d81
commit affc1de0f5
18 changed files with 107 additions and 52 deletions

View File

@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `coat-bridge`: Added experimental text mapper based on Tweed Serde. - `coat-bridge`: Added experimental text mapper based on Tweed Serde.
### Changed ### Changed
- **Breaking**@`core`: Refactored type hierarchy and methods of `StructuredConfigEntry`.
- `weaver-pojo-serde-extension`: Slightly changed the `SerdePojoReaderWriterSpec` - `weaver-pojo-serde-extension`: Slightly changed the `SerdePojoReaderWriterSpec`
to be more closely aligned with Java's identifier rules. to be more closely aligned with Java's identifier rules.
- `attributes-extension`: The `AttributesReadWriteFilterExtension` now correctly skips non-matching compound entries - `attributes-extension`: The `AttributesReadWriteFilterExtension` now correctly skips non-matching compound entries

View File

@@ -6,6 +6,7 @@ plugins {
dependencies { dependencies {
implementation(project(":tweed5-core")) implementation(project(":tweed5-core"))
compileOnly(project(":tweed5-serde-extension")) compileOnly(project(":tweed5-serde-extension"))
testImplementation(project(":tweed5-default-extensions"))
testImplementation(project(":tweed5-serde-extension")) testImplementation(project(":tweed5-serde-extension"))
testImplementation(project(":tweed5-serde-hjson")) testImplementation(project(":tweed5-serde-hjson"))
} }

View File

@@ -7,6 +7,7 @@ import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor; import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
import de.siphalor.tweed5.patchwork.api.Patchwork; import de.siphalor.tweed5.patchwork.api.Patchwork;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.util.Map; import java.util.Map;
@@ -16,7 +17,7 @@ public class AttributesFilteredCompoundEntry<T extends @Nullable Object> impleme
private final CompoundConfigEntry<T> delegate; private final CompoundConfigEntry<T> delegate;
@Override @Override
public <V> void set(T compoundValue, String key, V value) { public void set(T compoundValue, String key, Object value) {
if (value == AttributesReadWriteFilterExtensionImpl.NOOP_MARKER) { if (value == AttributesReadWriteFilterExtensionImpl.NOOP_MARKER) {
return; return;
} }
@@ -24,13 +25,13 @@ public class AttributesFilteredCompoundEntry<T extends @Nullable Object> impleme
} }
@Override @Override
public <V> V get(T compoundValue, String key) { public Object get(T compoundValue, String key) {
return delegate.get(compoundValue, key); return delegate.get(compoundValue, key);
} }
@Override @Override
public T instantiateCompoundValue() { public @NonNull T instantiateValue() {
return delegate.instantiateCompoundValue(); return delegate.instantiateValue();
} }
@Override @Override

View File

@@ -0,0 +1,13 @@
package de.siphalor.tweed5.core.api.entry;
import java.util.function.Consumer;
public interface AddressableStructuredConfigEntry<T> extends StructuredConfigEntry<T> {
@Override
default AddressableStructuredConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
StructuredConfigEntry.super.apply(function);
return this;
}
Object get(T value, String key);
}

View File

@@ -38,9 +38,9 @@ public interface CollectionConfigEntry<E, T extends Collection<E>> extends Struc
int index = 0; int index = 0;
for (E item : value) { for (E item : value) {
String indexString = Integer.toString(index); String indexString = Integer.toString(index);
if (visitor.enterStructuredSubEntry("element", indexString)) { if (visitor.enterAddressableStructuredSubEntry("element", indexString, null)) {
elementEntry().visitInOrder(visitor, item); elementEntry().visitInOrder(visitor, item);
visitor.leaveStructuredSubEntry("element", indexString); visitor.leaveAddressableStructuredSubEntry("element", indexString, null);
} }
index++; index++;
} }

View File

@@ -4,18 +4,13 @@ import de.siphalor.tweed5.core.api.Arity;
import java.util.function.Consumer; import java.util.function.Consumer;
public interface CompoundConfigEntry<T> extends StructuredConfigEntry<T> { public interface CompoundConfigEntry<T> extends MutableStructuredConfigEntry<T> {
@Override @Override
default CompoundConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) { default CompoundConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
StructuredConfigEntry.super.apply(function); MutableStructuredConfigEntry.super.apply(function);
return this; return this;
} }
<V> void set(T compoundValue, String key, V value);
<V> V get(T compoundValue, String key);
T instantiateCompoundValue();
@Override @Override
default void visitInOrder(ConfigEntryVisitor visitor) { default void visitInOrder(ConfigEntryVisitor visitor) {
if (visitor.enterStructuredEntry(this)) { if (visitor.enterStructuredEntry(this)) {

View File

@@ -3,18 +3,25 @@ package de.siphalor.tweed5.core.api.entry;
public interface ConfigEntryValueVisitor { public interface ConfigEntryValueVisitor {
<T> void visitEntry(ConfigEntry<T> entry, T value); <T> void visitEntry(ConfigEntry<T> entry, T value);
default <T> boolean enterStructuredEntry(ConfigEntry<T> entry, T value) { default <T> boolean enterStructuredEntry(StructuredConfigEntry<T> entry, T value) {
visitEntry(entry, value); visitEntry(entry, value);
return true; return true;
} }
default boolean enterAddressableStructuredSubEntry(String entryKey, String valueKey, String dataKey) {
return true;
}
default boolean enterStructuredSubEntry(String entryKey, String valueKey) { default boolean enterStructuredSubEntry(String entryKey, String valueKey) {
return true; return true;
} }
default void leaveAddressableStructuredSubEntry(String entryKey, String valueKey, String dataKey) {
}
default void leaveStructuredSubEntry(String entryKey, String valueKey) { default void leaveStructuredSubEntry(String entryKey, String valueKey) {
} }
default <T> void leaveStructuredEntry(ConfigEntry<T> entry, T value) { default <T> void leaveStructuredEntry(StructuredConfigEntry<T> entry, T value) {
} }
} }

View File

@@ -0,0 +1,16 @@
package de.siphalor.tweed5.core.api.entry;
import org.jspecify.annotations.NonNull;
import java.util.function.Consumer;
public interface MutableStructuredConfigEntry<T> extends AddressableStructuredConfigEntry<T> {
@Override
default AddressableStructuredConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
AddressableStructuredConfigEntry.super.apply(function);
return this;
}
@NonNull T instantiateValue();
void set(T value, String key, Object subValue);
}

View File

@@ -5,12 +5,12 @@ import org.jetbrains.annotations.Nullable;
import java.util.function.Consumer; import java.util.function.Consumer;
public interface NullableConfigEntry<T extends @Nullable Object> extends StructuredConfigEntry<T> { public interface NullableConfigEntry<T extends @Nullable Object> extends AddressableStructuredConfigEntry<T> {
String NON_NULL_KEY = ":nonNull"; String NON_NULL_KEY = ":nonNull";
@Override @Override
default NullableConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) { default NullableConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
StructuredConfigEntry.super.apply(function); AddressableStructuredConfigEntry.super.apply(function);
return this; return this;
} }

View File

@@ -2,11 +2,12 @@ package de.siphalor.tweed5.core.impl.entry;
import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.*; import de.siphalor.tweed5.core.api.entry.*;
import org.jspecify.annotations.NonNull;
import java.util.Collection; import java.util.Collection;
import java.util.function.IntFunction; import java.util.function.IntFunction;
public class CollectionConfigEntryImpl<E, T extends Collection<E>> extends BaseConfigEntry<T> implements CollectionConfigEntry<E, T> { public class CollectionConfigEntryImpl<E, T extends @NonNull Collection<E>> extends BaseConfigEntry<T> implements CollectionConfigEntry<E, T> {
private final IntFunction<T> collectionConstructor; private final IntFunction<T> collectionConstructor;
private final ConfigEntry<E> elementEntry; private final ConfigEntry<E> elementEntry;

View File

@@ -24,6 +24,11 @@ public class NullableConfigEntryImpl<T extends @Nullable Object> extends BaseCon
this.nonNullEntry = nonNullEntry; this.nonNullEntry = nonNullEntry;
} }
@Override
public T get(T value, String key) {
return value;
}
@Override @Override
public Map<String, ConfigEntry<?>> subEntries() { public Map<String, ConfigEntry<?>> subEntries() {
return Collections.singletonMap(NON_NULL_KEY, nonNullEntry); return Collections.singletonMap(NON_NULL_KEY, nonNullEntry);
@@ -33,9 +38,9 @@ public class NullableConfigEntryImpl<T extends @Nullable Object> extends BaseCon
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) { public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
if (value != null) { if (value != null) {
if (visitor.enterStructuredEntry(this, value)) { if (visitor.enterStructuredEntry(this, value)) {
if (visitor.enterStructuredSubEntry(NON_NULL_KEY, NON_NULL_KEY)) { if (visitor.enterAddressableStructuredSubEntry(NON_NULL_KEY, NON_NULL_KEY, NON_NULL_KEY)) {
nonNullEntry.visitInOrder(visitor, value); nonNullEntry.visitInOrder(visitor, value);
visitor.leaveStructuredSubEntry(NON_NULL_KEY, NON_NULL_KEY); visitor.leaveAddressableStructuredSubEntry(NON_NULL_KEY, NON_NULL_KEY, NON_NULL_KEY);
} }
visitor.leaveStructuredEntry(this, value); visitor.leaveStructuredEntry(this, value);
} }

View File

@@ -2,12 +2,13 @@ package de.siphalor.tweed5.core.impl.entry;
import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.*; import de.siphalor.tweed5.core.api.entry.*;
import org.jspecify.annotations.NonNull;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.function.IntFunction; import java.util.function.IntFunction;
public class StaticMapCompoundConfigEntryImpl<T extends Map<String, Object>> extends BaseConfigEntry<T> implements CompoundConfigEntry<T> { public class StaticMapCompoundConfigEntryImpl<T extends @NonNull Map<String, Object>> extends BaseConfigEntry<T> implements CompoundConfigEntry<T> {
private final IntFunction<T> mapConstructor; private final IntFunction<T> mapConstructor;
private final Map<String, ConfigEntry<?>> compoundEntries; private final Map<String, ConfigEntry<?>> compoundEntries;
@@ -28,16 +29,15 @@ public class StaticMapCompoundConfigEntryImpl<T extends Map<String, Object>> ext
} }
@Override @Override
public <V> void set(T compoundValue, String key, V value) { public void set(T compoundValue, String key, Object value) {
requireKey(key); requireKey(key);
compoundValue.put(key, value); compoundValue.put(key, value);
} }
@Override @Override
public <V> V get(T compoundValue, String key) { public Object get(T compoundValue, String key) {
requireKey(key); requireKey(key);
//noinspection unchecked return compoundValue.get(key);
return (V) compoundValue.get(key);
} }
private void requireKey(String key) { private void requireKey(String key) {
@@ -47,29 +47,27 @@ public class StaticMapCompoundConfigEntryImpl<T extends Map<String, Object>> ext
} }
@Override @Override
public T instantiateCompoundValue() { public T instantiateValue() {
return mapConstructor.apply(compoundEntries.size()); return mapConstructor.apply(compoundEntries.size());
} }
@Override @Override
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) { public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
if (visitor.enterStructuredEntry(this, value)) { if (visitor.enterStructuredEntry(this, value)) {
if (value != null) { compoundEntries.forEach((key, entry) -> {
compoundEntries.forEach((key, entry) -> { if (visitor.enterAddressableStructuredSubEntry(key, key, key)) {
if (visitor.enterStructuredSubEntry(key, key)) { //noinspection unchecked
//noinspection unchecked ((ConfigEntry<Object>) entry).visitInOrder(visitor, value.get(key));
((ConfigEntry<Object>) entry).visitInOrder(visitor, value.get(key)); visitor.leaveAddressableStructuredSubEntry(key, key, key);
visitor.leaveStructuredSubEntry(key, key); }
} });
});
}
visitor.leaveStructuredEntry(this, value); visitor.leaveStructuredEntry(this, value);
} }
} }
@Override @Override
public T deepCopy(T value) { public T deepCopy(T value) {
T copy = instantiateCompoundValue(); T copy = instantiateValue();
value.forEach((String key, Object element) -> { value.forEach((String key, Object element) -> {
//noinspection unchecked //noinspection unchecked
ConfigEntry<Object> elementEntry = (ConfigEntry<Object>) compoundEntries.get(key); ConfigEntry<Object> elementEntry = (ConfigEntry<Object>) compoundEntries.get(key);

View File

@@ -62,7 +62,7 @@ public class PatchExtensionImpl implements PatchExtension, ReadWriteRelatedExten
if (targetValue != null) { if (targetValue != null) {
targetCompoundValue = targetValue; targetCompoundValue = targetValue;
} else { } else {
targetCompoundValue = compoundEntry.instantiateCompoundValue(); targetCompoundValue = compoundEntry.instantiateValue();
} }
compoundEntry.subEntries().forEach((key, subEntry) -> { compoundEntry.subEntries().forEach((key, subEntry) -> {
if (!patchInfo.containsEntry(subEntry)) { if (!patchInfo.containsEntry(subEntry)) {
@@ -70,7 +70,7 @@ public class PatchExtensionImpl implements PatchExtension, ReadWriteRelatedExten
} }
compoundEntry.set( compoundEntry.set(
targetCompoundValue, key, patch( targetCompoundValue, key, patch(
subEntry, (ConfigEntry<Object>) subEntry,
compoundEntry.get(targetCompoundValue, key), compoundEntry.get(targetCompoundValue, key),
compoundEntry.get(patchValue, key), compoundEntry.get(patchValue, key),
patchInfo patchInfo

View File

@@ -2,6 +2,7 @@ package de.siphalor.tweed5.defaultextensions.pather.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor; import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.entry.StructuredConfigEntry;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
@@ -16,10 +17,19 @@ public class PathTrackingConfigEntryValueVisitor implements ConfigEntryValueVisi
} }
@Override @Override
public <T> boolean enterStructuredEntry(ConfigEntry<T> entry, T value) { public <T> boolean enterStructuredEntry(StructuredConfigEntry<T> entry, T value) {
return delegate.enterStructuredEntry(entry, value); return delegate.enterStructuredEntry(entry, value);
} }
@Override
public boolean enterAddressableStructuredSubEntry(String entryKey, String valueKey, String dataKey) {
boolean enter = delegate.enterAddressableStructuredSubEntry(entryKey, valueKey, dataKey);
if (enter) {
pathTracking.pushPathPart(entryKey, valueKey);
}
return enter;
}
@Override @Override
public boolean enterStructuredSubEntry(String entryKey, String valueKey) { public boolean enterStructuredSubEntry(String entryKey, String valueKey) {
boolean enter = delegate.enterStructuredSubEntry(entryKey, valueKey); boolean enter = delegate.enterStructuredSubEntry(entryKey, valueKey);
@@ -29,6 +39,12 @@ public class PathTrackingConfigEntryValueVisitor implements ConfigEntryValueVisi
return enter; return enter;
} }
@Override
public void leaveAddressableStructuredSubEntry(String entryKey, String valueKey, String dataKey) {
delegate.leaveAddressableStructuredSubEntry(entryKey, valueKey, dataKey);
pathTracking.popPathPart();
}
@Override @Override
public void leaveStructuredSubEntry(String entryKey, String valueKey) { public void leaveStructuredSubEntry(String entryKey, String valueKey) {
delegate.leaveStructuredSubEntry(entryKey, valueKey); delegate.leaveStructuredSubEntry(entryKey, valueKey);
@@ -36,7 +52,7 @@ public class PathTrackingConfigEntryValueVisitor implements ConfigEntryValueVisi
} }
@Override @Override
public <T> void leaveStructuredEntry(ConfigEntry<T> entry, T value) { public <T> void leaveStructuredEntry(StructuredConfigEntry<T> entry, T value) {
delegate.leaveStructuredEntry(entry, value); delegate.leaveStructuredEntry(entry, value);
} }
} }

View File

@@ -3,6 +3,7 @@ package de.siphalor.tweed5.defaultextensions.validation.impl;
import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor; import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.entry.StructuredConfigEntry;
import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext; import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer; import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
@@ -290,12 +291,12 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
} }
@Override @Override
public <T> boolean enterStructuredEntry(ConfigEntry<T> entry, T value) { public <T> boolean enterStructuredEntry(StructuredConfigEntry<T> entry, T value) {
return true; return true;
} }
@Override @Override
public <T> void leaveStructuredEntry(ConfigEntry<T> entry, T value) { public <T> void leaveStructuredEntry(StructuredConfigEntry<T> entry, T value) {
visitEntry(entry, value); visitEntry(entry, value);
} }
} }

View File

@@ -234,7 +234,7 @@ public class TweedEntryReaderWriterImpls {
} }
Map<String, ConfigEntry<?>> compoundEntries = entry.subEntries(); Map<String, ConfigEntry<?>> compoundEntries = entry.subEntries();
T compoundValue = entry.instantiateCompoundValue(); T compoundValue = entry.instantiateValue();
while (true) { while (true) {
try { try {
TweedDataToken token = reader.readToken(); TweedDataToken token = reader.readToken();

View File

@@ -37,7 +37,7 @@ public class DefaultPresetWeavingProcessor<T> implements TweedPojoWeavingExtensi
private T instantiateEntry(ConfigEntry<T> entry) { private T instantiateEntry(ConfigEntry<T> entry) {
if (entry instanceof CompoundConfigEntry) { if (entry instanceof CompoundConfigEntry) {
return ((CompoundConfigEntry<T>) entry).instantiateCompoundValue(); return ((CompoundConfigEntry<T>) entry).instantiateValue();
} else { } else {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Can only determine default preset from instantiation for POJOs. " "Can only determine default preset from instantiation for POJOs. "

View File

@@ -6,7 +6,6 @@ import de.siphalor.tweed5.core.api.entry.BaseConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor; import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry; import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry;
import org.jspecify.annotations.Nullable;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -41,7 +40,7 @@ public class StaticPojoCompoundConfigEntry<T> extends BaseConfigEntry<T> impleme
} }
@Override @Override
public <V> void set(T compoundValue, String key, V value) { public void set(T compoundValue, String key, Object value) {
SubEntry subEntry = subEntries.get(key); SubEntry subEntry = subEntries.get(key);
if (subEntry == null) { if (subEntry == null) {
throw new IllegalArgumentException("Unknown config entry: " + key); throw new IllegalArgumentException("Unknown config entry: " + key);
@@ -55,22 +54,21 @@ public class StaticPojoCompoundConfigEntry<T> extends BaseConfigEntry<T> impleme
} }
@Override @Override
public <V> V get(T compoundValue, String key) { public Object get(T compoundValue, String key) {
SubEntry subEntry = subEntries.get(key); SubEntry subEntry = subEntries.get(key);
if (subEntry == null) { if (subEntry == null) {
throw new IllegalArgumentException("Unknown config entry: " + key); throw new IllegalArgumentException("Unknown config entry: " + key);
} }
try { try {
//noinspection unchecked return subEntry.getter().invoke(compoundValue);
return (V) subEntry.getter().invoke(compoundValue);
} catch (Throwable e) { } 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 \"" + key + "\"", e);
} }
} }
@Override @Override
public T instantiateCompoundValue() { public T instantiateValue() {
try { try {
return noArgsConstructor.get(); return noArgsConstructor.get();
} catch (Throwable e) { } catch (Throwable e) {
@@ -82,22 +80,24 @@ public class StaticPojoCompoundConfigEntry<T> extends BaseConfigEntry<T> impleme
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) { public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
if (visitor.enterStructuredEntry(this, value)) { if (visitor.enterStructuredEntry(this, value)) {
subEntries.forEach((key, entry) -> { subEntries.forEach((key, entry) -> {
if (visitor.enterStructuredSubEntry(key, key)) { if (visitor.enterAddressableStructuredSubEntry(key, key, key)) {
try { try {
Object subValue = entry.getter().invoke(value); Object subValue = entry.getter().invoke(value);
//noinspection unchecked //noinspection unchecked
((ConfigEntry<Object>) entry.configEntry()).visitInOrder(visitor, subValue); ((ConfigEntry<Object>) entry.configEntry()).visitInOrder(visitor, subValue);
visitor.leaveAddressableStructuredSubEntry(key, key, key);
} catch (Throwable e) { } catch (Throwable e) {
throw new RuntimeException("Failed to get compound sub entry value \"" + key + "\""); throw new RuntimeException("Failed to get compound sub entry value \"" + key + "\"");
} }
} }
}); });
visitor.leaveStructuredEntry(this, value);
} }
} }
@Override @Override
public T deepCopy(T value) { public T deepCopy(T value) {
T copy = instantiateCompoundValue(); T copy = instantiateValue();
for (SubEntry subEntry : subEntries.values()) { for (SubEntry subEntry : subEntries.values()) {
try { try {
Object subValue = subEntry.getter().invoke(value); Object subValue = subEntry.getter().invoke(value);