From af747671fbcb0b52f648f517867d52bd23bec5e8 Mon Sep 17 00:00:00 2001 From: steank Date: Wed, 20 Mar 2024 20:34:36 -0700 Subject: [PATCH] Bump Ethylene dependency to latest, bump version to 0.18.0 * Delete ElementPath.java, ElementPathUtils.java, associated test as the functionality is now duplicated by Ethylene's ConfigPath --- .../element.java-conventions.gradle.kts | 2 +- .../element/core/ElementException.java | 28 +- .../steanky/element/core/ElementFactory.java | 6 +- .../element/core/annotation/Child.java | 5 +- .../core/context/BasicElementContext.java | 24 +- .../element/core/context/ElementContext.java | 156 +++--- .../core/factory/BasicFactoryResolver.java | 75 +-- .../element/core/key/BasicKeyExtractor.java | 8 +- .../element/core/path/BasicElementPath.java | 490 ------------------ .../element/core/path/ElementPath.java | 284 ---------- .../element/core/path/ElementPathUtils.java | 24 - .../steanky/element/core/util/Validate.java | 18 +- .../core/path/BasicElementPathTest.java | 408 --------------- gradle/libs.versions.toml | 2 +- 14 files changed, 174 insertions(+), 1356 deletions(-) delete mode 100644 core/src/main/java/com/github/steanky/element/core/path/BasicElementPath.java delete mode 100644 core/src/main/java/com/github/steanky/element/core/path/ElementPath.java delete mode 100644 core/src/main/java/com/github/steanky/element/core/path/ElementPathUtils.java delete mode 100644 core/src/test/java/com/github/steanky/element/core/path/BasicElementPathTest.java diff --git a/buildSrc/src/main/kotlin/element.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/element.java-conventions.gradle.kts index 51ad6d6..5896c7f 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.17.0" +version = "0.18.0" java { toolchain.languageVersion.set(JavaLanguageVersion.of(17)) diff --git a/core/src/main/java/com/github/steanky/element/core/ElementException.java b/core/src/main/java/com/github/steanky/element/core/ElementException.java index 87084e7..6a80569 100644 --- a/core/src/main/java/com/github/steanky/element/core/ElementException.java +++ b/core/src/main/java/com/github/steanky/element/core/ElementException.java @@ -1,9 +1,9 @@ package com.github.steanky.element.core; -import com.github.steanky.element.core.path.ElementPath; +import com.github.steanky.ethylene.core.path.ConfigPath; /** - * Represents a generic exception thrown by various parts of the Element API. Can have an associated {@link ElementPath} + * Represents a generic exception thrown by various parts of the Element API. Can have an associated {@link ConfigPath} * and {@link Class} which, respectively, define a path and/or class related to this error. */ public class ElementException extends RuntimeException { @@ -15,7 +15,7 @@ public class ElementException extends RuntimeException { /** * The element path associated with this error. */ - private ElementPath elementPath; + private ConfigPath configPath; /** * Creates a new ElementException with no detail message or cause. @@ -64,13 +64,13 @@ public void setElementClass(Class elementClass) { } /** - * Sets the {@link ElementPath} associated with this error. This method does nothing if an error path is already set. + * Sets the {@link ConfigPath} associated with this error. This method does nothing if an error path is already set. * - * @param elementPath the path + * @param configPath the path */ - public void setElementPath(ElementPath elementPath) { - if (this.elementPath == null) { - this.elementPath = elementPath; + public void setConfigPath(ConfigPath configPath) { + if (this.configPath == null) { + this.configPath = configPath; } } @@ -88,24 +88,24 @@ public Class elementClass() { * * @return the error path */ - public ElementPath errorPath() { - return elementPath; + public ConfigPath errorPath() { + return configPath; } @Override public String getMessage() { final String baseMessage = super.getMessage(); - final StringBuilder builder = new StringBuilder(baseMessage.length() + 100); + final StringBuilder builder = new StringBuilder(baseMessage.length()); final Class elementClass = this.elementClass; - final ElementPath elementPath = this.elementPath; + final ConfigPath elementPath = this.configPath; - builder.append("\"").append(baseMessage).append("\""); + builder.append('\"').append(baseMessage).append('\"'); if (elementClass != null) { builder.append(System.lineSeparator()).append("Relevant class: ").append(elementClass); } if (elementPath != null) { - builder.append(System.lineSeparator()).append("Data path: '").append(elementPath).append("'"); + builder.append(System.lineSeparator()).append("Data path: '").append(elementPath).append('\''); } 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 7b14d8d..5b94ec2 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 @@ -2,7 +2,7 @@ import com.github.steanky.element.core.context.ElementContext; import com.github.steanky.element.core.dependency.DependencyProvider; -import com.github.steanky.element.core.path.ElementPath; +import com.github.steanky.ethylene.core.path.ConfigPath; import org.jetbrains.annotations.NotNull; /** @@ -18,12 +18,12 @@ public interface ElementFactory { * * @param objectData the specific data object used to create this type; may be null if this element does not * accept any data - * @param dataPath the path of the data used to create this type; will always be non-null even if the + * @param configPath the path of the data used to create this type; will always be non-null even if the * element accepts no data, because in such cases there is still a type key present * @param context the element context, potentially used for resolving children * @param dependencyProvider the provider of dependency objects that are not elements * @return the element object */ - @NotNull TElement make(final TData objectData, final @NotNull ElementPath dataPath, + @NotNull TElement make(final TData objectData, final @NotNull ConfigPath configPath, final @NotNull ElementContext context, final @NotNull DependencyProvider dependencyProvider); } diff --git a/core/src/main/java/com/github/steanky/element/core/annotation/Child.java b/core/src/main/java/com/github/steanky/element/core/annotation/Child.java index 30b8f19..9a41872 100644 --- a/core/src/main/java/com/github/steanky/element/core/annotation/Child.java +++ b/core/src/main/java/com/github/steanky/element/core/annotation/Child.java @@ -2,9 +2,10 @@ import com.github.steanky.element.core.context.ElementContext; import com.github.steanky.element.core.dependency.DependencyProvider; -import com.github.steanky.element.core.path.ElementPath; import org.jetbrains.annotations.NotNull; +import com.github.steanky.ethylene.core.path.ConfigPath; + import java.lang.annotation.*; /** @@ -17,7 +18,7 @@ public @interface Child { /** * The path of the child dependency, relative to the configuration object on which it is defined. Syntax is - * interpreted as if by calling {@link ElementPath#of(String)}. + * interpreted as if by calling {@link ConfigPath#of(String)}. * * @return the value of this annotation */ 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 0a5611a..cfa7f00 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 @@ -5,9 +5,9 @@ import com.github.steanky.element.core.Registry; import com.github.steanky.element.core.dependency.DependencyProvider; import com.github.steanky.element.core.key.KeyExtractor; -import com.github.steanky.element.core.path.ElementPath; import com.github.steanky.ethylene.core.collection.ConfigContainer; import com.github.steanky.ethylene.core.collection.ConfigNode; +import com.github.steanky.ethylene.core.path.ConfigPath; import com.github.steanky.ethylene.core.processor.ConfigProcessException; import com.github.steanky.ethylene.core.processor.ConfigProcessor; import net.kyori.adventure.key.Key; @@ -30,9 +30,9 @@ public class BasicElementContext implements ElementContext { private final Registry cacheRegistry; private final KeyExtractor typeKeyExtractor; private final ConfigContainer rootCopy; - private final Map dataObjects; - private final Map elementObjects; - private final Map typeMap; + private final Map dataObjects; + private final Map elementObjects; + private final Map typeMap; /** * Creates a new instance of this class. @@ -62,15 +62,21 @@ public BasicElementContext(final @NotNull Registry> processor @SuppressWarnings("unchecked") @Override - public @NotNull TElement provide(final @NotNull ElementPath path, final @Nullable ConfigNode substitute, + public @NotNull TElement provide(final @NotNull ConfigPath path, final @Nullable ConfigNode substitute, final @NotNull DependencyProvider dependencyProvider, final boolean cache) { try { - final ElementPath absolutePath = path.toAbsolute(); + final ConfigPath absolutePath = path.toAbsolute(); Key objectType = typeMap.get(absolutePath); final ConfigNode dataNode; if (objectType == null) { - ConfigNode child = substitute != null ? substitute : absolutePath.followNode(rootCopy); + ConfigNode child; + try { + child = substitute != null ? substitute : rootCopy.atOrThrow(absolutePath).asNodeOrThrow(); + } + catch (ConfigProcessException e) { + throw elementException(e, absolutePath, "Configuration error"); + } objectType = typeKeyExtractor.extractKey(child); typeMap.put(absolutePath, objectType); @@ -95,7 +101,7 @@ public BasicElementContext(final @NotNull Registry> processor if (dataInfo == null) { try { final ConfigNode configuration = dataNode != null ? dataNode : - (substitute != null ? substitute : absolutePath.followNode(rootCopy)); + (substitute != null ? substitute : rootCopy.atOrThrow(absolutePath).asNodeOrThrow()); final Object data = processorRegistry.contains(objectTypeFinal) ? processorRegistry.lookup(objectTypeFinal).dataFromElement(configuration) : null; @@ -127,7 +133,7 @@ public BasicElementContext(final @NotNull Registry> processor .make(dataInfo.data, absolutePath, this, dependencyProvider); } catch (ElementException exception) { - exception.setElementPath(path); + exception.setConfigPath(path); exception.fillInStackTrace(); throw exception; } 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 823252e..8cf2c4e 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 @@ -5,11 +5,12 @@ 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.path.ElementPath; import com.github.steanky.ethylene.core.collection.ConfigContainer; 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.path.ConfigPath; +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; @@ -37,7 +38,7 @@ public interface ElementContext { * 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 {@link ElementPath} used to locate the target data + * @param path the {@link ConfigPath} used to locate the target data * @param substitute if non-null, effectively "replaces" the element at {@code path} regardless of what is * actually in the data * @param dependencyProvider the {@link DependencyProvider} used to provide dependencies @@ -45,29 +46,29 @@ public interface ElementContext { * @param the type of the element object * @return the contextual element object */ - @NotNull TElement provide(final @NotNull ElementPath path, final @Nullable ConfigNode substitute, + @NotNull TElement provide(final @NotNull ConfigPath path, final @Nullable ConfigNode substitute, final @NotNull DependencyProvider dependencyProvider, final boolean cache); /** - * Works identically to {@link ElementContext#provide(ElementPath, ConfigNode, DependencyProvider, boolean)}, but + * Works identically to {@link ElementContext#provide(ConfigPath, ConfigNode, DependencyProvider, boolean)}, but * uses a {@code null} substitute * - * @param path the {@link ElementPath} used to locate the target data + * @param path the {@link ConfigPath} used to locate the target data * @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 */ - default @NotNull TElement provide(final @NotNull ElementPath path, + default @NotNull TElement provide(final @NotNull ConfigPath path, final @NotNull DependencyProvider dependencyProvider, final boolean cache) { return provide(path, null, dependencyProvider, cache); } /** - * Convenience overload of {@link ElementContext#provide(ElementPath, DependencyProvider, boolean)} that handles + * Convenience overload of {@link ElementContext#provide(ConfigPath, DependencyProvider, boolean)} that handles * {@link ElementException}s thrown during construction. * - * @param path the {@link ElementPath} used to locate the target data + * @param path the {@link ConfigPath} used to locate the target data * @param dependencyProvider the {@link DependencyProvider} used to supply dependencies * @param cache true if this element should be cached, false otherwise * @param exceptionHandler a {@link Consumer} which will be called with an {@link ElementException}, if one @@ -76,7 +77,7 @@ public interface ElementContext { * @param the element type * @return the element object */ - default TElement provide(final @NotNull ElementPath path, + default TElement provide(final @NotNull ConfigPath path, final @NotNull DependencyProvider dependencyProvider, final boolean cache, final @NotNull Consumer exceptionHandler, final @NotNull Supplier defaultValueSupplier) { @@ -93,7 +94,7 @@ default TElement provide(final @NotNull ElementPath path, /** * Convenience overload of - * {@link ElementContext#provide(ElementPath, DependencyProvider, boolean, Consumer, Supplier)} that provides the + * {@link ElementContext#provide(ConfigPath, DependencyProvider, boolean, Consumer, Supplier)} that provides the * root element, and no caching. * * @param dependencyProvider the {@link DependencyProvider} used to supply dependencies @@ -106,15 +107,15 @@ default TElement provide(final @NotNull ElementPath path, default TElement provide(final @NotNull DependencyProvider dependencyProvider, final @NotNull Consumer exceptionHandler, final @NotNull Supplier defaultValueSupplier) { - return provide(ElementPath.EMPTY, dependencyProvider, false, exceptionHandler, defaultValueSupplier); + return provide(ConfigPath.EMPTY, dependencyProvider, false, exceptionHandler, defaultValueSupplier); } /** * Convenience overload of - * {@link ElementContext#provide(ElementPath, DependencyProvider, boolean, Consumer, Supplier)} that does not + * {@link ElementContext#provide(ConfigPath, DependencyProvider, boolean, Consumer, Supplier)} that does not * specify any caching. * - * @param path the {@link ElementPath} used to locate the target data + * @param path the {@link ConfigPath} used to locate the target data * @param dependencyProvider the {@link DependencyProvider} used to supply dependencies * @param exceptionHandler a {@link Consumer} which will be called with an {@link ElementException}, if one * occurs @@ -122,7 +123,7 @@ default TElement provide(final @NotNull DependencyProvider dependency * @param the element type * @return the element object */ - default TElement provide(final @NotNull ElementPath path, + default TElement provide(final @NotNull ConfigPath path, final @NotNull DependencyProvider dependencyProvider, final @NotNull Consumer exceptionHandler, final @NotNull Supplier defaultValueSupplier) { @@ -131,17 +132,17 @@ default TElement provide(final @NotNull ElementPath path, /** * Convenience overload of - * {@link ElementContext#provide(ElementPath, DependencyProvider, boolean, Consumer, Supplier)} that specifies an + * {@link ElementContext#provide(ConfigPath, DependencyProvider, boolean, Consumer, Supplier)} that specifies an * empty {@link DependencyProvider} and no caching. * - * @param path the {@link ElementPath} used to locate the target data + * @param path the {@link ConfigPath} used to locate the target data * @param exceptionHandler a {@link Consumer} which will be called with an {@link ElementException}, if one * occurs * @param defaultValueSupplier the {@link Supplier} used to supply a fallback value in case an exception occurs * @param the element type * @return the element object */ - default TElement provide(final @NotNull ElementPath path, + default TElement provide(final @NotNull ConfigPath path, final @NotNull Consumer exceptionHandler, final @NotNull Supplier defaultValueSupplier) { return provide(path, DependencyProvider.EMPTY, false, exceptionHandler, defaultValueSupplier); @@ -149,7 +150,7 @@ default TElement provide(final @NotNull ElementPath path, /** * Convenience overload of - * {@link ElementContext#provide(ElementPath, DependencyProvider, boolean, Consumer, Supplier)} that provides the + * {@link ElementContext#provide(ConfigPath, DependencyProvider, boolean, Consumer, Supplier)} that provides the * root element and uses the empty {@link DependencyProvider}. * * @param exceptionHandler a {@link Consumer} which will be called with an {@link ElementException}, if one @@ -160,11 +161,11 @@ default TElement provide(final @NotNull ElementPath path, */ default TElement provide(final @NotNull Consumer exceptionHandler, final @NotNull Supplier defaultValueSupplier) { - return provide(ElementPath.EMPTY, DependencyProvider.EMPTY, false, exceptionHandler, defaultValueSupplier); + return provide(ConfigPath.EMPTY, DependencyProvider.EMPTY, false, exceptionHandler, defaultValueSupplier); } /** - * Convenience overload for {@link ElementContext#provide(ElementPath, DependencyProvider, boolean)}. This will + * Convenience overload for {@link ElementContext#provide(ConfigPath, DependencyProvider, boolean)}. This will * provide the root element using the given {@link DependencyProvider}, and no caching. * * @param dependencyProvider the DependencyProvider used to provide dependencies @@ -172,11 +173,11 @@ default TElement provide(final @NotNull Consumer @NotNull TElement provide(final @NotNull DependencyProvider dependencyProvider) { - return provide(ElementPath.EMPTY, dependencyProvider, false); + return provide(ConfigPath.EMPTY, dependencyProvider, false); } /** - * Convenience overload for {@link ElementContext#provide(ElementPath, DependencyProvider, boolean)}. This will + * Convenience overload for {@link ElementContext#provide(ConfigPath, DependencyProvider, boolean)}. This will * provide the element specified by the path, using an empty dependency provider ({@link DependencyProvider#EMPTY}), * and no caching. * @@ -184,23 +185,23 @@ default TElement provide(final @NotNull Consumer the type of the contextual element object * @return the element object */ - default @NotNull TElement provide(final @NotNull ElementPath path) { + default @NotNull TElement provide(final @NotNull ConfigPath path) { return provide(path, DependencyProvider.EMPTY, false); } /** - * Convenience overload for {@link ElementContext#provide(ElementPath, DependencyProvider, boolean)}. This will + * Convenience overload for {@link ElementContext#provide(ConfigPath, 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(ElementPath.EMPTY, DependencyProvider.EMPTY, false); + return provide(ConfigPath.EMPTY, DependencyProvider.EMPTY, false); } /** - * Provides a collection of elements, given a valid {@link ElementPath} pointing at a {@link ConfigList}, relative + * Provides a collection of elements, given a valid {@link ConfigPath} 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. @@ -215,7 +216,7 @@ default TElement provide(final @NotNull Consumer> TCollection provideCollection( - final @NotNull ElementPath listPath, final @NotNull DependencyProvider dependencyProvider, + final @NotNull ConfigPath listPath, final @NotNull DependencyProvider dependencyProvider, final boolean cache, final @NotNull IntFunction collectionSupplier, final @NotNull Consumer exceptionHandler) { Objects.requireNonNull(listPath); @@ -225,11 +226,12 @@ default TElement provide(final @NotNull Consumer TElement provide(final @NotNull Consumer TElement provide(final @NotNull Consumer TElement provide(final @NotNull Consumer the type of element object * @return a collection of provided element objects */ - default @NotNull List provideCollection(final @NotNull ElementPath listPath, + default @NotNull List provideCollection(final @NotNull ConfigPath listPath, final @NotNull DependencyProvider dependencyProvider, final boolean cache, final @NotNull Consumer exceptionHandler) { return provideCollection(listPath, dependencyProvider, cache, ArrayList::new, exceptionHandler); @@ -275,7 +277,7 @@ default TElement provide(final @NotNull Consumer TElement provide(final @NotNull Consumer the type of element object * @return a collection of provided element objects */ - default @NotNull List provideCollection(final @NotNull ElementPath listPath, + default @NotNull List provideCollection(final @NotNull ConfigPath listPath, final boolean cache, final @NotNull Consumer exceptionHandler) { return provideCollection(listPath, DependencyProvider.EMPTY, cache, ArrayList::new, exceptionHandler); } /** * Convenience overload for - * {@link ElementContext#provideCollection(ElementPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses a + * {@link ElementContext#provideCollection(ConfigPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses a * default collection supplier {@code ArrayList::new} and prefers no caching. * * @param listPath the path string pointing to the ConfigList @@ -300,7 +302,7 @@ default TElement provide(final @NotNull Consumer the type of element object * @return a collection of provided element objects */ - default @NotNull List provideCollection(final @NotNull ElementPath listPath, + default @NotNull List provideCollection(final @NotNull ConfigPath listPath, final @NotNull DependencyProvider dependencyProvider, final @NotNull Consumer exceptionHandler) { return provideCollection(listPath, dependencyProvider, false, ArrayList::new, exceptionHandler); @@ -308,7 +310,7 @@ default TElement provide(final @NotNull Consumer TElement provide(final @NotNull Consumer the type of element object * @return a collection of provided element objects */ - default @NotNull List provideCollection(final @NotNull ElementPath listPath, + default @NotNull List provideCollection(final @NotNull ConfigPath listPath, final @NotNull Consumer exceptionHandler) { return provideCollection(listPath, DependencyProvider.EMPTY, false, ArrayList::new, exceptionHandler); } /** * Convenience overload for - * {@link ElementContext#provideCollection(ElementPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses + * {@link ElementContext#provideCollection(ConfigPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses * the default exception handler {@link ElementContext#DEFAULT_EXCEPTION_HANDLER}. * * @param listPath the path string pointing to the ConfigList @@ -336,14 +338,14 @@ default TElement provide(final @NotNull Consumer> TCollection provideCollection( - final @NotNull ElementPath listPath, final @NotNull DependencyProvider dependencyProvider, + final @NotNull ConfigPath 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(ElementPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses + * {@link ElementContext#provideCollection(ConfigPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses * the default exception handler {@link ElementContext#DEFAULT_EXCEPTION_HANDLER} and the empty * {@link DependencyProvider}. * @@ -355,7 +357,7 @@ default TElement provide(final @NotNull Consumer> TCollection provideCollection( - final @NotNull ElementPath listPath, final boolean cache, + final @NotNull ConfigPath listPath, final boolean cache, final @NotNull IntFunction collectionSupplier) { return provideCollection(listPath, DependencyProvider.EMPTY, cache, collectionSupplier, DEFAULT_EXCEPTION_HANDLER); @@ -363,7 +365,7 @@ default TElement provide(final @NotNull Consumer TElement provide(final @NotNull Consumer the type of element object * @return a collection of provided element objects */ - default @NotNull List provideCollection(final @NotNull ElementPath listPath, + default @NotNull List provideCollection(final @NotNull ConfigPath listPath, final @NotNull DependencyProvider dependencyProvider, final boolean cache) { return provideCollection(listPath, dependencyProvider, cache, ArrayList::new, DEFAULT_EXCEPTION_HANDLER); } /** * Convenience overload for - * {@link ElementContext#provideCollection(ElementPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses + * {@link ElementContext#provideCollection(ConfigPath, 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}. * @@ -389,14 +391,14 @@ default TElement provide(final @NotNull Consumer the type of element object * @return a collection of provided element objects */ - default @NotNull List provideCollection(final @NotNull ElementPath listPath, + default @NotNull List provideCollection(final @NotNull ConfigPath listPath, final boolean cache) { return provideCollection(listPath, DependencyProvider.EMPTY, cache, ArrayList::new, DEFAULT_EXCEPTION_HANDLER); } /** * Convenience overload for - * {@link ElementContext#provideCollection(ElementPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses + * {@link ElementContext#provideCollection(ConfigPath, 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}. * @@ -405,14 +407,14 @@ default TElement provide(final @NotNull Consumer the type of element object * @return a collection of provided element objects */ - default @NotNull List provideCollection(final @NotNull ElementPath listPath, + default @NotNull List provideCollection(final @NotNull ConfigPath listPath, final @NotNull DependencyProvider dependencyProvider) { return provideCollection(listPath, dependencyProvider, false, ArrayList::new, DEFAULT_EXCEPTION_HANDLER); } /** * Convenience overload for - * {@link ElementContext#provideCollection(ElementPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses + * {@link ElementContext#provideCollection(ConfigPath, 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. * @@ -420,7 +422,7 @@ default TElement provide(final @NotNull Consumer the type of element object * @return a collection of provided element objects */ - default @NotNull List provideCollection(final @NotNull ElementPath listPath) { + default @NotNull List provideCollection(final @NotNull ConfigPath listPath) { return provideCollection(listPath, DependencyProvider.EMPTY, false, ArrayList::new, DEFAULT_EXCEPTION_HANDLER); } @@ -439,7 +441,7 @@ default TElement provide(final @NotNull Consumer> the type of map * @return a collection of provided element objects */ - default @NotNull > TMap provideMap(final @NotNull ElementPath nodePath, + default @NotNull > TMap provideMap(final @NotNull ConfigPath nodePath, final @NotNull DependencyProvider dependencyProvider, final boolean cache, final @NotNull IntFunction mapSupplier, final @NotNull Consumer exceptionHandler) { @@ -450,10 +452,12 @@ default TElement provide(final @NotNull Consumer TElement provide(final @NotNull Consumer TElement provide(final @NotNull Consumer the type of element object * @return a map of provided element objects */ - default @NotNull Map provideMap(final @NotNull ElementPath nodePath, + default @NotNull Map provideMap(final @NotNull ConfigPath nodePath, final @NotNull DependencyProvider dependencyProvider, final boolean cache, final @NotNull Consumer exceptionHandler) { return provideMap(nodePath, dependencyProvider, cache, LinkedHashMap::new, exceptionHandler); @@ -499,7 +503,7 @@ default TElement provide(final @NotNull Consumer TElement provide(final @NotNull Consumer the type of element object * @return a map of provided element objects */ - default @NotNull Map provideMap(final @NotNull ElementPath nodePath, + default @NotNull Map provideMap(final @NotNull ConfigPath nodePath, final boolean cache, final @NotNull Consumer exceptionHandler) { return provideMap(nodePath, DependencyProvider.EMPTY, cache, LinkedHashMap::new, exceptionHandler); } /** * Convenience overload for - * {@link ElementContext#provideMap(ElementPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses a + * {@link ElementContext#provideMap(ConfigPath, 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 @@ -524,7 +528,7 @@ default TElement provide(final @NotNull Consumer the type of element object * @return a map of provided element objects */ - default @NotNull Map provideMap(final @NotNull ElementPath nodePath, + default @NotNull Map provideMap(final @NotNull ConfigPath nodePath, final @NotNull DependencyProvider dependencyProvider, final @NotNull Consumer exceptionHandler) { return provideMap(nodePath, dependencyProvider, false, LinkedHashMap::new, exceptionHandler); @@ -532,7 +536,7 @@ default TElement provide(final @NotNull Consumer TElement provide(final @NotNull Consumer the type of element object * @return a map of provided element objects */ - default @NotNull Map provideMap(final @NotNull ElementPath nodePath, + default @NotNull Map provideMap(final @NotNull ConfigPath nodePath, final @NotNull Consumer exceptionHandler) { return provideMap(nodePath, DependencyProvider.EMPTY, false, LinkedHashMap::new, exceptionHandler); } /** * Convenience overload for - * {@link ElementContext#provideMap(ElementPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the + * {@link ElementContext#provideMap(ConfigPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the * default exception handler {@link ElementContext#DEFAULT_EXCEPTION_HANDLER}. * * @param nodePath the path string pointing to the ConfigNode @@ -559,7 +563,7 @@ default TElement provide(final @NotNull Consumer the type of map * @return a map of provided element objects */ - default @NotNull > TMap provideMap(final @NotNull ElementPath nodePath, + default @NotNull > TMap provideMap(final @NotNull ConfigPath nodePath, final @NotNull DependencyProvider dependencyProvider, final boolean cache, final @NotNull IntFunction mapSupplier) { return provideMap(nodePath, dependencyProvider, cache, mapSupplier, DEFAULT_EXCEPTION_HANDLER); @@ -567,7 +571,7 @@ default TElement provide(final @NotNull Consumer TElement provide(final @NotNull Consumer the type of map * @return a map of provided element objects */ - default @NotNull > TMap provideMap(final @NotNull ElementPath nodePath, + default @NotNull > TMap provideMap(final @NotNull ConfigPath nodePath, final boolean cache, final @NotNull IntFunction mapSupplier) { return provideMap(nodePath, DependencyProvider.EMPTY, cache, mapSupplier, DEFAULT_EXCEPTION_HANDLER); } /** * Convenience overload for - * {@link ElementContext#provideMap(ElementPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the + * {@link ElementContext#provideMap(ConfigPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the * default exception handler {@link ElementContext#DEFAULT_EXCEPTION_HANDLER} and the default map supplier * {@code LinkedHashMap::new}. * @@ -595,14 +599,14 @@ default TElement provide(final @NotNull Consumer the type of element object * @return a map of provided element objects */ - default @NotNull Map provideMap(final @NotNull ElementPath nodePath, + default @NotNull Map provideMap(final @NotNull ConfigPath nodePath, final @NotNull DependencyProvider dependencyProvider, final boolean cache) { return provideMap(nodePath, dependencyProvider, cache, LinkedHashMap::new, DEFAULT_EXCEPTION_HANDLER); } /** * Convenience overload for - * {@link ElementContext#provideMap(ElementPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the + * {@link ElementContext#provideMap(ConfigPath, 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}. * @@ -611,14 +615,14 @@ default TElement provide(final @NotNull Consumer the type of element object * @return a map of provided element objects */ - default @NotNull Map provideMap(final @NotNull ElementPath nodePath, + default @NotNull Map provideMap(final @NotNull ConfigPath nodePath, final boolean cache) { return provideMap(nodePath, DependencyProvider.EMPTY, cache, LinkedHashMap::new, DEFAULT_EXCEPTION_HANDLER); } /** * Convenience overload for - * {@link ElementContext#provideMap(ElementPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the + * {@link ElementContext#provideMap(ConfigPath, 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. * @@ -627,14 +631,14 @@ default TElement provide(final @NotNull Consumer the type of element object * @return a map of provided element objects */ - default @NotNull Map provideMap(final @NotNull ElementPath nodePath, + default @NotNull Map provideMap(final @NotNull ConfigPath nodePath, final @NotNull DependencyProvider dependencyProvider) { return provideMap(nodePath, dependencyProvider, false, LinkedHashMap::new, DEFAULT_EXCEPTION_HANDLER); } /** * Convenience overload for - * {@link ElementContext#provideMap(ElementPath, DependencyProvider, boolean, IntFunction, Consumer)}. Uses the + * {@link ElementContext#provideMap(ConfigPath, 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}. * @@ -642,7 +646,7 @@ default TElement provide(final @NotNull Consumer the type of element object * @return a map of provided element objects */ - default @NotNull Map provideMap(final @NotNull ElementPath nodePath) { + default @NotNull Map provideMap(final @NotNull ConfigPath nodePath) { return provideMap(nodePath, DependencyProvider.EMPTY, false, LinkedHashMap::new, DEFAULT_EXCEPTION_HANDLER); } 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 a1528c6..8754caf 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 @@ -10,12 +10,13 @@ import com.github.steanky.element.core.dependency.DependencyProvider; import com.github.steanky.element.core.key.Constants; import com.github.steanky.element.core.key.KeyParser; -import com.github.steanky.element.core.path.ElementPath; 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.collection.LinkedConfigNode; +import com.github.steanky.ethylene.core.path.ConfigPath; +import com.github.steanky.ethylene.core.processor.ConfigProcessException; import com.github.steanky.ethylene.core.processor.ConfigProcessor; import com.github.steanky.ethylene.mapper.MappingProcessorSource; import com.github.steanky.ethylene.mapper.annotation.Default; @@ -61,20 +62,20 @@ private GenericFactory(final Constructor factoryConstructor, final ElementPar @NotNull @Override - public Object make(final Object objectData, final @NotNull ElementPath dataPath, final @NotNull ElementContext context, + public Object make(final Object objectData, final @NotNull ConfigPath configPath, final @NotNull ElementContext context, final @NotNull DependencyProvider dependencyProvider) { if (requiresData && objectData == null) { - throw elementException(factoryConstructor.getDeclaringClass(), dataPath, + throw elementException(factoryConstructor.getDeclaringClass(), configPath, "Element requires data, but none was provided"); } if (!requiresData && objectData != null) { - throw elementException(factoryConstructor.getDeclaringClass(), dataPath, + throw elementException(factoryConstructor.getDeclaringClass(), configPath, "Element does not accept data, and data was provided"); } if (!context.root().isNode()) { - throw elementException(factoryConstructor.getDeclaringClass(), dataPath, + throw elementException(factoryConstructor.getDeclaringClass(), configPath, "Root must be a ConfigNode"); } @@ -93,22 +94,23 @@ public Object make(final Object objectData, final @NotNull ElementPath dataPath, yield dependencyProvider.provide(parameter.typeKey); } catch (ElementException exception) { - exception.setElementPath(dataPath); + exception.setConfigPath(configPath); exception.setElementClass(factoryConstructor.getDeclaringClass()); throw exception; } } case CHILD -> { if (ourData == null) { - ourData = ConfigNode.defaulting(dataPath.followNode(context.root()), defaultValues); + ourData = ConfigNode.defaulting(context.root().atOrThrow(configPath).asNodeOrThrow(), + defaultValues); } - final ElementPath absoluteChildDataPath = dataPath.resolve(parameter.childPath); + final ConfigPath absoluteChildDataPath = configPath.resolve(parameter.childPath); final boolean isContainer = containerCreator .isContainerType(Token.ofType(parameter.parameter.getParameterizedType())); //recursively resolve child elements - yield child(parameter, dataPath, dataPath, ourData, context, absoluteChildDataPath, + yield child(parameter, configPath, configPath, ourData, context, absoluteChildDataPath, dependencyProvider, isContainer); } }; @@ -116,32 +118,43 @@ yield child(parameter, dataPath, dataPath, ourData, context, absoluteChildDataPa } catch (ElementException exception) { exception.setElementClass(factoryConstructor.getDeclaringClass()); - exception.setElementPath(dataPath); + exception.setConfigPath(configPath); throw exception; } + catch (ConfigProcessException exception) { + throw elementException(exception, factoryConstructor.getDeclaringClass(), configPath, + "Failure to follow path"); + } try { return factoryConstructor.newInstance(args); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw elementException(e, factoryConstructor.getDeclaringClass(), dataPath, "Error instantiating element"); + throw elementException(e, factoryConstructor.getDeclaringClass(), configPath, "Error instantiating element"); } } @SuppressWarnings("unchecked") - private Object child(ElementParameter parameter, ElementPath dataPath, ElementPath defaultingPath, - ConfigNode defaultingData, ElementContext context, ElementPath absoluteChildDataPath, + private Object child(ElementParameter parameter, ConfigPath dataPath, ConfigPath defaultingPath, + ConfigNode defaultingData, ElementContext context, ConfigPath absoluteChildDataPath, DependencyProvider dependencyProvider, boolean isContainer) { final ConfigElement childData; - if (absoluteChildDataPath.startsWith(defaultingPath)) { - childData = dataPath.relativize(absoluteChildDataPath).toAbsolute().follow(defaultingData); - } - else { - childData = absoluteChildDataPath.follow(context.root()); - } - if (childData.isNode()) { - //simple case: child is a node - return context.provide(absoluteChildDataPath, childData.asNode(), dependencyProvider, false); + try { + if (absoluteChildDataPath.startsWith(defaultingPath)) { + childData = defaultingData.atOrThrow(dataPath.relativize(absoluteChildDataPath).toAbsolute()); + } + else { + childData = context.root().atOrThrow(absoluteChildDataPath); + } + + if (childData.isNode()) { + //simple case: child is a node + return context.provide(absoluteChildDataPath, childData.asNode(), dependencyProvider, false); + } + } + catch (ConfigProcessException exception) { + throw elementException(exception, factoryConstructor.getDeclaringClass(), absoluteChildDataPath, + "Failure to follow path"); } if (childData.isList()) { @@ -154,14 +167,14 @@ private Object child(ElementParameter parameter, ElementPath dataPath, ElementPa } catch (ElementException exception) { exception.setElementClass(factoryConstructor.getDeclaringClass()); - exception.setElementPath(absoluteChildDataPath); + exception.setConfigPath(absoluteChildDataPath); throw exception; } for (int i = 0; i < childList.size(); i++) { - final ElementPath absoluteElementPath = absoluteChildDataPath.append(i); + final ConfigPath absoluteConfigPath = absoluteChildDataPath.append(Integer.toString(i)); listOutput.add(child(parameter, dataPath, defaultingPath, defaultingData, context, - absoluteElementPath, dependencyProvider, false)); + absoluteConfigPath, dependencyProvider, false)); } return listOutput; @@ -173,11 +186,11 @@ private Object child(ElementParameter parameter, ElementPath dataPath, ElementPa } return child(parameter, dataPath, defaultingPath, defaultingData, context, - absoluteChildDataPath.append(0), dependencyProvider, false); + absoluteChildDataPath.append("0"), dependencyProvider, false); } if (childData.isString()) { - final ElementPath childRedirect = absoluteChildDataPath.resolveSibling(childData.asString()); + final ConfigPath childRedirect = absoluteChildDataPath.resolveSibling(childData.asString()); if (!childRedirect.isAbsolute()) { throw elementException(factoryConstructor.getDeclaringClass(), absoluteChildDataPath, "Child redirect points outside of root"); @@ -360,7 +373,7 @@ private enum ParameterType { } private record ElementParameter(Parameter parameter, ParameterType type, DependencyProvider.TypeKey typeKey, - ElementPath childPath) {} + ConfigPath childPath) {} private record SearchResult(T first, V second) {} @@ -383,7 +396,7 @@ private ElementParameter[] extractParameters(final Executable executable) { final ParameterType type; final DependencyProvider.TypeKey typeKey; - final ElementPath childPath; + final ConfigPath childPath; if ((!isData && childAnnotation == null) || classDepend != null || parameterDepend != null) { type = ParameterType.DEPENDENCY; typeKey = DependencyProvider.key(Token.ofType(parameter.getParameterizedType()), @@ -400,11 +413,11 @@ else if (isData) { else { type = ParameterType.CHILD; typeKey = null; - childPath = ElementPath.of(childAnnotation.value()); + childPath = ConfigPath.of(childAnnotation.value()); } elementParameters[i] = new ElementParameter(parameter, type, typeKey, childPath == null ? null : - (childPath.isAbsolute() ? ElementPath.EMPTY.relativize(childPath) : childPath)); + (childPath.isAbsolute() ? ConfigPath.EMPTY.relativize(childPath) : childPath)); } return elementParameters; diff --git a/core/src/main/java/com/github/steanky/element/core/key/BasicKeyExtractor.java b/core/src/main/java/com/github/steanky/element/core/key/BasicKeyExtractor.java index c71beef..a5a404b 100644 --- a/core/src/main/java/com/github/steanky/element/core/key/BasicKeyExtractor.java +++ b/core/src/main/java/com/github/steanky/element/core/key/BasicKeyExtractor.java @@ -21,7 +21,7 @@ public class BasicKeyExtractor implements KeyExtractor { /** * Create a new instance of this class. * - * @param keyName the name of the key passed to {@link ConfigElement#getStringOrThrow(Object...)} + * @param keyName the name of the key * @param keyParser the {@link KeyParser} instance used to parse strings into {@link Key} instances */ public BasicKeyExtractor(final @NotNull String keyName, final @NotNull KeyParser keyParser) { @@ -32,7 +32,7 @@ public BasicKeyExtractor(final @NotNull String keyName, final @NotNull KeyParser @Override public @NotNull Key extractKey(final @NotNull ConfigNode node) { try { - @Subst(Constants.NAMESPACE_OR_KEY) final String keyString = node.getStringOrThrow(keyName); + @Subst(Constants.NAMESPACE_OR_KEY) final String keyString = node.atOrThrow(keyName).asStringOrThrow(); return keyParser.parseKey(keyString); } catch (ConfigProcessException e) { throw elementException("Missing type key " + keyName); @@ -42,9 +42,9 @@ public BasicKeyExtractor(final @NotNull String keyName, final @NotNull KeyParser @Override public boolean hasKey(final @NotNull ConfigNode node) { if (node.containsKey(keyName)) { - final ConfigElement element = node.getElement(keyName); + final ConfigElement element = node.at(keyName); - if (element.isString()) { + if (element != null && element.isString()) { return keyParser.isValidKey(element.asString()); } } diff --git a/core/src/main/java/com/github/steanky/element/core/path/BasicElementPath.java b/core/src/main/java/com/github/steanky/element/core/path/BasicElementPath.java deleted file mode 100644 index 946c128..0000000 --- a/core/src/main/java/com/github/steanky/element/core/path/BasicElementPath.java +++ /dev/null @@ -1,490 +0,0 @@ -package com.github.steanky.element.core.path; - -import com.github.steanky.ethylene.core.ConfigElement; -import com.github.steanky.ethylene.core.collection.ConfigList; -import com.github.steanky.toolkit.collection.Containers; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Unmodifiable; - -import java.util.*; - -import static com.github.steanky.element.core.util.Validate.elementException; - -/** - * Basic ElementPath implementation with UNIX-like semantics. - */ -class BasicElementPath implements ElementPath { - private static final String CURRENT_COMMAND = "."; - private static final String PREVIOUS_COMMAND = ".."; - - private static final Node CURRENT_NODE = new Node(CURRENT_COMMAND, NodeType.CURRENT); - private static final Node PREVIOUS_NODE = new Node(PREVIOUS_COMMAND, NodeType.PREVIOUS); - - private static final Node[] EMPTY_NODE_ARRAY = new Node[0]; - - static final BasicElementPath EMPTY_PATH = new BasicElementPath(EMPTY_NODE_ARRAY); - static final BasicElementPath RELATIVE_BASE = new BasicElementPath(new Node[]{CURRENT_NODE}); - static final BasicElementPath PREVIOUS_BASE = new BasicElementPath(new Node[]{PREVIOUS_NODE}); - - private static final char DELIMITER = '/'; - private static final char ESCAPE = '\\'; - - private static final char CURRENT = '.'; - - private final Node[] nodes; - private final List nodeView; - - private int hash; - private boolean hashed; - - private String stringValue; - - private BasicElementPath(final @NotNull Node @NotNull [] nodeArray) { - this.nodes = nodeArray; - this.nodeView = Containers.arrayView(nodeArray); - } - - private static boolean isCharacterEscapable(char current) { - return current == DELIMITER || current == CURRENT || current == ESCAPE; - } - - /** - * Parses the given UNIX-style path string into a {@link BasicElementPath}. The resulting object will represent the - * normalized path, with redundant elements removed. - * - * @param path the path string - * @return a normalized BasicElementPath - */ - static @NotNull BasicElementPath parse(final @NotNull String path) { - if (path.isEmpty()) { - return EMPTY_PATH; - } - - if (path.equals(".")) { - return RELATIVE_BASE; - } - - if(path.equals("..")) { - return PREVIOUS_BASE; - } - - final int pathLength = path.length(); - - final List nodes = new ArrayList<>(); - final StringBuilder builder = new StringBuilder(); - - boolean escape = false; - boolean nodeEscape = false; - for (int i = 0; i < pathLength; i++) { - final char current = path.charAt(i); - - if (escape) { - if (!isCharacterEscapable(current)) { - //re-add the escape character that we previously encountered - builder.append(ESCAPE); - } - - builder.append(current); - escape = false; - } else if (current == ESCAPE) { - escape = true; - - if (builder.isEmpty()) { - nodeEscape = true; - } - } else if (current == DELIMITER) { - tryAddNode(nodes, builder, nodeEscape); - nodeEscape = false; - } else { - builder.append(current); - } - } - - tryAddNode(nodes, builder, nodeEscape); - - if (nodes.isEmpty()) { - return BasicElementPath.EMPTY_PATH; - } - - //process the path to remove unnecessary or redundant PREVIOUS commands - //we don't have to worry about redundant CURRENTs because they wouldn't have been added in the first place - for (int i = nodes.size() - 1; i > 0; i--) { - final Node node = nodes.get(i); - if (node.nodeType() != NodeType.PREVIOUS) { - continue; - } - - final int previousIndex = i - 1; - final Node previous = nodes.get(previousIndex); - final NodeType previousType = previous.nodeType(); - if (previousType == NodeType.PREVIOUS) { - //don't remove the previous node if it's also a previous - continue; - } - - //the current PREVIOUS command erased the previous node - nodes.remove(previousIndex); - - if (previousType == NodeType.NAME) { - //strip out redundant PREVIOUS commands, otherwise leave them alone - nodes.remove(previousIndex); - } - - if (i > nodes.size()) { - i--; - } - } - - if (nodes.isEmpty()) { - return EMPTY_PATH; - } - - return new BasicElementPath(nodes.toArray(Node[]::new)); - } - - private static void tryAddNode(final List nodes, final StringBuilder builder, final boolean escape) { - if (builder.isEmpty()) { - return; - } - - final String string = builder.toString(); - final boolean empty = nodes.isEmpty(); - if (!empty && !escape && string.length() <= 1 && string.charAt(0) == CURRENT) { - builder.setLength(0); - return; - } - - final NodeType type = escape ? NodeType.NAME : switch (string) { - case PREVIOUS_COMMAND -> NodeType.PREVIOUS; - case CURRENT_COMMAND -> NodeType.CURRENT; - default -> NodeType.NAME; - }; - - nodes.add(switch (type) { - case CURRENT -> CURRENT_NODE; - case PREVIOUS -> PREVIOUS_NODE; - case NAME -> new Node(string, NodeType.NAME); - }); - - builder.setLength(0); - } - - @Override - public @NotNull @Unmodifiable List nodes() { - return nodeView; - } - - @Override - public boolean isAbsolute() { - return nodes.length == 0 || nodes[0].nodeType() == NodeType.NAME; - } - - @Override - public @NotNull ElementPath resolve(final @NotNull ElementPath relativePath) { - if (relativePath.isAbsolute()) { - //mimic behavior of Path#resolve(Path) - return relativePath; - } - - final List ourNodes = nodes(); - final List relativeNodes = relativePath.nodes(); - - if (relativeNodes.isEmpty()) { - return this; - } - - final Deque newNodes = new ArrayDeque<>(ourNodes.size() + relativeNodes.size()); - - for (final Node node : ourNodes) { - newNodes.addLast(node); - } - - for (final Node node : relativeNodes) { - switch (node.nodeType()) { - case NAME -> newNodes.addLast(node); - case CURRENT -> { - } //no-op - case PREVIOUS -> { //resolve previous command - if (!newNodes.isEmpty() && newNodes.peekLast().nodeType() != NodeType.PREVIOUS) { - newNodes.removeLast(); - } - else { - newNodes.addLast(node); - } - } - } - } - - if (newNodes.isEmpty()) { - return EMPTY_PATH; - } - - return new BasicElementPath(newNodes.toArray(Node[]::new)); - } - - @Override - public @NotNull ElementPath resolve(@NotNull String relativePath) { - return resolve(parse(relativePath)); - } - - @Override - public @NotNull ElementPath append(@Nullable Object node) { - final Node[] newNodes = new Node[nodes.length + 1]; - System.arraycopy(nodes, 0, newNodes, 0, nodes.length); - newNodes[newNodes.length - 1] = new Node(Objects.toString(node), NodeType.NAME); - return new BasicElementPath(newNodes); - } - - @Override - public @NotNull ElementPath toAbsolute() { - if (isAbsolute()) { - return this; - } - - int i; - for (i = 0; i < nodes.length; i++) { - if (nodes[i].nodeType() == NodeType.NAME) { - break; - } - } - - if (i == nodes.length) { - return EMPTY_PATH; - } - - final Node[] newNodes = new Node[nodes.length - i]; - System.arraycopy(this.nodes, i, newNodes, 0, newNodes.length); - return new BasicElementPath(newNodes); - } - - @Override - public ElementPath getParent() { - if (nodes.length == 0) { - return null; - } - - final Node[] newNodes = new Node[nodes.length - 1]; - System.arraycopy(nodes, 0, newNodes, 0, newNodes.length); - return new BasicElementPath(newNodes); - } - - @Override - public @NotNull ElementPath relativize(final @NotNull ElementPath other) { - if (this.equals(other)) { - return RELATIVE_BASE; - } - - if (this.isAbsolute() != other.isAbsolute()) { - throw new IllegalArgumentException("Can only relativize paths of the same type"); - } - - final Node[] otherNodes = nodeArray(other); - final int min = Math.min(otherNodes.length, this.nodes.length); - final int max = Math.max(otherNodes.length, this.nodes.length); - - int matched = matched(this, this.nodes, other, otherNodes, min); - - if (otherNodes.length < this.nodes.length || matched < min) { - final int unmatched = min - matched; - final int previousCount = max - matched; - final Node[] newNodes = new Node[previousCount + unmatched]; - - Arrays.fill(newNodes, 0, previousCount, PREVIOUS_NODE); - System.arraycopy(otherNodes, matched, newNodes, previousCount, unmatched); - return new BasicElementPath(newNodes); - } - - //matched == min && otherNodes.length >= this.nodes.length - final int unmatched = max - min; - final Node[] newNodes = new Node[unmatched + 1]; - newNodes[0] = CURRENT_NODE; - System.arraycopy(otherNodes, matched, newNodes, 1, unmatched); - return new BasicElementPath(newNodes); - - } - - private static int matched(final ElementPath self, final Node[] selfNodes, final ElementPath other, - final Node[] otherNodes, final int min) { - int matched = 0; - - //count number of shared elements - while (matched < min) { - if (!otherNodes[matched].equals(selfNodes[matched])) { - break; - } - - matched++; - } - - //remaining .. in this path means we have no sane way to resolve the relative path - for (int i = matched; i < selfNodes.length; i++) { - if (selfNodes[i].nodeType() == NodeType.PREVIOUS) { - throw new IllegalArgumentException("Cannot compute relative path from " + self + " to " + other); - } - } - - return matched; - } - - @Override - public @NotNull ElementPath relativize(final @NotNull String other) { - return relativize(parse(other)); - } - - @Override - public @NotNull ElementPath resolveSibling(final @NotNull ElementPath sibling) { - if (sibling.isAbsolute()) { - return sibling; - } - - final ElementPath parentPath = getParent(); - if (parentPath == null) { - return sibling; - } - - return parentPath.resolve(sibling); - } - - @Override - public @NotNull ElementPath resolveSibling(final @NotNull String sibling) { - return resolveSibling(parse(sibling)); - } - - @Override - public @NotNull ConfigElement follow(final @NotNull ConfigElement root) { - Objects.requireNonNull(root); - ConfigElement current = root; - - for (int i = 0; i < nodes.length; i++) { - final Node node = nodes[i]; - - final NodeType type = node.nodeType(); - if (type == NodeType.CURRENT || type == NodeType.PREVIOUS) { - continue; - } - - final String name = node.name(); - if (current.isNode()) { - current = current.asNode().get(name); - } else if (current.isList()) { - final ConfigList list = current.asList(); - final int size = list.size(); - - try { - - final int value = Integer.parseInt(name); - if (value < 0 || value > size) { - formatPathException("Index " + value + " out of bounds for ConfigList of length " + size, i); - } - - current = list.get(value); - } catch (NumberFormatException e) { - formatPathException("String " + name + " cannot be parsed", i); - } - } else { - formatPathException("Unexpected ConfigElement type " + current.type(), i); - } - - if (current == null) { - formatPathException("Does not exist", i); - } - } - - return current; - } - - @Override - public @NotNull ElementPath subpath(final int beginIndex, final int endIndex) { - if (beginIndex < 0 || beginIndex >= nodes.length || endIndex < 0 || endIndex > nodes.length) { - throw new IndexOutOfBoundsException(); - } - - if (endIndex < beginIndex) { - throw new IllegalArgumentException("endIndex < beginIndex"); - } - - final int length = endIndex - beginIndex; - if (nodes[beginIndex].nodeType() != NodeType.NAME) { - final Node[] newNodes = new Node[length]; - System.arraycopy(nodes, beginIndex, newNodes, 0, length); - return new BasicElementPath(newNodes); - } - - final Node[] newNodes = new Node[length + 1]; - newNodes[0] = CURRENT_NODE; - System.arraycopy(nodes, 0, newNodes, 1, length); - return new BasicElementPath(newNodes); - } - - @Override - public boolean startsWith(final @NotNull ElementPath other) { - if (this.nodes.length == 0) { - return other.nodes().isEmpty(); - } - - final List otherNodes = other.nodes(); - if (otherNodes.size() > this.nodes.length) { - return false; - } - - for (int i = 0; i < otherNodes.size(); i++) { - if (!this.nodes[i].equals(otherNodes.get(i))) { - return false; - } - } - - return true; - } - - private static Node[] nodeArray(ElementPath elementPath) { - if (elementPath instanceof BasicElementPath basicElementPath) { - return basicElementPath.nodes; - } - - return elementPath.nodes().toArray(Node[]::new); - } - - private void formatPathException(final String message, final int position) { - throw elementException(this, "Path element " + nodes[position].name() + ": " + message); - } - - @Override - public int hashCode() { - if (hashed) { - return hash; - } - - final int hash = this.hash = Arrays.hashCode(nodes); - hashed = true; - return hash; - } - - @Override - public boolean equals(final Object obj) { - if (obj == null) { - return false; - } - - if (obj == this) { - return true; - } - - if (obj instanceof ElementPath elementPath) { - if (obj instanceof BasicElementPath basicElementPath) { - //do array equality comparison if we can - return Arrays.equals(nodes, basicElementPath.nodes); - } - - return nodes().equals(elementPath.nodes()); - } - - return false; - } - - @Override - public String toString() { - return Objects.requireNonNullElseGet(stringValue, - () -> stringValue = String.join("/", Arrays.stream(nodes).map(Node::name).toArray(String[]::new))); - } -} diff --git a/core/src/main/java/com/github/steanky/element/core/path/ElementPath.java b/core/src/main/java/com/github/steanky/element/core/path/ElementPath.java deleted file mode 100644 index ea417a9..0000000 --- a/core/src/main/java/com/github/steanky/element/core/path/ElementPath.java +++ /dev/null @@ -1,284 +0,0 @@ -package com.github.steanky.element.core.path; - -import com.github.steanky.element.core.ElementException; -import com.github.steanky.ethylene.core.ConfigElement; -import com.github.steanky.ethylene.core.collection.ConfigContainer; -import com.github.steanky.ethylene.core.collection.ConfigList; -import com.github.steanky.ethylene.core.collection.ConfigNode; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Unmodifiable; - -import java.nio.file.Path; -import java.util.List; -import java.util.Objects; - -/** - * Represents a particular path through a configuration tree. Implementations of this interface should be - * equality-comparable with all other implementations of this interface. Therefore, equality (and hash code) should be - * determined using the list returned by {@link ElementPath#nodes()}. Furthermore, implementations must be immutable; - * their nodes list should never change throughout their lifetime. - */ -public interface ElementPath { - /** - * The empty path, pointing at the root node. - */ - ElementPath EMPTY = BasicElementPath.EMPTY_PATH; - - /** - * The current (relative) path {@code .}, pointing at this node. - */ - ElementPath CURRENT = BasicElementPath.RELATIVE_BASE; - - /** - * The previous (relative) path {@code ..}, pointing at the previous node. - */ - ElementPath PREVIOUS = BasicElementPath.PREVIOUS_BASE; - - /** - * Parses the given string, assuming UNIX-like formatting and semantics. The resulting path will be normalized such - * that redundant nodes are removed. - *

- * Like UNIX paths, path entries are separated by slashes. Unlike UNIX, leading slashes have no significance. All - * paths that start with {@code .} or {@code ..} are considered "relative". All paths that do not are considered - * absolute. - *

- * Backslashes are considered escape characters. They can be used to include slashes, {@code .} and {@code ..} in - * path entries. - * - * @param path the path string to parse - * @return a new ElementPath - */ - static @NotNull ElementPath of(final @NotNull String path) { - Objects.requireNonNull(path); - return BasicElementPath.parse(path); - } - - /** - * Gets the nodes represented by this path. - * - * @return the nodes represented by this path - */ - @NotNull @Unmodifiable List nodes(); - - /** - * Determines if this path represents an absolute or a relative path. - * - * @return true if this path is absolute; false otherwise - */ - boolean isAbsolute(); - - /** - * Computes a new path by considering the given relative path as relative to this one. If the given path is not - * actually relative, it will be returned as-is. Otherwise, the relative path's nodes will be appropriately added to - * this path's, and a new path will be returned. - *

- * This method is analogous to {@link Path#resolve(Path)}. - * - * @param relativePath the relative path - * @return a new path representing the combination of this path and another - */ - @NotNull ElementPath resolve(final @NotNull ElementPath relativePath); - - /** - * Convenience overload for {@link ElementPath#resolve(ElementPath)} that parses the given string before - * resolution. - * - * @param relativePath the relative path string - * @return a new path representing the combination of this path and another - */ - @NotNull ElementPath resolve(final @NotNull String relativePath); - - /** - * Appends some object to this path as a single, new node. If the object is a string, commands will not be - * interpreted; the value is used as-is for the name of the node only. {@link Objects#toString(Object)} is used to - * convert arbitrary objects into strings before appending. - * - * @param node the object to append - * @return a new path - */ - @NotNull ElementPath append(final @Nullable Object node); - - /** - * Converts this path into an absolute path. Path commands will be removed. - * - * @return a new path - */ - @NotNull ElementPath toAbsolute(); - - /** - * Gets the parent path. If this path is empty, {@code null} is returned. - * - * @return the parent path, or {@code null} if empty - */ - ElementPath getParent(); - - /** - * Relativizes this path with respect to another. This is the inverse of {@link ElementPath#resolve(ElementPath)}, - * and is analogous to {@link Path#relativize(Path)}. - * - * @param other the other path - * @return the relativized path - */ - @NotNull ElementPath relativize(final @NotNull ElementPath other); - - /** - * Same behavior as {@link ElementPath#relativize(ElementPath)}, but interprets the given string as an ElementPath. - * @param other the other path - * @return the relativized path - */ - @NotNull ElementPath relativize(final @NotNull String other); - - /** - * Analogous operation to {@link Path#resolveSibling(Path)}. - * - * @param sibling the sibling path - * @return a new path - */ - @NotNull ElementPath resolveSibling(final @NotNull ElementPath sibling); - - /** - * Same behavior as {@link ElementPath#resolveSibling(ElementPath)}, but interprets the given string as an - * ElementPath. - * - * @param sibling the sibling path - * @return a new path - */ - @NotNull ElementPath resolveSibling(final @NotNull String sibling); - - /** - * Follows this path through the given {@link ConfigElement} according to its nodes. Node values will be converted - * to integers, as necessary, if lists are encountered. If this path is relative, it is considered as an absolute - * path. - *

- * If the path does not exist, an {@link ElementException} is thrown. - * - * @param root the root ConfigElement - * @return the element at the path - */ - @NotNull ConfigElement follow(final @NotNull ConfigElement root); - - /** - * Returns a path representing a sub-path of this one. Analogous to {@link Path#subpath(int, int)}. - * - * @param beginIndex the starting index (inclusive) - * @param endIndex the ending index (exclusive) - * @return a new path - */ - @NotNull ElementPath subpath(final int beginIndex, final int endIndex); - - /** - * Checks if this path starts with the same entries as the given path. - * - * @param startsWith the other path - * @return true if this path starts with the same entries as {@code startsWith}, false otherwise - */ - boolean startsWith(final @NotNull ElementPath startsWith); - - /** - * Convenience overload for {@link ElementPath#follow(ConfigElement)} that additionally converts the located element - * to a {@link ConfigContainer}. If the object is not of the right type, an {@link ElementException} will be - * thrown. - * - * @param root the root ConfigElement - * @return the element at the path - */ - default @NotNull ConfigContainer followContainer(final @NotNull ConfigElement root) { - return ElementPathUtils.follow(ConfigElement::isContainer, ConfigElement::asContainer, root, this, "list"); - } - - /** - * Convenience overload for {@link ElementPath#follow(ConfigElement)} that additionally converts the located element - * to a {@link ConfigNode}. If the object is not of the right type, an {@link ElementException} will be thrown. - * - * @param root the root ConfigElement - * @return the element at the path - */ - default @NotNull ConfigNode followNode(final @NotNull ConfigElement root) { - return ElementPathUtils.follow(ConfigElement::isNode, ConfigElement::asNode, root, this, "node"); - } - - /** - * Convenience overload for {@link ElementPath#follow(ConfigElement)} that additionally converts the located element - * to a {@link ConfigList}. If the object is not of the right type, an {@link ElementException} will be thrown. - * - * @param root the root ConfigElement - * @return the element at the path - */ - default @NotNull ConfigList followList(final @NotNull ConfigElement root) { - return ElementPathUtils.follow(ConfigElement::isList, ConfigElement::asList, root, this, "list"); - } - - /** - * Convenience overload for {@link ElementPath#follow(ConfigElement)} that additionally converts the located element - * to a scalar (using {@link ConfigElement#asScalar()}). If the object is not of the right type, an - * {@link ElementException} will be thrown. - * - * @param root the root ConfigElement - * @return the element at the path - */ - default @NotNull Object followScalar(final @NotNull ConfigElement root) { - return ElementPathUtils.follow(ConfigElement::isScalar, ConfigElement::asScalar, root, this, "scalar"); - } - - /** - * Convenience overload for {@link ElementPath#follow(ConfigElement)} that additionally converts the located element - * to a string. If the object is not of the right type, an {@link ElementException} will be thrown. - * - * @param root the root ConfigElement - * @return the element at the path - */ - default @NotNull String followString(final @NotNull ConfigElement root) { - return ElementPathUtils.follow(ConfigElement::isString, ConfigElement::asString, root, this, "string"); - } - - /** - * Convenience overload for {@link ElementPath#follow(ConfigElement)} that additionally converts the located element - * to a number. If the object is not of the right type, an {@link ElementException} will be thrown. - * - * @param root the root ConfigElement - * @return the element at the path - */ - default @NotNull Number followNumber(final @NotNull ConfigElement root) { - return ElementPathUtils.follow(ConfigElement::isNumber, ConfigElement::asNumber, root, this, "number"); - } - - /** - * Convenience overload for {@link ElementPath#follow(ConfigElement)} that additionally converts the located element - * to a boolean. If the object is not of the right type, an {@link ElementException} will be thrown. - * - * @param root the root ConfigElement - * @return the element at the path - */ - default boolean followBoolean(final @NotNull ConfigElement root) { - return ElementPathUtils.follow(ConfigElement::isBoolean, ConfigElement::asBoolean, root, this, "boolean"); - } - - /** - * Indicates various types of nodes. - */ - enum NodeType { - /** - * A node representing the "current" command. - */ - CURRENT, - - /** - * A node representing the "previous" command. - */ - PREVIOUS, - - /** - * A node representing the name of a particular point along a path. - */ - NAME - } - - /** - * A record representing an individual node in a path. - * - * @param name the name of the node - * @param nodeType the kind of node this is - */ - record Node(@NotNull String name, @NotNull NodeType nodeType) {} -} diff --git a/core/src/main/java/com/github/steanky/element/core/path/ElementPathUtils.java b/core/src/main/java/com/github/steanky/element/core/path/ElementPathUtils.java deleted file mode 100644 index 2554904..0000000 --- a/core/src/main/java/com/github/steanky/element/core/path/ElementPathUtils.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.github.steanky.element.core.path; - -import com.github.steanky.ethylene.core.ConfigElement; -import org.jetbrains.annotations.NotNull; - -import java.util.function.Function; -import java.util.function.Predicate; - -import static com.github.steanky.element.core.util.Validate.elementException; - -final class ElementPathUtils { - private ElementPathUtils() {} - - static @NotNull R follow(final @NotNull Predicate typeValidator, - final @NotNull Function function, final @NotNull ConfigElement root, - final @NotNull ElementPath elementPath, final @NotNull String typeName) { - final ConfigElement element = elementPath.follow(root); - if (typeValidator.test(element)) { - return function.apply(element); - } - - throw elementException(elementPath, "Expected object to be a " + typeName); - } -} diff --git a/core/src/main/java/com/github/steanky/element/core/util/Validate.java b/core/src/main/java/com/github/steanky/element/core/util/Validate.java index 2ba97c8..6e02711 100644 --- a/core/src/main/java/com/github/steanky/element/core/util/Validate.java +++ b/core/src/main/java/com/github/steanky/element/core/util/Validate.java @@ -1,7 +1,7 @@ package com.github.steanky.element.core.util; import com.github.steanky.element.core.ElementException; -import com.github.steanky.element.core.path.ElementPath; +import com.github.steanky.ethylene.core.path.ConfigPath; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -84,9 +84,9 @@ public static void validateReturnType(final @NotNull Method method, final @NotNu * @param message the error message * @return a new ElementException */ - public static @NotNull ElementException elementException(final @NotNull ElementPath path, final @NotNull String message) { + public static @NotNull ElementException elementException(final @NotNull ConfigPath path, final @NotNull String message) { final ElementException exception = new ElementException(message); - exception.setElementPath(path); + exception.setConfigPath(path); return exception; } @@ -141,10 +141,10 @@ public static void validateReturnType(final @NotNull Method method, final @NotNu * @return a new ElementException */ public static @NotNull ElementException elementException(final @NotNull Throwable cause, - final @NotNull ElementPath path, + final @NotNull ConfigPath path, final @NotNull String message) { final ElementException exception = new ElementException(message, cause); - exception.setElementPath(path); + exception.setConfigPath(path); return exception; } @@ -159,11 +159,11 @@ public static void validateReturnType(final @NotNull Method method, final @NotNu */ public static @NotNull ElementException elementException(final @NotNull Throwable cause, final @NotNull Class elementClass, - final @NotNull ElementPath path, + final @NotNull ConfigPath path, final @NotNull String message) { final ElementException exception = new ElementException(message, cause); exception.setElementClass(elementClass); - exception.setElementPath(path); + exception.setConfigPath(path); return exception; } @@ -176,11 +176,11 @@ public static void validateReturnType(final @NotNull Method method, final @NotNu * @return a new ElementException */ public static @NotNull ElementException elementException(final @NotNull Class elementClass, - final @NotNull ElementPath path, + final @NotNull ConfigPath path, final @NotNull String message) { final ElementException exception = new ElementException(message); exception.setElementClass(elementClass); - exception.setElementPath(path); + exception.setConfigPath(path); return exception; } } diff --git a/core/src/test/java/com/github/steanky/element/core/path/BasicElementPathTest.java b/core/src/test/java/com/github/steanky/element/core/path/BasicElementPathTest.java deleted file mode 100644 index cc63b94..0000000 --- a/core/src/test/java/com/github/steanky/element/core/path/BasicElementPathTest.java +++ /dev/null @@ -1,408 +0,0 @@ -package com.github.steanky.element.core.path; - -import com.github.steanky.ethylene.core.ConfigPrimitive; -import com.github.steanky.ethylene.core.collection.ConfigList; -import com.github.steanky.ethylene.core.collection.ConfigNode; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -class BasicElementPathTest { - @Test - void backCommand() { - ElementPath base = ElementPath.of(""); - assertEquals(ElementPath.of(".."), base.resolve("..")); - } - - @Test - void sibling4() { - ElementPath base = ElementPath.of("/a/b/c/d"); - assertEquals(ElementPath.of("/a/b/g"), base.resolveSibling("../g")); - } - - @Test - void sibling3() { - ElementPath base = ElementPath.of("/a/b/c/d"); - assertEquals(ElementPath.of("/a/b/c/f/g/h"), base.resolveSibling("./f/g/h")); - } - - @Test - void sibling1() { - ElementPath base = ElementPath.of("/a/b/c/d"); - assertEquals(ElementPath.of("f"), base.resolveSibling("f")); - } - - @Test - void sibling() { - ElementPath base = ElementPath.of("/a/b/c/d"); - assertEquals(ElementPath.of("a/b/c/f"), base.resolveSibling("./f")); - } - - @Test - void parent() { - ElementPath base = ElementPath.of("/a/b/c/d"); - assertEquals(ElementPath.of("/a/b/c"), base.getParent()); - } - - @Test - void rootParentNull() { - assertNull(ElementPath.of("").getParent()); - } - - private static void assertNormalized(ElementPath elementPath) { - boolean foundName = false; - for (ElementPath.Node node : elementPath.nodes()) { - if (node.nodeType() == ElementPath.NodeType.NAME) { - foundName = true; - } - else if (foundName) { - fail(elementPath + " is not normalized"); - } - } - } - - @Test - void relativeRelativize4() { - ElementPath base = ElementPath.of("../../"); - ElementPath other = ElementPath.of("../../../../../a/b/c/d/e/f/g"); - - ElementPath relative = base.relativize(other); - - assertEquals(other, base.resolve(relative)); - assertNormalized(relative); - } - - @Test - void relativeRelativize3() { - ElementPath base = ElementPath.of("../../a/b/c"); - ElementPath other = ElementPath.of("../.."); - - ElementPath relative = base.relativize(other); - - assertEquals(other, base.resolve(relative)); - assertNormalized(relative); - } - - @Test - void relativeRelativize2() { - ElementPath base = ElementPath.of("../../"); - ElementPath other = ElementPath.of("../../a/b/c"); - - ElementPath relative = base.relativize(other); - - assertEquals(other, base.resolve(relative)); - assertNormalized(relative); - } - - @Test - void relativeRelativize1() { - ElementPath base = ElementPath.of(".."); - ElementPath other = ElementPath.of("../a"); - - ElementPath relative = base.relativize(other); - - assertEquals(other, base.resolve(relative)); - assertNormalized(relative); - } - - @Test - void relativeRelativize() { - ElementPath base = ElementPath.of("./a/b/c/d"); - ElementPath other = ElementPath.of("./a/b/c"); - - ElementPath relative = base.relativize(other); - - assertEquals(other, base.resolve(relative)); - assertNormalized(relative); - } - - @Test - void simpleRelativize8() { - ElementPath base = ElementPath.of("/a/b/c/d"); - ElementPath other = ElementPath.of("/f/g/h"); - ElementPath relative = base.relativize(other); - - assertEquals(other, base.resolve(relative)); - assertNormalized(relative); - } - - @Test - void simpleRelativize7() { - ElementPath base = ElementPath.of("/a/b/c/d"); - ElementPath other = ElementPath.of("/a/b/c/d"); - ElementPath relative = base.relativize(other); - - assertEquals(other, base.resolve(relative)); - assertNormalized(relative); - } - - @Test - void simpleRelativize6() { - ElementPath base = ElementPath.of("/a/b/c/d"); - ElementPath other = ElementPath.of("/a/b/c/d/e/f/g"); - ElementPath relative = base.relativize(other); - - assertEquals(other, base.resolve(relative)); - assertNormalized(relative); - } - - @Test - void simpleRelativize5() { - ElementPath base = ElementPath.of("/a/b/c/d"); - ElementPath other = ElementPath.of("/a/b/c/f"); - ElementPath relative = base.relativize(other); - - assertEquals(other, base.resolve(relative)); - assertNormalized(relative); - } - - @Test - void simpleRelativize4() { - ElementPath base = ElementPath.of("/a/b/c/d"); - ElementPath other = ElementPath.of("f"); - ElementPath relative = base.relativize(other); - - assertEquals(other, base.resolve(relative)); - assertNormalized(relative); - } - - @Test - void simpleRelativize3() { - ElementPath base = ElementPath.of("/a/b/c/d"); - ElementPath other = ElementPath.of("/a"); - ElementPath relative = base.relativize(other); - - assertEquals(other, base.resolve(relative)); - assertNormalized(relative); - } - - @Test - void simpleRelativize2() { - ElementPath base = ElementPath.of("/a/b/c/d"); - ElementPath other = ElementPath.of("/a/b/c"); - ElementPath relative = base.relativize(other); - - assertEquals(other, base.resolve(relative)); - assertNormalized(relative); - } - - @Test - void simpleRelativize() { - ElementPath base = ElementPath.of("/a/b/c"); - ElementPath other = ElementPath.of("/a/b/c/d"); - ElementPath relative = base.relativize(other); - - assertEquals(other, base.resolve(relative)); - assertNormalized(relative); - } - - @Test - void appendToCurrent() { - ElementPath current = ElementPath.of("."); - assertEquals(ElementPath.of("./0"), current.append(0)); - } - - @Test - void chainedPreviousMakingEmpty() { - ElementPath path = BasicElementPath.parse("/test/test1/test2/test3/test4/../../../../.."); - assertEquals(List.of(), path.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void chainedPrevious() { - ElementPath path = BasicElementPath.parse("/test/test1/test2/test3/test4/../../../.."); - assertEquals(List.of("test"), path.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void previousToAbsolute() { - ElementPath path = BasicElementPath.parse("./..").toAbsolute(); - assertEquals(List.of(), path.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void messyToAbsolute() { - ElementPath path = BasicElementPath.parse("./test///././././././././././.").toAbsolute(); - assertEquals(List.of("test"), path.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void simpleToAbsolute() { - ElementPath path = BasicElementPath.parse("./test").toAbsolute(); - assertEquals(List.of("test"), path.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void pain() { - BasicElementPath path = BasicElementPath.parse("////./././//////./test/../..//////test2/../././//.//////"); - assertEquals(List.of(".."), path.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void redundantSlashes() { - BasicElementPath path = BasicElementPath.parse("////./././//////./test/../..//////test2/./././//.//////"); - assertEquals(List.of("..", "test2"), path.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void redundantPrevious() { - BasicElementPath path = BasicElementPath.parse("./../.."); - assertEquals(List.of("..", ".."), path.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void resolvePrevious1() { - ElementPath base = ElementPath.of(".."); - ElementPath resolved = base.resolve("./a/b/c"); - - assertEquals(ElementPath.of("../a/b/c"), resolved); - } - - @Test - void resolvePrevious() { - ElementPath base = ElementPath.of(".."); - ElementPath resolved = base.resolve("."); - - assertEquals(ElementPath.of(".."), resolved); - } - - @Test - void currentToPrevious() { - BasicElementPath path = BasicElementPath.parse("./.."); - assertEquals(List.of(".."), path.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void mixedPath() { - BasicElementPath path = BasicElementPath.parse("test/../../test2/./././."); - assertEquals(List.of("..", "test2"), path.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void mixedPath2() { - BasicElementPath path = BasicElementPath.parse("./test/../../test2/./././."); - assertEquals(List.of("..", "test2"), path.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void escapedBackslash() { - BasicElementPath path = BasicElementPath.parse("\\\\test/test"); - assertEquals(List.of("\\test", "test"), path.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void escapedCommandNodes() { - BasicElementPath path = BasicElementPath.parse("\\../\\."); - List nodes = path.nodes(); - - assertEquals(nodes.get(0).nodeType(), ElementPath.NodeType.NAME); - assertEquals(nodes.get(1).nodeType(), ElementPath.NodeType.NAME); - } - - @Test - void followEscapedCommandNodes() { - ConfigNode node = ConfigNode.of("..", ConfigNode.of(".", "test")); - - BasicElementPath path = BasicElementPath.parse("\\../\\."); - - assertEquals("test", path.follow(node).asString()); - } - - @Test - void followsSimplePath() { - BasicElementPath path = BasicElementPath.parse("/absolute/path"); - ConfigNode node = ConfigNode.of("absolute", ConfigNode.of("path", 0)); - - assertEquals(0, path.follow(node).asNumber()); - } - - @Test - void followsRelativePath() { - BasicElementPath path = BasicElementPath.parse("./absolute/path"); - ConfigNode node = ConfigNode.of("absolute", ConfigNode.of("path", 0)); - - assertEquals(0, path.follow(node).asNumber()); - } - - @Test - void followsPathWithIndex() { - BasicElementPath path = BasicElementPath.parse("1/test"); - ConfigList list = ConfigList.of(ConfigPrimitive.NULL, ConfigNode.of("test", 10)); - - assertEquals(10, path.follow(list).asNumber()); - } - - @Test - void simpleRelativePath() { - BasicElementPath absolutePath = BasicElementPath.parse("/this/is/a/test"); - BasicElementPath relativePath = BasicElementPath.parse("./relative/path"); - - ElementPath result = absolutePath.resolve(relativePath); - - assertEquals(List.of("this", "is", "a", "test", "relative", "path"), - result.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void relativeRelativePath() { - BasicElementPath absolutePath = BasicElementPath.parse("./this/is/a/test"); - BasicElementPath relativePath = BasicElementPath.parse("./relative/path"); - - ElementPath result = absolutePath.resolve(relativePath); - - assertEquals(List.of(".", "this", "is", "a", "test", "relative", "path"), - result.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void backReference() { - BasicElementPath absolutePath = BasicElementPath.parse("/this/is/a/test"); - BasicElementPath relativePath = BasicElementPath.parse(".."); - - ElementPath result = absolutePath.resolve(relativePath); - - assertEquals(List.of("this", "is", "a"), result.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void doubleBackReference() { - BasicElementPath absolutePath = BasicElementPath.parse("/this/is/a/test"); - BasicElementPath relativePath = BasicElementPath.parse("../.."); - - ElementPath result = absolutePath.resolve(relativePath); - - assertEquals(List.of("this", "is"), result.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void tripleBackReference() { - BasicElementPath absolutePath = BasicElementPath.parse("/this/is/a/test"); - BasicElementPath relativePath = BasicElementPath.parse("../../.."); - - ElementPath result = absolutePath.resolve(relativePath); - - assertEquals(List.of("this"), result.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void quadrupleBackReference() { - BasicElementPath absolutePath = BasicElementPath.parse("/this/is/a/test"); - BasicElementPath relativePath = BasicElementPath.parse("../../../.."); - - ElementPath result = absolutePath.resolve(relativePath); - - assertEquals(List.of(), result.nodes().stream().map(ElementPath.Node::name).toList()); - } - - @Test - void quintupleBackReference() { - BasicElementPath absolutePath = BasicElementPath.parse("/this/is/a/test"); - BasicElementPath relativePath = BasicElementPath.parse("../../../../.."); - - ElementPath result = absolutePath.resolve(relativePath); - - assertEquals(List.of(".."), result.nodes().stream().map(ElementPath.Node::name).toList()); - } -} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 85f0828..d53758f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ metadata.format.version = "1.1" [versions] -ethylene = "0.24.0" +ethylene = "0.25.0" junit-jupiter = "5.9.0-M1" toolkit = "0.4.0"