diff --git a/buildSrc/src/main/kotlin/element.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/element.java-conventions.gradle.kts index 7a9939c..0d6f60b 100644 --- a/buildSrc/src/main/kotlin/element.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/element.java-conventions.gradle.kts @@ -3,7 +3,7 @@ plugins { } group = "com.github.steanky" -version = "0.5.0" +version = "0.6.0" java { toolchain.languageVersion.set(JavaLanguageVersion.of(17)) diff --git a/core/src/main/java/com/github/steanky/element/core/ElementFactory.java b/core/src/main/java/com/github/steanky/element/core/ElementFactory.java index c67e4d6..0a6fa06 100644 --- a/core/src/main/java/com/github/steanky/element/core/ElementFactory.java +++ b/core/src/main/java/com/github/steanky/element/core/ElementFactory.java @@ -3,7 +3,6 @@ import com.github.steanky.element.core.context.ElementContext; import com.github.steanky.element.core.dependency.DependencyProvider; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * Creates an element from some data. diff --git a/core/src/main/java/com/github/steanky/element/core/ElementInspector.java b/core/src/main/java/com/github/steanky/element/core/ElementInspector.java index 226ff9e..f065661 100644 --- a/core/src/main/java/com/github/steanky/element/core/ElementInspector.java +++ b/core/src/main/java/com/github/steanky/element/core/ElementInspector.java @@ -1,9 +1,9 @@ package com.github.steanky.element.core; +import com.github.steanky.element.core.context.ContextManager; import com.github.steanky.ethylene.core.processor.ConfigProcessor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import com.github.steanky.element.core.context.ContextManager; import java.util.Objects; @@ -20,24 +20,6 @@ public interface ElementInspector { */ @NotNull Information inspect(final @NotNull Class elementClass); - /** - * Represents information about an element class. - */ - record Information(@Nullable ConfigProcessor processor, @NotNull ElementFactory factory, @NotNull CachePreference cachePreference) { - /** - * Represents information extracted from an Element Model-compliant class. - * - * @param processor the processor used to process the data associated with the class, which may be null if there - * is no data required to construct the class - * @param factory the factory used to create instances of the class, which may never be null - * @param cachePreference how this element class prefers to be cached when it is instantiated - */ - public Information { - Objects.requireNonNull(factory); - Objects.requireNonNull(cachePreference); - } - } - /** * Describes how element objects prefer to be cached. */ @@ -58,4 +40,23 @@ enum CachePreference { */ NO_CACHE } + + /** + * Represents information about an element class. + */ + record Information(@Nullable ConfigProcessor processor, @NotNull ElementFactory factory, + @NotNull CachePreference cachePreference) { + /** + * Represents information extracted from an Element Model-compliant class. + * + * @param processor the processor used to process the data associated with the class, which may be null if + * there is no data required to construct the class + * @param factory the factory used to create instances of the class, which may never be null + * @param cachePreference how this element class prefers to be cached when it is instantiated + */ + public Information { + Objects.requireNonNull(factory); + Objects.requireNonNull(cachePreference); + } + } } diff --git a/core/src/main/java/com/github/steanky/element/core/annotation/Cache.java b/core/src/main/java/com/github/steanky/element/core/annotation/Cache.java index 2da279a..decd76f 100644 --- a/core/src/main/java/com/github/steanky/element/core/annotation/Cache.java +++ b/core/src/main/java/com/github/steanky/element/core/annotation/Cache.java @@ -11,6 +11,7 @@ public @interface Cache { /** * Whether this element object should always be cached. Defaults to true. + * * @return true if this element should be cached, false otherwise */ boolean value() default true; diff --git a/core/src/main/java/com/github/steanky/element/core/annotation/DataPath.java b/core/src/main/java/com/github/steanky/element/core/annotation/DataPath.java index d402b96..4c89153 100644 --- a/core/src/main/java/com/github/steanky/element/core/annotation/DataPath.java +++ b/core/src/main/java/com/github/steanky/element/core/annotation/DataPath.java @@ -25,6 +25,7 @@ * If the element object referred to by this path should be cached or not. If true, the element object for this path * will only be created once, and the same instance will be shared across all cache-enabled dependencies. Defaults * to {@code true}. + * * @return true if this element object should be cached, false otherwise */ boolean cache() default true; diff --git a/core/src/main/java/com/github/steanky/element/core/context/BasicContextManager.java b/core/src/main/java/com/github/steanky/element/core/context/BasicContextManager.java index e1a3500..33fb772 100644 --- a/core/src/main/java/com/github/steanky/element/core/context/BasicContextManager.java +++ b/core/src/main/java/com/github/steanky/element/core/context/BasicContextManager.java @@ -49,8 +49,8 @@ public void registerElementClass(final @NotNull Class elementClass) { //don't bother to register if we are cache-unspecified if (preference != ElementInspector.CachePreference.UNSPECIFIED) { - elementContextSource.cacheRegistry().register(elementKey, - preference == ElementInspector.CachePreference.CACHE); + elementContextSource.cacheRegistry() + .register(elementKey, preference == ElementInspector.CachePreference.CACHE); } } diff --git a/core/src/main/java/com/github/steanky/element/core/context/BasicElementContext.java b/core/src/main/java/com/github/steanky/element/core/context/BasicElementContext.java index c4db140..d1c4607 100644 --- a/core/src/main/java/com/github/steanky/element/core/context/BasicElementContext.java +++ b/core/src/main/java/com/github/steanky/element/core/context/BasicElementContext.java @@ -22,8 +22,6 @@ * Basic implementation of {@link ElementContext}. */ public class BasicElementContext implements ElementContext { - private record DataInfo(Object data, Key type) {} - private final Registry> processorRegistry; private final Registry> factoryRegistry; private final Registry cacheRegistry; @@ -31,10 +29,8 @@ private record DataInfo(Object data, Key type) {} private final DataLocator dataLocator; private final KeyExtractor typeKeyExtractor; private final ConfigNode rootNode; - private final Map dataObjects; private final Map elementObjects; - /** * Creates a new instance of this class. * @@ -43,16 +39,16 @@ private record DataInfo(Object data, Key type) {} * @param factoryRegistry the Registry used to hold references to {@link ElementFactory} instances needed to * construct element objects * @param cacheRegistry the Registry used to determine if element types request caching or not - * @param pathSplitter the {@link PathSplitter} used to split path keys + * @param pathSplitter the {@link PathSplitter} used to split path keys * @param dataLocator the {@link DataLocator} implementation used to locate data objects from identifiers * @param typeKeyExtractor the {@link KeyExtractor} implementation used to extract type keys from nodes * @param rootNode the {@link ConfigNode} used as the root (may contain additional element data) */ public BasicElementContext(final @NotNull Registry> processorRegistry, final @NotNull Registry> factoryRegistry, - final @NotNull Registry cacheRegistry, - final @NotNull PathSplitter pathSplitter, final @NotNull DataLocator dataLocator, - final @NotNull KeyExtractor typeKeyExtractor, final @NotNull ConfigNode rootNode) { + final @NotNull Registry cacheRegistry, final @NotNull PathSplitter pathSplitter, + final @NotNull DataLocator dataLocator, final @NotNull KeyExtractor typeKeyExtractor, + final @NotNull ConfigNode rootNode) { this.processorRegistry = Objects.requireNonNull(processorRegistry); this.factoryRegistry = Objects.requireNonNull(factoryRegistry); this.cacheRegistry = Objects.requireNonNull(cacheRegistry); @@ -65,20 +61,10 @@ public BasicElementContext(final @NotNull Registry> processor this.elementObjects = new HashMap<>(4); } - @Override - public @NotNull TElement provide(@Nullable String path, @NotNull DependencyProvider dependencyProvider) { - return provideInternal(path, dependencyProvider, false); - } - - @Override - public @NotNull TElement provideAndCache(final @Nullable String path, - final @NotNull DependencyProvider dependencyProvider) { - return provideInternal(path, dependencyProvider, true); - } - @SuppressWarnings("unchecked") - private @NotNull TElement provideInternal(final @Nullable String path, - final @NotNull DependencyProvider dependencyProvider, final boolean provideCache) { + @Override + public @NotNull TElement provide(@Nullable String path, @NotNull DependencyProvider dependencyProvider, + final boolean cache) { final boolean cacheElement; final ConfigNode dataNode = dataLocator.locate(rootNode, path); @@ -86,9 +72,8 @@ public BasicElementContext(final @NotNull Registry> processor if (cacheRegistry.contains(objectType)) { cacheElement = cacheRegistry.lookup(objectType); - } - else { - cacheElement = provideCache; + } else { + cacheElement = cache; } final String normalizedPath = path == null ? null : pathSplitter.normalize(path); @@ -101,8 +86,7 @@ public BasicElementContext(final @NotNull Registry> processor final DataInfo dataInfo; if (dataObjects.containsKey(normalizedPath)) { dataInfo = dataObjects.get(normalizedPath); - } - else { + } else { try { Object data = processorRegistry.contains(objectType) ? processorRegistry.lookup(objectType).dataFromElement(dataNode) : null; @@ -113,8 +97,8 @@ public BasicElementContext(final @NotNull Registry> processor } } - final TElement element = (TElement) ((ElementFactory) factoryRegistry.lookup(dataInfo.type)) - .make(dataInfo.data, this, dependencyProvider); + final TElement element = (TElement) ((ElementFactory) factoryRegistry.lookup( + dataInfo.type)).make(dataInfo.data, this, dependencyProvider); if (cacheElement) { elementObjects.put(normalizedPath, element); @@ -128,6 +112,13 @@ public BasicElementContext(final @NotNull Registry> processor return rootNode; } + @Override + public @NotNull PathSplitter pathSplitter() { + return pathSplitter; + } + + private record DataInfo(Object data, Key type) {} + /** * Basic implementation of {@link ElementContext.Source}. */ @@ -148,7 +139,7 @@ public static class Source implements ElementContext.Source { * source, used for referencing {@link ElementFactory} objects * @param cacheRegistry the Registry passed to all BasicElementContext instances created by this source, * used to determine whether element objects should be cached. - * @param pathSplitter the {@link PathSplitter} used to split path keys + * @param pathSplitter the {@link PathSplitter} used to split path keys * @param dataLocator the {@link DataLocator} passed to all BasicDataContext instances created by this * source * @param keyExtractor the {@link KeyExtractor} passed to all BasicDataContext instances created by this @@ -156,9 +147,8 @@ public static class Source implements ElementContext.Source { */ public Source(final @NotNull Registry> processorRegistry, final @NotNull Registry> factoryRegistry, - final @NotNull Registry cacheRegistry, - final @NotNull PathSplitter pathSplitter, final @NotNull DataLocator dataLocator, - final @NotNull KeyExtractor keyExtractor) { + final @NotNull Registry cacheRegistry, final @NotNull PathSplitter pathSplitter, + final @NotNull DataLocator dataLocator, final @NotNull KeyExtractor keyExtractor) { this.processorRegistry = Objects.requireNonNull(processorRegistry); this.factoryRegistry = Objects.requireNonNull(factoryRegistry); this.cacheRegistry = Objects.requireNonNull(cacheRegistry); diff --git a/core/src/main/java/com/github/steanky/element/core/context/ElementContext.java b/core/src/main/java/com/github/steanky/element/core/context/ElementContext.java index 3f2a7c8..cf98f0b 100644 --- a/core/src/main/java/com/github/steanky/element/core/context/ElementContext.java +++ b/core/src/main/java/com/github/steanky/element/core/context/ElementContext.java @@ -1,111 +1,535 @@ package com.github.steanky.element.core.context; +import com.github.steanky.element.core.ElementException; import com.github.steanky.element.core.ElementFactory; import com.github.steanky.element.core.Registry; +import com.github.steanky.element.core.annotation.Cache; import com.github.steanky.element.core.dependency.DependencyProvider; +import com.github.steanky.element.core.key.PathSplitter; +import com.github.steanky.ethylene.core.ConfigElement; +import com.github.steanky.ethylene.core.collection.ConfigEntry; +import com.github.steanky.ethylene.core.collection.ConfigList; import com.github.steanky.ethylene.core.collection.ConfigNode; +import com.github.steanky.ethylene.core.processor.ConfigProcessException; import com.github.steanky.ethylene.core.processor.ConfigProcessor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import com.github.steanky.element.core.annotation.Cache; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.IntFunction; /** * Object holding contextual elements. */ public interface ElementContext { /** - * Provides a contextual element object given a path key. This prefers to perform no caching of element objects, but - * will do so if the element object itself specifies the {@link Cache} annotation. + * The default exception handler used by providers of maps and collections of elements. Simply rethrows the + * exception as-is. + */ + Consumer DEFAULT_EXCEPTION_HANDLER = e -> { + throw e; + }; + + /** + * Provides a contextual element object given a path key, dependency provider, and caching preference. If true, the + * element will attempt to be cached (if it does not specify otherwise using the {@link Cache} annotation). If + * false, no caching will be performed, unless overridden by the element itself (again using {@link Cache}). * * @param path the path key * @param dependencyProvider the {@link DependencyProvider} used to provide dependencies + * @param cache true if this element should be cached, false otherwise * @param the type of the element object * @return the contextual element object */ @NotNull TElement provide(final @Nullable String path, - final @NotNull DependencyProvider dependencyProvider); + final @NotNull DependencyProvider dependencyProvider, final boolean cache); /** - * Convenience overload for {@link ElementContext#provide(String, DependencyProvider)}. This will provide the root - * element using the given {@link DependencyProvider}. + * Convenience overload for {@link ElementContext#provide(String, DependencyProvider, boolean)}. This will provide + * the root element using the given {@link DependencyProvider}, and no caching. * * @param dependencyProvider the DependencyProvider used to provide dependencies * @param the type of the contextual element object * @return the root element object */ default @NotNull TElement provide(final @NotNull DependencyProvider dependencyProvider) { - return provide(null, dependencyProvider); + return provide(null, dependencyProvider, false); } /** - * Convenience overload for {@link ElementContext#provide(String, DependencyProvider)}. This will provide the - * element specified by the path, using an empty dependency provider ({@link DependencyProvider#EMPTY}). + * Convenience overload for {@link ElementContext#provide(String, DependencyProvider, boolean)}. This will provide + * the element specified by the path, using an empty dependency provider ({@link DependencyProvider#EMPTY}), and no + * caching. * * @param path the data path * @param the type of the contextual element object * @return the element object */ default @NotNull TElement provide(final @Nullable String path) { - return provide(path, DependencyProvider.EMPTY); + return provide(path, DependencyProvider.EMPTY, false); } /** - * Convenience overload for {@link ElementContext#provide(String, DependencyProvider)}. This will provide the root - * element using an empty dependency provider ({@link DependencyProvider#EMPTY}). + * Convenience overload for {@link ElementContext#provide(String, DependencyProvider, boolean)}. This will provide + * the root element using an empty dependency provider ({@link DependencyProvider#EMPTY}), without caching. * * @param the type of the contextual element object * @return the element object */ default @NotNull TElement provide() { - return provide(null, DependencyProvider.EMPTY); + return provide(null, DependencyProvider.EMPTY, false); } /** - * Provides a contextual element object given a path key. If null, will attempt to provide the root node. This - * attempts to perform caching, if allowed to do so by the element object. + * Provides a collection of elements, given a valid path string pointing at a {@link ConfigList}, relative to this + * context's root node. This method catches {@link ElementException}s that are thrown when elements are + * provided, and relays them to the supplied consumer after the list has been iterated. This can allow certain + * elements to fail to load without preventing others from doing so. * - * @param path the path key + * @param listPath the path string pointing to the ConfigList * @param dependencyProvider the {@link DependencyProvider} used to provide dependencies - * @param the type of the element object - * @return the contextual element object + * @param cache whether to prefer caching elements or not + * @param collectionSupplier the function used to create a {@link Collection} implementation based on a known size + * @param exceptionHandler the consumer used to handle exceptions + * @param the type of element object + * @param the type of collection + * @return a collection of provided element objects */ - @NotNull TElement provideAndCache(final @Nullable String path, - final @NotNull DependencyProvider dependencyProvider); + default @NotNull > TCollection provideCollection( + final @NotNull String listPath, final @NotNull DependencyProvider dependencyProvider, final boolean cache, + final @NotNull IntFunction collectionSupplier, + final @NotNull Consumer exceptionHandler) { + Objects.requireNonNull(listPath); + Objects.requireNonNull(dependencyProvider); + Objects.requireNonNull(collectionSupplier); + Objects.requireNonNull(exceptionHandler); + + PathSplitter pathSplitter = pathSplitter(); + + Object[] ethylenePath = pathSplitter.splitPathKey(listPath); + String normalized = pathSplitter.normalize(listPath); + + ConfigList listElement; + try { + listElement = rootNode().getListOrThrow(ethylenePath); + } + catch (ConfigProcessException e) { + throw new ElementException("expected ConfigList at '" + normalized + "'", e); + } + + TCollection elementCollection = collectionSupplier.apply(listElement.size()); + + ElementException exception = null; + for (int i = 0; i < listElement.size(); i++) { + try { + elementCollection.add(provide(pathSplitter.append(normalized, i), dependencyProvider, cache)); + } catch (ElementException e) { + if (exception == null) { + exception = e; + } else { + exception.addSuppressed(e); + } + } + } + + if (exception != null) { + exceptionHandler.accept(exception); + } + + return elementCollection; + } /** - * Convenience overload for {@link ElementContext#provideAndCache(String, DependencyProvider)}. This will provide the root - * element using the given {@link DependencyProvider}. + * Convenience overload for + * {@link ElementContext#provideCollection(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses a + * default collection supplier {@code ArrayList::new}. * - * @param dependencyProvider the DependencyProvider used to provide dependencies - * @param the type of the contextual element object - * @return the root element object + * @param listPath the path string pointing to the ConfigList + * @param dependencyProvider the {@link DependencyProvider} used to provide dependencies + * @param cache whether to prefer caching elements or not + * @param exceptionHandler the consumer used to handle exceptions + * @param the type of element object + * @return a collection of provided element objects */ - default @NotNull TElement provideAndCache(final @NotNull DependencyProvider dependencyProvider) { - return provideAndCache(null, dependencyProvider); + default @NotNull List provideCollection(final @NotNull String listPath, + final @NotNull DependencyProvider dependencyProvider, final boolean cache, + final @NotNull Consumer exceptionHandler) { + return provideCollection(listPath, dependencyProvider, cache, ArrayList::new, exceptionHandler); } /** - * Convenience overload for {@link ElementContext#provideAndCache(String, DependencyProvider)}. This will provide the element - * specified by the path, using an empty dependency provider ({@link DependencyProvider#EMPTY}). + * Convenience overload for + * {@link ElementContext#provideCollection(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses a + * default collection supplier {@code ArrayList::new} and the empty {@link DependencyProvider}. * - * @param path the data path - * @param the type of the contextual element object - * @return the element object + * @param listPath the path string pointing to the ConfigList + * @param cache whether to prefer caching elements or not + * @param exceptionHandler the consumer used to handle exceptions + * @param the type of element object + * @return a collection of provided element objects */ - default @NotNull TElement provideAndCache(final @Nullable String path) { - return provideAndCache(path, DependencyProvider.EMPTY); + default @NotNull List provideCollection(final @NotNull String listPath, final boolean cache, + final @NotNull Consumer exceptionHandler) { + return provideCollection(listPath, DependencyProvider.EMPTY, cache, ArrayList::new, exceptionHandler); } /** - * Convenience overload for {@link ElementContext#provideAndCache(String, DependencyProvider)}. This will provide the root - * element using an empty dependency provider ({@link DependencyProvider#EMPTY}). + * Convenience overload for + * {@link ElementContext#provideCollection(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses a + * default collection supplier {@code ArrayList::new} and prefers no caching. * - * @param the type of the contextual element object - * @return the element object + * @param listPath the path string pointing to the ConfigList + * @param dependencyProvider the {@link DependencyProvider} used to provide dependencies + * @param exceptionHandler the consumer used to handle exceptions + * @param the type of element object + * @return a collection of provided element objects */ - default @NotNull TElement provideAndCache() { - return provideAndCache(null, DependencyProvider.EMPTY); + default @NotNull List provideCollection(final @NotNull String listPath, + final @NotNull DependencyProvider dependencyProvider, + final @NotNull Consumer exceptionHandler) { + return provideCollection(listPath, dependencyProvider, false, ArrayList::new, exceptionHandler); + } + + /** + * Convenience overload for + * {@link ElementContext#provideCollection(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses a + * default collection supplier {@code ArrayList::new}, the empty {@link DependencyProvider}, and prefers no + * caching. + * + * @param listPath the path string pointing to the ConfigList + * @param exceptionHandler the consumer used to handle exceptions + * @param the type of element object + * @return a collection of provided element objects + */ + default @NotNull List provideCollection(final @NotNull String listPath, + final @NotNull Consumer exceptionHandler) { + return provideCollection(listPath, DependencyProvider.EMPTY, false, ArrayList::new, exceptionHandler); + } + + /** + * Convenience overload for + * {@link ElementContext#provideCollection(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the + * default exception handler {@link ElementContext#DEFAULT_EXCEPTION_HANDLER}. + * + * @param listPath the path string pointing to the ConfigList + * @param dependencyProvider the {@link DependencyProvider} used to provide dependencies + * @param cache whether to prefer caching elements or not + * @param collectionSupplier the function used to create a {@link Collection} implementation based on a known size + * @param the type of element object + * @param the type of collection + * @return a collection of provided element objects + */ + default @NotNull > TCollection provideCollection( + final @NotNull String listPath, final @NotNull DependencyProvider dependencyProvider, final boolean cache, + final @NotNull IntFunction collectionSupplier) { + return provideCollection(listPath, dependencyProvider, cache, collectionSupplier, DEFAULT_EXCEPTION_HANDLER); + } + + /** + * Convenience overload for + * {@link ElementContext#provideCollection(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the + * default exception handler {@link ElementContext#DEFAULT_EXCEPTION_HANDLER} and the empty + * {@link DependencyProvider}. + * + * @param listPath the path string pointing to the ConfigList + * @param cache whether to prefer caching elements or not + * @param collectionSupplier the function used to create a {@link Collection} implementation based on a known size + * @param the type of element object + * @param the type of collection + * @return a collection of provided element objects + */ + default @NotNull > TCollection provideCollection( + final @NotNull String listPath, final boolean cache, + final @NotNull IntFunction collectionSupplier) { + return provideCollection(listPath, DependencyProvider.EMPTY, cache, collectionSupplier, + DEFAULT_EXCEPTION_HANDLER); + } + + /** + * Convenience overload for + * {@link ElementContext#provideCollection(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the + * default exception handler {@link ElementContext#DEFAULT_EXCEPTION_HANDLER}, and the default collection supplier + * {@code ArrayList::new}. + * + * @param listPath the path string pointing to the ConfigList + * @param dependencyProvider the {@link DependencyProvider} used to provide dependencies + * @param cache whether to prefer caching elements or not + * @param the type of element object + * @return a collection of provided element objects + */ + default @NotNull List provideCollection(final @NotNull String listPath, + final @NotNull DependencyProvider dependencyProvider, final boolean cache) { + return provideCollection(listPath, dependencyProvider, cache, ArrayList::new, DEFAULT_EXCEPTION_HANDLER); + } + + /** + * Convenience overload for + * {@link ElementContext#provideCollection(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the + * default exception handler {@link ElementContext#DEFAULT_EXCEPTION_HANDLER}, the empty {@link DependencyProvider}, + * and the default collection supplier {@code ArrayList::new}. + * + * @param listPath the path string pointing to the ConfigList + * @param cache whether to prefer caching elements or not + * @param the type of element object + * @return a collection of provided element objects + */ + default @NotNull List provideCollection(final @NotNull String listPath, final boolean cache) { + return provideCollection(listPath, DependencyProvider.EMPTY, cache, ArrayList::new, DEFAULT_EXCEPTION_HANDLER); + } + + /** + * Convenience overload for + * {@link ElementContext#provideCollection(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the + * default exception handler {@link ElementContext#DEFAULT_EXCEPTION_HANDLER}, prefers no caching, and uses the + * default collection supplier {@code ArrayList::new}. + * + * @param listPath the path string pointing to the ConfigList + * @param dependencyProvider the {@link DependencyProvider} used to provide dependencies + * @param the type of element object + * @return a collection of provided element objects + */ + default @NotNull List provideCollection(final @NotNull String listPath, + final @NotNull DependencyProvider dependencyProvider) { + return provideCollection(listPath, dependencyProvider, false, ArrayList::new, DEFAULT_EXCEPTION_HANDLER); + } + + /** + * Convenience overload for + * {@link ElementContext#provideCollection(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the + * default exception handler {@link ElementContext#DEFAULT_EXCEPTION_HANDLER}, prefers no caching, the default + * collection supplier {@code ArrayList::new}, and the empty dependency provider. + * + * @param listPath the path string pointing to the ConfigList + * @param the type of element object + * @return a collection of provided element objects + */ + default @NotNull List provideCollection(final @NotNull String listPath) { + return provideCollection(listPath, DependencyProvider.EMPTY, false, ArrayList::new, DEFAULT_EXCEPTION_HANDLER); + } + + /** + * Provides a map of elements, given a valid path string pointing at a {@link ConfigNode}, relative to this + * context's root node. This method catches {@link ElementException}s that are thrown when elements are + * provided, and relays them to the supplied consumer after the list has been iterated. This can allow certain + * elements to fail to load without preventing others from doing so. + * + * @param nodePath the path string pointing to the ConfigNode + * @param dependencyProvider the {@link DependencyProvider} used to provide dependencies + * @param cache whether to prefer caching elements or not + * @param mapSupplier the function used to create a {@link Map} implementation based on a known size + * @param exceptionHandler the consumer used to handle exceptions + * @param the type of element object + * @param > the type of map + * @return a collection of provided element objects + */ + default @NotNull > TMap provideMap(final @NotNull String nodePath, + final @NotNull DependencyProvider dependencyProvider, final boolean cache, + final @NotNull IntFunction mapSupplier, + final @NotNull Consumer exceptionHandler) { + Objects.requireNonNull(nodePath); + Objects.requireNonNull(dependencyProvider); + Objects.requireNonNull(mapSupplier); + Objects.requireNonNull(exceptionHandler); + + PathSplitter pathSplitter = pathSplitter(); + + Object[] ethylenePath = pathSplitter.splitPathKey(nodePath); + String normalized = pathSplitter.normalize(nodePath); + + ConfigNode nodeElement; + try { + nodeElement = rootNode().getNodeOrThrow(ethylenePath); + } + catch (ConfigProcessException e) { + throw new ElementException("expected ConfigNode at '" + normalized + "'", e); + } + + TMap elementMap = mapSupplier.apply(nodeElement.size()); + + ElementException exception = null; + for (ConfigEntry entry : nodeElement.entryCollection()) { + try { + elementMap.put(entry.getKey(), + provide(pathSplitter.append(normalized, entry.getKey()), dependencyProvider, cache)); + } catch (ElementException e) { + if (exception == null) { + exception = e; + } else { + exception.addSuppressed(e); + } + } + } + + if (exception != null) { + exceptionHandler.accept(exception); + } + + return elementMap; + } + + /** + * Convenience overload for + * {@link ElementContext#provideMap(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses a default map + * supplier {@code LinkedHashMap::new}. + * + * @param nodePath the path string pointing to the ConfigNode + * @param dependencyProvider the {@link DependencyProvider} used to provide dependencies + * @param cache whether to prefer caching elements or not + * @param exceptionHandler the consumer used to handle exceptions + * @param the type of element object + * @return a map of provided element objects + */ + default @NotNull Map provideMap(final @NotNull String nodePath, + final @NotNull DependencyProvider dependencyProvider, final boolean cache, + final @NotNull Consumer exceptionHandler) { + return provideMap(nodePath, dependencyProvider, cache, LinkedHashMap::new, exceptionHandler); + } + + /** + * Convenience overload for + * {@link ElementContext#provideMap(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses a default map + * supplier {@code LinkedHashMap::new} and the empty {@link DependencyProvider}. + * + * @param nodePath the path string pointing to the ConfigNode + * @param cache whether to prefer caching elements or not + * @param exceptionHandler the consumer used to handle exceptions + * @param the type of element object + * @return a map of provided element objects + */ + default @NotNull Map provideMap(final @NotNull String nodePath, final boolean cache, + final @NotNull Consumer exceptionHandler) { + return provideMap(nodePath, DependencyProvider.EMPTY, cache, LinkedHashMap::new, exceptionHandler); + } + + /** + * Convenience overload for + * {@link ElementContext#provideMap(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses a default map + * supplier {@code LinkedHashMap::new} and prefers no caching. + * + * @param nodePath the path string pointing to the ConfigNode + * @param dependencyProvider the {@link DependencyProvider} used to provide dependencies + * @param exceptionHandler the consumer used to handle exceptions + * @param the type of element object + * @return a map of provided element objects + */ + default @NotNull Map provideMap(final @NotNull String nodePath, + final @NotNull DependencyProvider dependencyProvider, + final @NotNull Consumer exceptionHandler) { + return provideMap(nodePath, dependencyProvider, false, LinkedHashMap::new, exceptionHandler); + } + + /** + * Convenience overload for + * {@link ElementContext#provideMap(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses a default map + * supplier {@code LinkedHashMap::new}, prefers no caching, and uses the empty {@link DependencyProvider}. + * + * @param nodePath the path string pointing to the ConfigNode + * @param exceptionHandler the consumer used to handle exceptions + * @param the type of element object + * @return a map of provided element objects + */ + default @NotNull Map provideMap(final @NotNull String nodePath, + final @NotNull Consumer exceptionHandler) { + return provideMap(nodePath, DependencyProvider.EMPTY, false, LinkedHashMap::new, exceptionHandler); + } + + /** + * Convenience overload for + * {@link ElementContext#provideMap(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the default + * exception handler {@link ElementContext#DEFAULT_EXCEPTION_HANDLER}. + * + * @param nodePath the path string pointing to the ConfigNode + * @param dependencyProvider the {@link DependencyProvider} used to provide dependencies + * @param cache whether to prefer caching elements or not + * @param mapSupplier the function used to create a {@link Map} implementation based on a known size + * @param the type of element object + * @param the type of map + * @return a map of provided element objects + */ + default @NotNull > TMap provideMap(final @NotNull String nodePath, + final @NotNull DependencyProvider dependencyProvider, final boolean cache, + final @NotNull IntFunction mapSupplier) { + return provideMap(nodePath, dependencyProvider, cache, mapSupplier, DEFAULT_EXCEPTION_HANDLER); + } + + /** + * Convenience overload for + * {@link ElementContext#provideMap(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the default + * exception handler {@link ElementContext#DEFAULT_EXCEPTION_HANDLER} and the empty {@link DependencyProvider}. + * + * @param nodePath the path string pointing to the ConfigNode + * @param cache whether to prefer caching elements or not + * @param mapSupplier the function used to create a {@link Map} implementation based on a known size + * @param the type of element object + * @param the type of map + * @return a map of provided element objects + */ + default @NotNull > TMap provideMap(final @NotNull String nodePath, + final boolean cache, final @NotNull IntFunction mapSupplier) { + return provideMap(nodePath, DependencyProvider.EMPTY, cache, mapSupplier, DEFAULT_EXCEPTION_HANDLER); + } + + /** + * Convenience overload for + * {@link ElementContext#provideMap(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the default + * exception handler {@link ElementContext#DEFAULT_EXCEPTION_HANDLER} and the default map supplier + * {@code LinkedHashMap::new}. + * + * @param nodePath the path string pointing to the ConfigNode + * @param dependencyProvider the {@link DependencyProvider} used to provide dependencies + * @param cache whether to prefer caching elements or not + * @param the type of element object + * @return a map of provided element objects + */ + default @NotNull Map provideMap(final @NotNull String nodePath, + final @NotNull DependencyProvider dependencyProvider, final boolean cache) { + return provideMap(nodePath, dependencyProvider, cache, LinkedHashMap::new, DEFAULT_EXCEPTION_HANDLER); + } + + /** + * Convenience overload for + * {@link ElementContext#provideMap(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the default + * exception handler {@link ElementContext#DEFAULT_EXCEPTION_HANDLER}, the default map supplier + * {@code LinkedHashMap::new}, and the empty {@link DependencyProvider}. + * + * @param nodePath the path string pointing to the ConfigNode + * @param cache whether to prefer caching elements or not + * @param the type of element object + * @return a map of provided element objects + */ + default @NotNull Map provideMap(final @NotNull String nodePath, final boolean cache) { + return provideMap(nodePath, DependencyProvider.EMPTY, cache, LinkedHashMap::new, DEFAULT_EXCEPTION_HANDLER); + } + + /** + * Convenience overload for + * {@link ElementContext#provideMap(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the default + * exception handler {@link ElementContext#DEFAULT_EXCEPTION_HANDLER}, the default map supplier + * {@code LinkedHashMap::new}, and prefers no caching. + * + * @param nodePath the path string pointing to the ConfigNode + * @param dependencyProvider the {@link DependencyProvider} used to provide dependencies + * @param the type of element object + * @return a map of provided element objects + */ + default @NotNull Map provideMap(final @NotNull String nodePath, + final @NotNull DependencyProvider dependencyProvider) { + return provideMap(nodePath, dependencyProvider, false, LinkedHashMap::new, DEFAULT_EXCEPTION_HANDLER); + } + + /** + * Convenience overload for + * {@link ElementContext#provideMap(String, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the default + * exception handler {@link ElementContext#DEFAULT_EXCEPTION_HANDLER}, the default map supplier + * {@code LinkedHashMap::new}, prefers no caching, and the empty {@link DependencyProvider}. + * + * @param nodePath the path string pointing to the ConfigNode + * @param the type of element object + * @return a map of provided element objects + */ + default @NotNull Map provideMap(final @NotNull String nodePath) { + return provideMap(nodePath, DependencyProvider.EMPTY, false, LinkedHashMap::new, DEFAULT_EXCEPTION_HANDLER); } /** @@ -115,6 +539,13 @@ public interface ElementContext { */ @NotNull ConfigNode rootNode(); + /** + * The {@link PathSplitter} used by this context. + * + * @return the PathSplitter used by this context + */ + @NotNull PathSplitter pathSplitter(); + /** * A source of {@link ElementContext} objects. */ @@ -143,6 +574,7 @@ interface Source { /** * Returns the {@link Registry} object holding information about which element types should be always cached. + * * @return a Registry of {@link Boolean}s */ @NotNull Registry cacheRegistry(); diff --git a/core/src/main/java/com/github/steanky/element/core/data/BasicDataInspector.java b/core/src/main/java/com/github/steanky/element/core/data/BasicDataInspector.java index 11e3571..7b878a2 100644 --- a/core/src/main/java/com/github/steanky/element/core/data/BasicDataInspector.java +++ b/core/src/main/java/com/github/steanky/element/core/data/BasicDataInspector.java @@ -20,8 +20,8 @@ * Basic implementation of {@link DataInspector}. */ public class BasicDataInspector implements DataInspector { - private static final Type COLLECTION_TYPE = TypeUtils.parameterize(Collection.class, TypeUtils.wildcardType() - .withUpperBounds(String.class).build()); + private static final Type COLLECTION_TYPE = TypeUtils.parameterize(Collection.class, + TypeUtils.wildcardType().withUpperBounds(String.class).build()); private final KeyParser keyParser; @@ -51,8 +51,7 @@ public BasicDataInspector(final @NotNull KeyParser idParser) { final boolean isIterable; if (TypeUtils.isAssignable(returnType, COLLECTION_TYPE)) { isIterable = true; - } - else { + } else { validateType(dataClass, String.class, returnType, () -> "DataPath accessor return value must be " + "assignable to String or Collection"); isIterable = false; diff --git a/core/src/main/java/com/github/steanky/element/core/data/BasicDataLocator.java b/core/src/main/java/com/github/steanky/element/core/data/BasicDataLocator.java index e5cbdbd..d813fb2 100644 --- a/core/src/main/java/com/github/steanky/element/core/data/BasicDataLocator.java +++ b/core/src/main/java/com/github/steanky/element/core/data/BasicDataLocator.java @@ -33,8 +33,7 @@ public BasicDataLocator(final @NotNull PathSplitter pathSplitter) { final Object[] path = pathSplitter.splitPathKey(dataPath); try { return rootNode.getNodeOrThrow(path); - } - catch (ConfigProcessException e) { + } catch (ConfigProcessException e) { throw new ElementException("invalid or missing data path '" + dataPath + "'", e); } } diff --git a/core/src/main/java/com/github/steanky/element/core/data/DataInspector.java b/core/src/main/java/com/github/steanky/element/core/data/DataInspector.java index 9d4c120..91e0bb6 100644 --- a/core/src/main/java/com/github/steanky/element/core/data/DataInspector.java +++ b/core/src/main/java/com/github/steanky/element/core/data/DataInspector.java @@ -22,27 +22,11 @@ public interface DataInspector { */ @NotNull DataInformation inspectData(final @NotNull Class dataClass); - /** - * Information about a path. - * @param pathFunction the {@link PathFunction} for this data object - * @param infoMap an unmodifiable map of id {@link Key}s to {@link PathFunction.PathInfo} objects. - */ - record DataInformation(@NotNull PathFunction pathFunction, - @Unmodifiable @NotNull Map infoMap) {} - /** * A function that can extract the actual path of a data object. */ @FunctionalInterface interface PathFunction { - /** - * Represents some info about a specific path. - * @param accessorMethod the method used to access the path - * @param annotation the {@link DataPath} annotation - * @param isCollection whether this path represents a collection of data paths - */ - record PathInfo(@NotNull Method accessorMethod, @NotNull DataPath annotation, boolean isCollection) {} - /** * Extracts a path key from a data object. * @@ -51,5 +35,23 @@ record PathInfo(@NotNull Method accessorMethod, @NotNull DataPath annotation, bo * @return the path key */ @NotNull Collection apply(final @NotNull Object dataObject, final @NotNull Key id); + + /** + * Represents some info about a specific path. + * + * @param accessorMethod the method used to access the path + * @param annotation the {@link DataPath} annotation + * @param isCollection whether this path represents a collection of data paths + */ + record PathInfo(@NotNull Method accessorMethod, @NotNull DataPath annotation, boolean isCollection) {} } + + /** + * Information about a path. + * + * @param pathFunction the {@link PathFunction} for this data object + * @param infoMap an unmodifiable map of id {@link Key}s to {@link PathFunction.PathInfo} objects. + */ + record DataInformation(@NotNull PathFunction pathFunction, + @Unmodifiable @NotNull Map infoMap) {} } diff --git a/core/src/main/java/com/github/steanky/element/core/dependency/DependencyProvider.java b/core/src/main/java/com/github/steanky/element/core/dependency/DependencyProvider.java index 039c455..3e48652 100644 --- a/core/src/main/java/com/github/steanky/element/core/dependency/DependencyProvider.java +++ b/core/src/main/java/com/github/steanky/element/core/dependency/DependencyProvider.java @@ -18,7 +18,8 @@ public interface DependencyProvider { DependencyProvider EMPTY = new DependencyProvider() { @Override public @NotNull TDependency provide(@NotNull Key type, @Nullable Key name) { - throw new ElementException("unable to resolve dependency of type '" + type + "'" + " and name '" + name + "'"); + throw new ElementException( + "unable to resolve dependency of type '" + type + "'" + " and name '" + name + "'"); } }; diff --git a/core/src/main/java/com/github/steanky/element/core/dependency/ModuleDependencyProvider.java b/core/src/main/java/com/github/steanky/element/core/dependency/ModuleDependencyProvider.java index 32f2c30..baea89d 100644 --- a/core/src/main/java/com/github/steanky/element/core/dependency/ModuleDependencyProvider.java +++ b/core/src/main/java/com/github/steanky/element/core/dependency/ModuleDependencyProvider.java @@ -80,7 +80,7 @@ public ModuleDependencyProvider(final @NotNull DependencyModule module, final @N if (supplierParameters.length == 0) { memoize = declaredMethod.isAnnotationPresent(Memoize.class); - if(dependencyFunctionMap.put(dependencyName, new DependencyFunction(false) { + if (dependencyFunctionMap.put(dependencyName, new DependencyFunction(false) { private Object value = null; @Override @@ -96,7 +96,8 @@ public Object apply(final Key key) { return value = ReflectionUtils.invokeMethod(declaredMethod, module); } }) != null) { - throw elementException(moduleClass, "registered multiple DependencySuppliers under key '" + dependencyName + "'"); + throw elementException(moduleClass, + "registered multiple DependencySuppliers under key '" + dependencyName + "'"); } continue; diff --git a/core/src/main/java/com/github/steanky/element/core/factory/BasicCollectionCreator.java b/core/src/main/java/com/github/steanky/element/core/factory/BasicCollectionCreator.java index ac10313..2733367 100644 --- a/core/src/main/java/com/github/steanky/element/core/factory/BasicCollectionCreator.java +++ b/core/src/main/java/com/github/steanky/element/core/factory/BasicCollectionCreator.java @@ -8,17 +8,16 @@ import java.util.*; import java.util.function.Function; -import static com.github.steanky.element.core.util.Validate.*; +import static com.github.steanky.element.core.util.Validate.elementException; /** * Basic implementation of {@link CollectionCreator}. */ public class BasicCollectionCreator implements CollectionCreator { private static final Function, ? extends Class> DEFAULT_RESOLVER = type -> { - if(type.equals(List.class) || type.equals(Collection.class)) { + if (type.equals(List.class) || type.equals(Collection.class)) { return ArrayList.class; - } - else if (type.equals(Set.class)) { + } else if (type.equals(Set.class)) { return HashSet.class; } @@ -29,6 +28,7 @@ else if (type.equals(Set.class)) { /** * Creates a new instance of this class given the provided resolver {@link Function}. + * * @param resolverFunction the resolver function used to resolve concrete classes from abstract types. */ public BasicCollectionCreator(final @NotNull Function, ? extends Class> resolverFunction) { @@ -64,7 +64,8 @@ public BasicCollectionCreator() { try { return (Collection) desiredClass.getConstructor(int.class).newInstance(initialSize); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException e) { throw elementException(type, "failed to instantiate collection", e); } } diff --git a/core/src/main/java/com/github/steanky/element/core/factory/BasicFactoryResolver.java b/core/src/main/java/com/github/steanky/element/core/factory/BasicFactoryResolver.java index 2f763da..b423a38 100644 --- a/core/src/main/java/com/github/steanky/element/core/factory/BasicFactoryResolver.java +++ b/core/src/main/java/com/github/steanky/element/core/factory/BasicFactoryResolver.java @@ -41,7 +41,7 @@ public class BasicFactoryResolver implements FactoryResolver { * @param elementTypeIdentifier the {@link ElementTypeIdentifier} used to extract type keys from element classes * @param dataInspector the {@link DataInspector} object used to extract {@link PathFunction}s from data * classes - * @param collectionCreator the {@link CollectionCreator} used to reflectively create collection instances when + * @param collectionCreator the {@link CollectionCreator} used to reflectively create collection instances when * necessary, when requiring multiple element dependencies */ public BasicFactoryResolver(final @NotNull KeyParser keyParser, @@ -92,18 +92,17 @@ private Object processParameter(final ElementParameter parameter, final ElementC final PathFunction.PathInfo info = dataInformation.infoMap().get(parameter.id); final Collection path = dataInformation.pathFunction().apply(data, parameter.id); if (info.isCollection()) { - final Collection collection = typeResolver.createCollection(parameter.parameter.getType(), path.size()); + final Collection collection = typeResolver.createCollection(parameter.parameter.getType(), + path.size()); for (final String elementPath : path) { - collection.add(info.annotation().cache() ? context.provideAndCache(elementPath, provider) : context - .provide(elementPath, provider)); + collection.add(context.provide(elementPath, provider, info.annotation().cache())); } return collection; } final String onlyPath = path.iterator().next(); - return info.annotation().cache() ? context.provideAndCache(onlyPath, provider) : context.provide(onlyPath, - provider); + return context.provide(onlyPath, provider, info.annotation().cache()); } @Override @@ -123,8 +122,8 @@ private Object processParameter(final ElementParameter parameter, final ElementC () -> "FactoryMethod does not return an ElementFactory"); validateParameterCount(declaredMethod, 0, () -> "FactoryMethod has parameters"); - final Type requiredType = TypeUtils.parameterize(ElementFactory.class, TypeUtils.WILDCARD_ALL, TypeUtils - .wildcardType().withUpperBounds(elementClass).build()); + final Type requiredType = TypeUtils.parameterize(ElementFactory.class, TypeUtils.WILDCARD_ALL, + TypeUtils.wildcardType().withUpperBounds(elementClass).build()); validateType(elementClass, requiredType, declaredMethod.getGenericReturnType(), () -> "FactoryMethod returned type not assignable to ElementFactory where T " + "is the element type"); @@ -238,7 +237,8 @@ private Object processParameter(final ElementParameter parameter, final ElementC elementParameters.trimToSize(); - final DataInspector.DataInformation dataInformation = dataClass == null ? null : dataInspector.inspectData(dataClass); + final DataInspector.DataInformation dataInformation = + dataClass == null ? null : dataInspector.inspectData(dataClass); if (dataInformation != null) { final Map infoMap = dataInformation.infoMap(); @@ -247,13 +247,14 @@ private Object processParameter(final ElementParameter parameter, final ElementC if (!parameter.isDependency) { final PathFunction.PathInfo info = infoMap.get(parameter.id); if (info == null) { - throw elementException(elementClass, "missing element dependency with parameter name '" + - parameter.id + "'"); + throw elementException(elementClass, + "missing element dependency with parameter name '" + parameter.id + "'"); } if (info.isCollection()) { - validateType(elementClass, Collection.class, parameter.parameter.getType(), () -> "parameter" + - " with name '" + parameter.id + "' must be assignable to Collection"); + validateType(elementClass, Collection.class, parameter.parameter.getType(), + () -> "parameter" + " with name '" + parameter.id + + "' must be assignable to Collection"); } nonDependencyCount++; @@ -262,8 +263,9 @@ private Object processParameter(final ElementParameter parameter, final ElementC final int size = infoMap.size(); if (nonDependencyCount != size) { - throw elementException(elementClass, "unexpected number of element dependency parameters, needed " + - size + ", was " + nonDependencyCount); + throw elementException(elementClass, + "unexpected number of element dependency parameters, needed " + size + ", was " + + nonDependencyCount); } } diff --git a/core/src/main/java/com/github/steanky/element/core/factory/CollectionCreator.java b/core/src/main/java/com/github/steanky/element/core/factory/CollectionCreator.java index e4ef21c..67c6751 100644 --- a/core/src/main/java/com/github/steanky/element/core/factory/CollectionCreator.java +++ b/core/src/main/java/com/github/steanky/element/core/factory/CollectionCreator.java @@ -12,12 +12,13 @@ public interface CollectionCreator { /** * Instantiates a collection, given a type and size. If the type is concrete (non-abstract, not an interface), this * method should generally attempt to instantiate it directly using the provided initial size. If the type is an - * interface or abstract class, this method should attempt to resolve the interface type to a concrete - * type, dependent on the implementation. - * @param type the collection type to resolve + * interface or abstract class, this method should attempt to resolve the interface type to a concrete type, + * dependent on the implementation. + * + * @param type the collection type to resolve * @param initialSize the initial size of the collection + * @param the type of object held in the collection * @return the new collection, initially empty - * @param the type of object held in the collection */ @NotNull Collection createCollection(final @NotNull Class type, final int initialSize); } diff --git a/core/src/main/java/com/github/steanky/element/core/key/BasicKeyParser.java b/core/src/main/java/com/github/steanky/element/core/key/BasicKeyParser.java index 05cd644..edcadc6 100644 --- a/core/src/main/java/com/github/steanky/element/core/key/BasicKeyParser.java +++ b/core/src/main/java/com/github/steanky/element/core/key/BasicKeyParser.java @@ -32,7 +32,7 @@ public class BasicKeyParser implements KeyParser { */ public BasicKeyParser(final @NotNull @NamespaceString String defaultNamespace) { if (!namespaceValid(defaultNamespace)) { - throw new IllegalArgumentException("invalid default namespace '" + defaultNamespace + "'"); + throw new IllegalArgumentException("invalid default namespace '" + defaultNamespace + "'"); } this.defaultNamespace = defaultNamespace; diff --git a/core/src/main/java/com/github/steanky/element/core/key/BasicPathSplitter.java b/core/src/main/java/com/github/steanky/element/core/key/BasicPathSplitter.java index ae98707..a7b08c5 100644 --- a/core/src/main/java/com/github/steanky/element/core/key/BasicPathSplitter.java +++ b/core/src/main/java/com/github/steanky/element/core/key/BasicPathSplitter.java @@ -1,4 +1,5 @@ package com.github.steanky.element.core.key; + import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -6,8 +7,8 @@ import java.util.regex.Pattern; /** - * Basic implementation of {@link PathSplitter}. A forward slash '/' is interpreted as a delimiter character. Empty - * path nodes are ignored. + * Basic implementation of {@link PathSplitter}. A forward slash '/' is interpreted as a delimiter character. Empty path + * nodes are ignored. */ public class BasicPathSplitter implements PathSplitter { private static final String SPLIT_STRING = "/"; @@ -31,7 +32,8 @@ public class BasicPathSplitter implements PathSplitter { try { objects.add(Integer.parseInt(entry)); continue; - } catch (NumberFormatException ignored) {} + } catch (NumberFormatException ignored) { + } } objects.add(entry); @@ -55,4 +57,9 @@ public class BasicPathSplitter implements PathSplitter { return builder.toString(); } + + @Override + public @NotNull String append(final @NotNull String pathString, final @NotNull Object element) { + return normalize(pathString) + SPLIT_STRING + element; + } } diff --git a/core/src/main/java/com/github/steanky/element/core/key/PathSplitter.java b/core/src/main/java/com/github/steanky/element/core/key/PathSplitter.java index 470a23b..580ca3c 100644 --- a/core/src/main/java/com/github/steanky/element/core/key/PathSplitter.java +++ b/core/src/main/java/com/github/steanky/element/core/key/PathSplitter.java @@ -1,7 +1,7 @@ package com.github.steanky.element.core.key; -import org.jetbrains.annotations.NotNull; import com.github.steanky.ethylene.core.ConfigElement; +import org.jetbrains.annotations.NotNull; /** * Splits "path strings" into object arrays that can be passed to {@link ConfigElement#getElement(Object...)}. @@ -23,4 +23,13 @@ public interface PathSplitter { * @return the normalized string */ @NotNull String normalize(final @NotNull String pathKey); + + /** + * Appends a new element onto the given path string. + * + * @param pathString the current path string + * @param element the element to append to the path string + * @return the new path string + */ + @NotNull String append(final @NotNull String pathString, final @NotNull Object element); } diff --git a/core/src/main/java/com/github/steanky/element/core/util/ReflectionUtils.java b/core/src/main/java/com/github/steanky/element/core/util/ReflectionUtils.java index 784f442..f299e8c 100644 --- a/core/src/main/java/com/github/steanky/element/core/util/ReflectionUtils.java +++ b/core/src/main/java/com/github/steanky/element/core/util/ReflectionUtils.java @@ -4,8 +4,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.lang.reflect.*; -import java.util.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Objects; /** * Contains reflection-related utility methods. diff --git a/core/src/test/java/com/github/steanky/element/core/element/BasicContextManagerIntegrationTest.java b/core/src/test/java/com/github/steanky/element/core/element/BasicContextManagerIntegrationTest.java index 3c6d1a8..6c262b5 100644 --- a/core/src/test/java/com/github/steanky/element/core/element/BasicContextManagerIntegrationTest.java +++ b/core/src/test/java/com/github/steanky/element/core/element/BasicContextManagerIntegrationTest.java @@ -6,7 +6,10 @@ import com.github.steanky.element.core.context.BasicElementContext; import com.github.steanky.element.core.context.ContextManager; import com.github.steanky.element.core.context.ElementContext; -import com.github.steanky.element.core.data.*; +import com.github.steanky.element.core.data.BasicDataInspector; +import com.github.steanky.element.core.data.BasicDataLocator; +import com.github.steanky.element.core.data.DataInspector; +import com.github.steanky.element.core.data.DataLocator; import com.github.steanky.element.core.dependency.DependencyProvider; import com.github.steanky.element.core.factory.BasicCollectionCreator; import com.github.steanky.element.core.factory.BasicFactoryResolver; @@ -24,7 +27,6 @@ import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; -import java.util.Collection; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -52,8 +54,8 @@ public BasicContextManagerIntegrationTest() { final PathSplitter pathSplitter = new BasicPathSplitter(); final DataLocator dataLocator = new BasicDataLocator(pathSplitter); - final ElementContext.Source source = new BasicElementContext.Source(configRegistry, factoryRegistry, cacheRegistry, - pathSplitter, dataLocator, typeExtractor); + final ElementContext.Source source = new BasicElementContext.Source(configRegistry, factoryRegistry, + cacheRegistry, pathSplitter, dataLocator, typeExtractor); this.contextManager = new BasicContextManager(elementInspector, elementTypeIdentifier, source); contextManager.registerElementClass(SimpleElement.class); @@ -67,7 +69,7 @@ void simpleData() { final ConfigNode node = ConfigNode.of("type", "simple_data", "value", 10); final ElementContext data = contextManager.makeContext(node); - final SimpleData element = data.provideAndCache(null, DependencyProvider.EMPTY); + final SimpleData element = data.provide(null, DependencyProvider.EMPTY, false); assertNotNull(element); assertEquals(10, element.data.value); @@ -80,7 +82,7 @@ void nested() { node.put("simple_data", nested); final ElementContext data = contextManager.makeContext(node); - final Nested nestedElement = data.provideAndCache(null, DependencyProvider.EMPTY); + final Nested nestedElement = data.provide(null, DependencyProvider.EMPTY, false); assertNotNull(nestedElement); assertEquals(10, nestedElement.simpleElement.data.value); @@ -101,7 +103,7 @@ void multi() { node.put("simple_data_2", simpleData2); final ElementContext data = contextManager.makeContext(node); - final MultiElement multiElement = data.provideAndCache(); + final MultiElement multiElement = data.provide(); assertNotNull(multiElement); assertEquals(1, multiElement.elements.get(0).data.value); @@ -113,30 +115,30 @@ public static class MultiElement { private final Data data; private final List elements; + @FactoryMethod + public MultiElement(@NotNull Data data, @DataName("data") @NotNull List elements) { + this.data = data; + this.elements = elements; + } + @ProcessorMethod public static ConfigProcessor processor() { return new ConfigProcessor<>() { @Override public Data dataFromElement(@NotNull ConfigElement element) throws ConfigProcessException { - List simpleElements = ConfigProcessor.STRING.listProcessor().dataFromElement(element - .getElementOrThrow("simpleElements")); + List simpleElements = ConfigProcessor.STRING.listProcessor() + .dataFromElement(element.getElementOrThrow("simpleElements")); return new Data(simpleElements); } @Override public @NotNull ConfigElement elementFromData(Data data) throws ConfigProcessException { - return ConfigNode.of("simpleElements", ConfigProcessor.STRING.listProcessor() - .elementFromData(data.simpleElements)); + return ConfigNode.of("simpleElements", + ConfigProcessor.STRING.listProcessor().elementFromData(data.simpleElements)); } }; } - @FactoryMethod - public MultiElement(@NotNull Data data, @DataName("data") @NotNull List elements) { - this.data = data; - this.elements = elements; - } - @DataObject public record Data(@DataPath("data") List simpleElements) { diff --git a/core/src/test/java/com/github/steanky/element/core/element/BasicElementInspectorIntegrationTest.java b/core/src/test/java/com/github/steanky/element/core/element/BasicElementInspectorIntegrationTest.java index d2e0bdc..c0c5a67 100644 --- a/core/src/test/java/com/github/steanky/element/core/element/BasicElementInspectorIntegrationTest.java +++ b/core/src/test/java/com/github/steanky/element/core/element/BasicElementInspectorIntegrationTest.java @@ -31,7 +31,8 @@ public BasicElementInspectorIntegrationTest() { final ElementTypeIdentifier elementTypeIdentifier = new BasicElementTypeIdentifier(parser); final DataInspector dataInspector = new BasicDataInspector(parser); final CollectionCreator collectionCreator = new BasicCollectionCreator(); - final FactoryResolver factoryResolver = new BasicFactoryResolver(parser, elementTypeIdentifier, dataInspector, collectionCreator); + final FactoryResolver factoryResolver = new BasicFactoryResolver(parser, elementTypeIdentifier, dataInspector, + collectionCreator); final ProcessorResolver processorResolver = new BasicProcessorResolver(); this.inspector = new BasicElementInspector(factoryResolver, processorResolver); } diff --git a/core/src/test/java/com/github/steanky/element/core/key/BasicPathSplitterTest.java b/core/src/test/java/com/github/steanky/element/core/key/BasicPathSplitterTest.java index 0be307b..f605214 100644 --- a/core/src/test/java/com/github/steanky/element/core/key/BasicPathSplitterTest.java +++ b/core/src/test/java/com/github/steanky/element/core/key/BasicPathSplitterTest.java @@ -2,7 +2,8 @@ import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; class BasicPathSplitterTest { @Test diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1266136..4f69246 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,9 +6,9 @@ junit-jupiter = "5.9.0-M1" [libraries] adventure-key = { module = "net.kyori:adventure-key", version = "4.11.0" } -commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.12.0"} +commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.12.0" } -ethylene-core = { module = "com.github.steanky:ethylene-core", version = "0.12.0" } +ethylene-core = { module = "com.github.steanky:ethylene-core", version = "0.12.4" } jetbrains-annotations = { module = "org.jetbrains:annotations", version = "23.0.0" }