From 2de3b8218208d0c1b34e99e6c9ad2543a751c222 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Wed, 9 Aug 2023 11:15:04 +0200 Subject: [PATCH] #18 Comments on beans: Rename methods, revise Javadoc, add missing tests, remove unused CollectionUtils --- src/main/java/ch/jalu/configme/Comment.java | 13 +++-- .../BeanPropertyDescription.java | 2 +- .../BeanPropertyDescriptionImpl.java | 2 +- .../convertresult/ValueWithComments.java | 23 +++++++- .../resource/PropertyPathTraverser.java | 16 +++--- .../configme/resource/YamlFileResource.java | 35 ++++++++----- .../resource/yaml/SnakeYamlNodeBuilder.java | 14 ++--- .../yaml/SnakeYamlNodeBuilderImpl.java | 17 +++--- .../resource/yaml/SnakeYamlNodeContainer.java | 16 +++--- .../yaml/SnakeYamlNodeContainerImpl.java | 6 ++- .../jalu/configme/utils/CollectionUtils.java | 52 ------------------- .../ch/jalu/configme/utils/StreamUtils.java | 26 ++++++++++ .../MapperImplCommentRepetitionTest.java | 5 -- .../BeanDescriptionFactoryImplTest.java | 40 ++++++++++++++ .../convertresult/ValueWithCommentsTest.java | 15 ++++++ .../yaml/SnakeYamlNodeBuilderImplTest.java | 26 +++++----- .../yaml/SnakeYamlNodeContainerImplTest.java | 14 +++++ .../configme/utils/CollectionUtilsTest.java | 52 ------------------- .../jalu/configme/utils/StreamUtilsTest.java | 29 +++++++++++ 19 files changed, 226 insertions(+), 177 deletions(-) delete mode 100644 src/main/java/ch/jalu/configme/utils/CollectionUtils.java create mode 100644 src/main/java/ch/jalu/configme/utils/StreamUtils.java delete mode 100644 src/test/java/ch/jalu/configme/utils/CollectionUtilsTest.java create mode 100644 src/test/java/ch/jalu/configme/utils/StreamUtilsTest.java diff --git a/src/main/java/ch/jalu/configme/Comment.java b/src/main/java/ch/jalu/configme/Comment.java index 9c458e1d..903b257a 100644 --- a/src/main/java/ch/jalu/configme/Comment.java +++ b/src/main/java/ch/jalu/configme/Comment.java @@ -8,7 +8,9 @@ import java.lang.annotation.Target; /** - * Comment for properties which are also included in the YAML file upon saving. + * Comment for properties which are also included in the YAML file upon saving. This annotation can be used on + * {@link ch.jalu.configme.properties.Property Property} fields, as well as on the fields of + * bean classes that are used for {@link ch.jalu.configme.properties.BeanProperty bean properties}. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @@ -23,11 +25,12 @@ String @NotNull [] value(); /** - * Defines if the comment should be repeated if it would be included multiple times. Relevant only for bean - * properties: if a class used in a collection has comments, the comments will either be included on the first - * entry if {@code false}, or on each entry if {@code true}. + * Defines if the comment should be repeated, or if it should only be included in the output the first time. + * This method is relevant only for bean properties: if a bean class used in a collection has comments, + * the comments will either be included on the first entry if {@code false}, or on each entry if {@code true}. * - * @return whether to repeat the comment if the same field is exported multiple times + * @return whether the comment should be repeated for each occurrence of the same field */ boolean repeat() default false; + } diff --git a/src/main/java/ch/jalu/configme/beanmapper/propertydescription/BeanPropertyDescription.java b/src/main/java/ch/jalu/configme/beanmapper/propertydescription/BeanPropertyDescription.java index 41416fd9..b72c7eb9 100644 --- a/src/main/java/ch/jalu/configme/beanmapper/propertydescription/BeanPropertyDescription.java +++ b/src/main/java/ch/jalu/configme/beanmapper/propertydescription/BeanPropertyDescription.java @@ -40,7 +40,7 @@ public interface BeanPropertyDescription { @Nullable Object getValue(@NotNull Object bean); /** - * @return the comments to add when this property is exported + * @return the comments associated with this property */ @NotNull BeanPropertyComments getComments(); diff --git a/src/main/java/ch/jalu/configme/beanmapper/propertydescription/BeanPropertyDescriptionImpl.java b/src/main/java/ch/jalu/configme/beanmapper/propertydescription/BeanPropertyDescriptionImpl.java index 2e2086e6..35abc945 100644 --- a/src/main/java/ch/jalu/configme/beanmapper/propertydescription/BeanPropertyDescriptionImpl.java +++ b/src/main/java/ch/jalu/configme/beanmapper/propertydescription/BeanPropertyDescriptionImpl.java @@ -40,7 +40,7 @@ public BeanPropertyDescriptionImpl(@NotNull String name, @NotNull TypeInformatio * @param typeInformation type of the property * @param getter getter for the property * @param setter setter for the property - * @param comments the comments for this property + * @param comments the comments of the property */ public BeanPropertyDescriptionImpl(@NotNull String name, @NotNull TypeInformation typeInformation, @NotNull Method getter, @NotNull Method setter, diff --git a/src/main/java/ch/jalu/configme/properties/convertresult/ValueWithComments.java b/src/main/java/ch/jalu/configme/properties/convertresult/ValueWithComments.java index ae2ed648..73f6429b 100644 --- a/src/main/java/ch/jalu/configme/properties/convertresult/ValueWithComments.java +++ b/src/main/java/ch/jalu/configme/properties/convertresult/ValueWithComments.java @@ -1,12 +1,17 @@ package ch.jalu.configme.properties.convertresult; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.util.List; +import java.util.stream.Stream; /** * Wraps a value and allows to associate it with comments. Can be used as return type from * {@link ch.jalu.configme.properties.Property#toExportValue}. *

- * Prefer defining comments in {@link ch.jalu.configme.SettingsHolder} classes whenever possible. + * Prefer defining comments in {@link ch.jalu.configme.SettingsHolder} classes whenever your comments can be statically + * defined. */ public class ValueWithComments { @@ -19,7 +24,7 @@ public class ValueWithComments { * @param value the value to wrap * @param comments the comments associated with the value */ - public ValueWithComments(Object value, List comments) { + public ValueWithComments(@NotNull Object value, @NotNull List comments) { this.value = value; this.comments = comments; } @@ -51,4 +56,18 @@ public static Object unwrapValue(Object object) { } return object; } + + /** + * Returns a stream with the comments on the given object, if it is a {@link ValueWithComments}. An empty + * stream is returned otherwise. + * + * @param object the object to get comments from, if applicable + * @return stream with the comments (never null) + */ + public static @NotNull Stream streamThroughCommentsIfApplicable(@Nullable Object object) { + if (object instanceof ValueWithComments) { + return ((ValueWithComments) object).getComments().stream(); + } + return Stream.empty(); + } } diff --git a/src/main/java/ch/jalu/configme/resource/PropertyPathTraverser.java b/src/main/java/ch/jalu/configme/resource/PropertyPathTraverser.java index 42319765..a662a4b8 100644 --- a/src/main/java/ch/jalu/configme/resource/PropertyPathTraverser.java +++ b/src/main/java/ch/jalu/configme/resource/PropertyPathTraverser.java @@ -17,7 +17,7 @@ public class PropertyPathTraverser { /** The last path that was processed. */ private String lastPath; - private boolean isFirstProperty = true; + private boolean isFirstElement = true; /** * Returns all path elements of the given path. @@ -35,14 +35,14 @@ public class PropertyPathTraverser { int level = 0; for (int i = 0; i < totalParts; ++i) { fullPathBuilder.append(pathParts[i]); - PathElement element = new PathElement(level, pathParts[i], fullPathBuilder.toString(), isFirstProperty); + PathElement element = new PathElement(level, pathParts[i], fullPathBuilder.toString(), isFirstElement); element.setEndOfPath(i == totalParts - 1); element.setFirstOfGroup(levelOfFirstNewPart == level); pathElements.add(element); ++level; fullPathBuilder.append("."); - isFirstProperty = false; + isFirstElement = false; } lastPath = path; return pathElements; @@ -56,7 +56,7 @@ public class PropertyPathTraverser { * @param path the new path * @return the level of the first new path element */ - private int returnLevelOfFirstNewPathElement(@NotNull String path) { + protected int returnLevelOfFirstNewPathElement(@NotNull String path) { if (lastPath == null) { return 0; } @@ -134,22 +134,22 @@ public boolean isFirstOfGroup() { return isFirstOfGroup; } - void setFirstOfGroup(boolean firstOfGroup) { + protected void setFirstOfGroup(boolean firstOfGroup) { isFirstOfGroup = firstOfGroup; } /** - * Returns if this path element is at the end, i.e. whether it represents a leaf path that is associated to + * Returns whether this path element represents the final part of a path, indicating that it is associated with * a property. For example, given a property {@code config.datasource.driver.version}, the path element for * {@code version} returns true for this method. * - * @return true if this element is the final part of the given path + * @return true if this element is the last part of the path (i.e. if it's a "leaf element") */ public boolean isEndOfPath() { return isEndOfPath; } - void setEndOfPath(boolean isEndOfPath) { + protected void setEndOfPath(boolean isEndOfPath) { this.isEndOfPath = isEndOfPath; } } diff --git a/src/main/java/ch/jalu/configme/resource/YamlFileResource.java b/src/main/java/ch/jalu/configme/resource/YamlFileResource.java index 6242158c..fa561bc7 100644 --- a/src/main/java/ch/jalu/configme/resource/YamlFileResource.java +++ b/src/main/java/ch/jalu/configme/resource/YamlFileResource.java @@ -8,6 +8,7 @@ import ch.jalu.configme.resource.yaml.SnakeYamlNodeBuilderImpl; import ch.jalu.configme.resource.yaml.SnakeYamlNodeContainer; import ch.jalu.configme.resource.yaml.SnakeYamlNodeContainerImpl; +import ch.jalu.configme.utils.StreamUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.yaml.snakeyaml.DumperOptions; @@ -22,14 +23,12 @@ import java.nio.file.Path; import java.util.List; import java.util.stream.Collectors; -import java.util.stream.IntStream; import java.util.stream.Stream; public class YamlFileResource implements PropertyResource { private final Path path; private final @NotNull YamlFileResourceOptions options; - private final int indentationSize; private @Nullable Yaml yamlObject; public YamlFileResource(@NotNull Path path) { @@ -39,7 +38,6 @@ public YamlFileResource(@NotNull Path path) { public YamlFileResource(@NotNull Path path, @NotNull YamlFileResourceOptions options) { this.path = path; this.options = options; - this.indentationSize = options.getIndentationSize(); } /** @@ -68,8 +66,9 @@ public void exportProperties(@NotNull ConfigurationData configurationData) { for (Property property : properties) { Object exportValue = getExportValue(property, configurationData); if (exportValue != null) { - List pathElements = pathTraverser.getPathElements(property.getPath()); - exportValue(configurationData, root, pathElements, nodeBuilder, property.getPath(), exportValue); + String path = property.getPath(); + List pathElements = pathTraverser.getPathElements(path); + createAndAddYamlNode(exportValue, path, pathElements, root, configurationData, nodeBuilder); } } @@ -90,16 +89,28 @@ public void exportProperties(@NotNull ConfigurationData configurationData) { } } - private void exportValue(@NotNull ConfigurationData configurationData, - @NotNull SnakeYamlNodeContainer rootContainer, - @NotNull List pathElements, @NotNull SnakeYamlNodeBuilder nodeBuilder, - @NotNull String path, @NotNull Object exportValue) { + /** + * Creates a YAML node for the export value and stores it, along with any comments for intermediate paths that + * have not been visited yet. + * + * @param exportValue the export value to store + * @param path the path the export value is for + * @param pathElements the path elements of this property's path + * @param rootContainer the root YAML node container for storing the export value + * @param configurationData the configuration data (for the retrieval of comments) + * @param nodeBuilder YAML node builder + */ + protected void createAndAddYamlNode(@NotNull Object exportValue, @NotNull String path, + @NotNull List pathElements, + @NotNull SnakeYamlNodeContainer rootContainer, + @NotNull ConfigurationData configurationData, + @NotNull SnakeYamlNodeBuilder nodeBuilder) { SnakeYamlNodeContainer container = rootContainer; for (PathElement pathElement : pathElements) { if (pathElement.isEndOfPath()) { int emptyLines = options.getNumberOfEmptyLinesBefore(pathElement); container.putNode(pathElement.getName(), - nodeBuilder.toYamlNode(exportValue, path, configurationData, emptyLines)); + nodeBuilder.createYamlNode(exportValue, path, configurationData, emptyLines)); } else { container = container.getOrCreateChildContainer(pathElement.getName(), () -> getCommentsForPathElement(configurationData, pathElement)); @@ -111,7 +122,7 @@ private void exportValue(@NotNull ConfigurationData configurationData, protected List getCommentsForPathElement(@NotNull ConfigurationData configurationData, @NotNull PathElement pathElement) { return Stream.concat( - IntStream.range(0, options.getNumberOfEmptyLinesBefore(pathElement)).mapToObj(i -> "\n"), + StreamUtils.repeat("\n", options.getNumberOfEmptyLinesBefore(pathElement)), configurationData.getCommentsForSection(pathElement.getFullPath()).stream()) .collect(Collectors.toList()); } @@ -150,7 +161,7 @@ protected void onWriteComplete() { options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); options.setAllowUnicode(true); options.setProcessComments(true); - options.setIndent(indentationSize); + options.setIndent(this.options.getIndentationSize()); return new Yaml(options); } diff --git a/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeBuilder.java b/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeBuilder.java index fed4de9b..d8162ea7 100644 --- a/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeBuilder.java +++ b/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeBuilder.java @@ -11,7 +11,7 @@ public interface SnakeYamlNodeBuilder { /** - * Creates a SnakeYAML node for the given value. + * Creates a SnakeYAML node representing the given value. * * @param value the value to create the node for (export value of a property) * @param path the path of the property whose value is exported @@ -19,8 +19,8 @@ public interface SnakeYamlNodeBuilder { * @param numberOfNewLines number of new lines before the property, to add as new lines to the node * @return SnakeYAML node of the appropriate type for the value, including comments */ - @NotNull Node toYamlNode(@NotNull Object value, @NotNull String path, @NotNull ConfigurationData configurationData, - int numberOfNewLines); + @NotNull Node createYamlNode(@NotNull Object value, @NotNull String path, + @NotNull ConfigurationData configurationData, int numberOfNewLines); /** * Creates a SnakeYAML string node for a key value (e.g. object property, or map key). @@ -32,7 +32,7 @@ public interface SnakeYamlNodeBuilder { /** * Creates a SnakeYAML {@link CommentLine} to represent the given comment. If the comment is equal to the new line - * character {@code \n}, the comment line should represent a blank line. + * character {@code \n}, the created comment line represents a blank line. * * @param comment the comment to represent as CommentLine * @return appropriate comment line object for the given comment @@ -41,11 +41,11 @@ public interface SnakeYamlNodeBuilder { /** * Transfers the comments from the value node to the key node. Logically, comments are associated with values, - * but we do not want the comments to appear between the key and the value in the YAML. Therefore, this method is - * called before producing YAML as to move the comments from the value to the key node. + * but we do not want the comments to appear between the key and the value in the YAML output. Therefore, this + * method is called before producing YAML as to move the comments from the value to the key node. * * @implNote Only considers {@link Node#getBlockComments() block comments} on the nodes because it's the only type - * of comment that this builder sets. + * of comment that this builder sets. Any block comments on the key node are overwritten. * * @param valueNode the value node to remove the comments from * @param keyNode the key node to set the comments to diff --git a/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeBuilderImpl.java b/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeBuilderImpl.java index 3c72fe25..a392f109 100644 --- a/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeBuilderImpl.java +++ b/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeBuilderImpl.java @@ -2,6 +2,7 @@ import ch.jalu.configme.configurationdata.ConfigurationData; import ch.jalu.configme.properties.convertresult.ValueWithComments; +import ch.jalu.configme.utils.StreamUtils; import org.jetbrains.annotations.NotNull; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.comments.CommentLine; @@ -22,7 +23,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.IntStream; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -32,8 +32,8 @@ public class SnakeYamlNodeBuilderImpl implements SnakeYamlNodeBuilder { @Override - public @NotNull Node toYamlNode(@NotNull Object obj, @NotNull String path, - @NotNull ConfigurationData configurationData, int numberOfNewLines) { + public @NotNull Node createYamlNode(@NotNull Object obj, @NotNull String path, + @NotNull ConfigurationData configurationData, int numberOfNewLines) { Object value = ValueWithComments.unwrapValue(obj); if (value instanceof Enum) { value = ((Enum) value).name(); @@ -108,7 +108,7 @@ public void transferComments(@NotNull Node valueNode, @NotNull Node keyNode) { List values = entries .map(entry -> { String entryPath = pathPrefix.concat(Integer.toString(counter.getAndIncrement())); - return toYamlNode(entry, entryPath, configurationData, 0); + return createYamlNode(entry, entryPath, configurationData, 0); }) .collect(Collectors.toList()); @@ -122,7 +122,7 @@ public void transferComments(@NotNull Node valueNode, @NotNull Node keyNode) { for (Map.Entry entry : value.entrySet()) { Node keyNode = createKeyNode(entry.getKey()); - Node valueNode = toYamlNode(entry.getValue(), pathPrefix.concat(entry.getKey()), configurationData, 0); + Node valueNode = createYamlNode(entry.getValue(), pathPrefix.concat(entry.getKey()), configurationData, 0); transferComments(valueNode, keyNode); nodeEntries.add(new NodeTuple(keyNode, valueNode)); @@ -144,12 +144,9 @@ public void transferComments(@NotNull Node valueNode, @NotNull Node keyNode) { protected @NotNull List collectComments(@NotNull Object value, @NotNull String path, @NotNull ConfigurationData configurationData, int numberOfNewLines) { - Stream emptyLineStream = IntStream.range(0, numberOfNewLines) - .mapToObj(i -> "\n"); + Stream emptyLineStream = StreamUtils.repeat("\n", numberOfNewLines); Stream configDataStream = configurationData.getCommentsForSection(path).stream(); - Stream additionalCommentsStream = (value instanceof ValueWithComments) - ? ((ValueWithComments) value).getComments().stream() - : Stream.empty(); + Stream additionalCommentsStream = ValueWithComments.streamThroughCommentsIfApplicable(value); return Stream.of(emptyLineStream, configDataStream, additionalCommentsStream) .flatMap(Function.identity()) diff --git a/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeContainer.java b/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeContainer.java index 79915a3e..00f6b8e7 100644 --- a/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeContainer.java +++ b/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeContainer.java @@ -8,13 +8,13 @@ /** * Container that keeps SnakeYAML node objects in hierarchical order. Leaf values are SnakeYAML nodes, while parent - * nodes are a container that can be converted to a SnakeYAML node. + * values are containers that can be converted to SnakeYAML nodes representing all enclosed values. */ public interface SnakeYamlNodeContainer { /** - * Returns the existing container for the given name, or creates one and registers the comments as defined by the - * supplier. An exception is thrown if a node was saved for the given name. + * Returns the existing container for the given name, or creates one and registers the comments as returned by the + * supplier. An exception is thrown if a value (SnakeYAML node) was saved under the given name. * * @param name the path name to get * @param commentsSupplier supplier with comments to set if the container has to be created @@ -24,15 +24,15 @@ public interface SnakeYamlNodeContainer { /** * Returns the SnakeYAML node at the root path (empty string). Used as root of the YAML document when the - * configuration only has a property at root path. + * configuration only has one property at root path. Throws an exception if no value was stored for the root path. * * @return internal root node */ @NotNull Node getRootValueNode(); /** - * Saves the given node at the given path name. Throws an exception if a value is already associated for the given - * path name. + * Saves the given node under the given name (= path element). Throws an exception if a value is already associated + * with the given name. * * @param name the name to save the value under * @param node the node to save @@ -40,8 +40,8 @@ public interface SnakeYamlNodeContainer { void putNode(@NotNull String name, @NotNull Node node); /** - * Converts his container and its sub-containers, recursively, to a SnakeYAML node that represents - * the container's values. + * Converts this container and its sub-containers, recursively, to a SnakeYAML node that represents + * all SnakeYAML nodes held by the containers. * * @param nodeBuilder node builder to create nodes with * @return this container's values as SnakeYAML node diff --git a/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeContainerImpl.java b/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeContainerImpl.java index 89dcda6a..c2c2da8f 100644 --- a/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeContainerImpl.java +++ b/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeContainerImpl.java @@ -39,7 +39,11 @@ public SnakeYamlNodeContainerImpl(@NotNull List comments) { @Override public @NotNull Node getRootValueNode() { - return (Node) values.get(""); + Object rootValue = values.get(""); + if (rootValue == null) { + throw new IllegalStateException("No value was stored for the root path ''"); + } + return (Node) rootValue; } @Override diff --git a/src/main/java/ch/jalu/configme/utils/CollectionUtils.java b/src/main/java/ch/jalu/configme/utils/CollectionUtils.java deleted file mode 100644 index b644751b..00000000 --- a/src/main/java/ch/jalu/configme/utils/CollectionUtils.java +++ /dev/null @@ -1,52 +0,0 @@ -package ch.jalu.configme.utils; - -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * Collection utils. - */ -public final class CollectionUtils { - - private CollectionUtils() { - } - - /** - * Gets all elements from a list starting from the given index. - * - * @param list the List - * @param start the start index - * @param the list type - * @return the sublist of all elements from index {@code start} and on; empty list - * if the start index exceeds the list's size - */ - public static @NotNull List getRange(@NotNull List list, int start) { - if (start >= list.size()) { - return new ArrayList<>(); - } - return list.subList(start, list.size()); - } - - /** - * Returns all entries that are the same at the beginning of both given lists. - * - * @param list1 first list - * @param list2 second list - * @param type of the lists - * @return list of all equal entries at the start of both lists - */ - public static @NotNull List filterCommonStart(@NotNull List list1, @NotNull List list2) { - List commonStart = new ArrayList<>(); - int minSize = Math.min(list1.size(), list2.size()); - int i = 0; - while (i < minSize && Objects.equals(list1.get(i), list2.get(i))) { - commonStart.add(list1.get(i)); - ++i; - } - return commonStart; - } - -} diff --git a/src/main/java/ch/jalu/configme/utils/StreamUtils.java b/src/main/java/ch/jalu/configme/utils/StreamUtils.java new file mode 100644 index 00000000..ac902e51 --- /dev/null +++ b/src/main/java/ch/jalu/configme/utils/StreamUtils.java @@ -0,0 +1,26 @@ +package ch.jalu.configme.utils; + +import java.util.stream.IntStream; +import java.util.stream.Stream; + +/** + * Stream utils. + */ +public final class StreamUtils { + + private StreamUtils() { + } + + /** + * Creates a stream with the requested size. Every element in the stream is the given {@code element}. + * + * @param element the element to repeat + * @param numberOfTimes number of times the stream should have the element + * @param element type + * @return stream with the element the requested number of times + */ + public static Stream repeat(T element, int numberOfTimes) { + return IntStream.range(0, numberOfTimes) + .mapToObj(i -> element); + } +} diff --git a/src/test/java/ch/jalu/configme/beanmapper/MapperImplCommentRepetitionTest.java b/src/test/java/ch/jalu/configme/beanmapper/MapperImplCommentRepetitionTest.java index af8bf77e..93582b93 100644 --- a/src/test/java/ch/jalu/configme/beanmapper/MapperImplCommentRepetitionTest.java +++ b/src/test/java/ch/jalu/configme/beanmapper/MapperImplCommentRepetitionTest.java @@ -3,9 +3,7 @@ import ch.jalu.configme.Comment; import ch.jalu.configme.properties.convertresult.ValueWithComments; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -24,9 +22,6 @@ class MapperImplCommentRepetitionTest { private final MapperImpl mapper = new MapperImpl(); - @TempDir - public Path temporaryFolder; - @Test void shouldExportAsExpected() { // given diff --git a/src/test/java/ch/jalu/configme/beanmapper/propertydescription/BeanDescriptionFactoryImplTest.java b/src/test/java/ch/jalu/configme/beanmapper/propertydescription/BeanDescriptionFactoryImplTest.java index 825f91ae..54bde9a7 100644 --- a/src/test/java/ch/jalu/configme/beanmapper/propertydescription/BeanDescriptionFactoryImplTest.java +++ b/src/test/java/ch/jalu/configme/beanmapper/propertydescription/BeanDescriptionFactoryImplTest.java @@ -3,6 +3,7 @@ import ch.jalu.configme.Comment; import ch.jalu.configme.beanmapper.ConfigMeMapperException; +import ch.jalu.configme.beanmapper.command.ExecutionDetails; import ch.jalu.configme.samples.beanannotations.AnnotatedEntry; import ch.jalu.configme.samples.beanannotations.BeanWithEmptyName; import ch.jalu.configme.samples.beanannotations.BeanWithNameClash; @@ -25,6 +26,8 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; /** @@ -157,6 +160,43 @@ void shouldThrowForWhenExportNameIsNullForProperty() { "may not be empty"); } + @Test + void shouldReturnCommentsWithUuidIfNotRepeatable() { + // given + BeanDescriptionFactory factory = new BeanDescriptionFactoryImpl(); + + // when + Collection sampleBeanProperties = factory.getAllProperties(SampleBean.class); + Collection sampleBeanProperties2 = factory.getAllProperties(SampleBean.class); + + // then + BeanPropertyComments sizeComments = getDescription("size", sampleBeanProperties).getComments(); + assertThat(sizeComments.getComments(), contains("Size of this entry (cm)")); + assertThat(sizeComments.getUuid(), notNullValue()); + + // Actually ensure that we have the same UUID if we fetch properties for the same class again + // -> there's no point in the UUID otherwise! + BeanPropertyComments sizeComments2 = getDescription("size", sampleBeanProperties2).getComments(); + assertThat(sizeComments2.getUuid(), equalTo(sizeComments.getUuid())); + } + + @Test + void shouldReturnCommentsWithoutUuid() { + // given + BeanDescriptionFactory factory = new BeanDescriptionFactoryImpl(); + + // when + Collection execDetailsProperties = factory.getAllProperties(ExecutionDetails.class); + + // then + BeanPropertyComments executorComments = getDescription("executor", execDetailsProperties).getComments(); + assertThat(executorComments, sameInstance(BeanPropertyComments.EMPTY)); + + BeanPropertyComments importanceComments = getDescription("importance", execDetailsProperties).getComments(); + assertThat(importanceComments.getComments(), contains("The higher the number, the more important")); + assertThat(importanceComments.getUuid(), nullValue()); + } + private static BeanPropertyDescription getDescription(String name, Collection descriptions) { for (BeanPropertyDescription description : descriptions) { diff --git a/src/test/java/ch/jalu/configme/properties/convertresult/ValueWithCommentsTest.java b/src/test/java/ch/jalu/configme/properties/convertresult/ValueWithCommentsTest.java index bb86616e..8524ef5e 100644 --- a/src/test/java/ch/jalu/configme/properties/convertresult/ValueWithCommentsTest.java +++ b/src/test/java/ch/jalu/configme/properties/convertresult/ValueWithCommentsTest.java @@ -5,7 +5,10 @@ import java.util.Arrays; import java.util.concurrent.TimeUnit; +import static java.util.stream.Collectors.toList; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.nullValue; @@ -25,4 +28,16 @@ void shouldUnwrapValue() { assertThat(ValueWithComments.unwrapValue(object2), equalTo(TimeUnit.SECONDS)); assertThat(ValueWithComments.unwrapValue(null), nullValue()); } + + @Test + void shouldStreamThroughComments() { + // given + Object object1 = new ValueWithComments("test", Arrays.asList("Explanatory", "comments")); + Object object2 = TimeUnit.SECONDS; + + // when / then + assertThat(ValueWithComments.streamThroughCommentsIfApplicable(object1).collect(toList()), contains("Explanatory", "comments")); + assertThat(ValueWithComments.streamThroughCommentsIfApplicable(object2).collect(toList()), empty()); + assertThat(ValueWithComments.streamThroughCommentsIfApplicable(null).collect(toList()), empty()); + } } diff --git a/src/test/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeBuilderImplTest.java b/src/test/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeBuilderImplTest.java index 540c0079..692db6c5 100644 --- a/src/test/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeBuilderImplTest.java +++ b/src/test/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeBuilderImplTest.java @@ -57,7 +57,7 @@ void shouldCreateNodeForString() { given(configurationData.getCommentsForSection(path)).willReturn(Collections.singletonList("Title text")); // when - Node node = nodeBuilder.toYamlNode(value, path, configurationData, 2); + Node node = nodeBuilder.createYamlNode(value, path, configurationData, 2); // then assertThat(node, instanceOf(ScalarNode.class)); @@ -82,7 +82,7 @@ void shouldCreateNodeForEnum() { given(configurationData.getCommentsForSection(path)).willReturn(Arrays.asList("comment 1", "comment 2")); // when - Node node = nodeBuilder.toYamlNode(value, path, configurationData, 0); + Node node = nodeBuilder.createYamlNode(value, path, configurationData, 0); // then assertThat(node, instanceOf(ScalarNode.class)); @@ -106,7 +106,7 @@ void shouldCreateNodeForInt() { given(configurationData.getCommentsForSection(path)).willReturn(Collections.emptyList()); // when - Node node = nodeBuilder.toYamlNode(value, path, configurationData, 0); + Node node = nodeBuilder.createYamlNode(value, path, configurationData, 0); // then assertThat(node, instanceOf(ScalarNode.class)); @@ -128,7 +128,7 @@ void shouldCreateNodeForDouble() { given(configurationData.getCommentsForSection(path)).willReturn(Collections.emptyList()); // when - Node node = nodeBuilder.toYamlNode(value, path, configurationData, 1); + Node node = nodeBuilder.createYamlNode(value, path, configurationData, 1); // then assertThat(node, instanceOf(ScalarNode.class)); @@ -151,7 +151,7 @@ void shouldCreateNodeForBigDecimal() { given(configurationData.getCommentsForSection(path)).willReturn(Collections.singletonList("Pi up to 12 digits")); // when - Node node = nodeBuilder.toYamlNode(value, path, configurationData, 1); + Node node = nodeBuilder.createYamlNode(value, path, configurationData, 1); // then assertThat(node, instanceOf(ScalarNode.class)); @@ -175,7 +175,7 @@ void shouldCreateNodeForBoolean() { given(configurationData.getCommentsForSection(path)).willReturn(Collections.emptyList()); // when - Node node = nodeBuilder.toYamlNode(value, path, configurationData, 0); + Node node = nodeBuilder.createYamlNode(value, path, configurationData, 0); // then assertThat(node, instanceOf(ScalarNode.class)); @@ -199,7 +199,7 @@ void shouldCreateNodeForList() { given(configurationData.getCommentsForSection(path + ".1")).willReturn(Collections.singletonList("\n")); // when - Node node = nodeBuilder.toYamlNode(value, path, configurationData, 1); + Node node = nodeBuilder.createYamlNode(value, path, configurationData, 1); // then verify(configurationData).getCommentsForSection(path); @@ -240,7 +240,7 @@ void shouldCreateNodeForArray() { String path = "calc.flags"; // when - Node result = nodeBuilder.toYamlNode(value, path, configurationData, 0); + Node result = nodeBuilder.createYamlNode(value, path, configurationData, 0); // then verify(configurationData).getCommentsForSection(path); @@ -282,7 +282,7 @@ void shouldCreateNodeForMap() { given(configurationData.getCommentsForSection(path + ".A")).willReturn(Collections.singletonList("Alpha comp.")); // when - Node result = nodeBuilder.toYamlNode(factors, path, configurationData, 0); + Node result = nodeBuilder.createYamlNode(factors, path, configurationData, 0); // then verify(configurationData).getCommentsForSection(path); @@ -329,7 +329,7 @@ void shouldHandleEmptyMap() { given(configurationData.getCommentsForSection(path)).willReturn(Collections.singletonList("Overridden factors")); // when - Node result = nodeBuilder.toYamlNode(factors, path, configurationData, 0); + Node result = nodeBuilder.createYamlNode(factors, path, configurationData, 0); // then verify(configurationData, only()).getCommentsForSection(path); @@ -355,7 +355,7 @@ void shouldHandleEmptyArray() { given(configurationData.getCommentsForSection(path)).willReturn(Collections.singletonList("Overridden factors")); // when - Node result = nodeBuilder.toYamlNode(new ValueWithComments(value, Arrays.asList("R1", "R2")), path, + Node result = nodeBuilder.createYamlNode(new ValueWithComments(value, Arrays.asList("R1", "R2")), path, configurationData, 1); // then @@ -382,9 +382,9 @@ void shouldThrowForUnknownValueTypes() { // when IllegalArgumentException ex1 = assertThrows(IllegalArgumentException.class, - () -> nodeBuilder.toYamlNode(Optional.of(3), "", configurationData, 3)); + () -> nodeBuilder.createYamlNode(Optional.of(3), "", configurationData, 3)); IllegalArgumentException ex2 = assertThrows(IllegalArgumentException.class, - () -> nodeBuilder.toYamlNode(null, "", configurationData, 3)); + () -> nodeBuilder.createYamlNode(null, "", configurationData, 3)); // then assertThat(ex1.getMessage(), equalTo("Unsupported value of type: java.util.Optional")); diff --git a/src/test/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeContainerImplTest.java b/src/test/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeContainerImplTest.java index 71fa55f9..741de4ec 100644 --- a/src/test/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeContainerImplTest.java +++ b/src/test/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeContainerImplTest.java @@ -148,4 +148,18 @@ void shouldReturnRootNode() { // then assertThat(rootNode, sameInstance(boolNode)); } + + @Test + void shouldThrowIfNoRootValueWasSaved() { + // given + SnakeYamlNodeContainer container = new SnakeYamlNodeContainerImpl(Collections.emptyList()); + ScalarNode boolNode = new ScalarNode(Tag.BOOL, "true", null, null, DumperOptions.ScalarStyle.PLAIN); + container.putNode("isTest", boolNode); + + // when + IllegalStateException ex = assertThrows(IllegalStateException.class, container::getRootValueNode); + + // then + assertThat(ex.getMessage(), equalTo("No value was stored for the root path ''")); + } } diff --git a/src/test/java/ch/jalu/configme/utils/CollectionUtilsTest.java b/src/test/java/ch/jalu/configme/utils/CollectionUtilsTest.java deleted file mode 100644 index 48255788..00000000 --- a/src/test/java/ch/jalu/configme/utils/CollectionUtilsTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package ch.jalu.configme.utils; - -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.List; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.empty; - -/** - * Test for {@link CollectionUtils}. - */ -class CollectionUtilsTest { - - @Test - void shouldGetRangesFromList() { - // given - List list = Arrays.asList("test", "1", "2", "3", "4"); - - // when - List result1 = CollectionUtils.getRange(list, 2); - List result2 = CollectionUtils.getRange(list, 5); - - // then - assertThat(result1, contains("2", "3", "4")); - assertThat(result2, empty()); - } - - @Test - void shouldFindCommonEntries() { - // given - List list1 = Arrays.asList("test", "1", "2", "3", "4"); - List list2 = Arrays.asList("test", "1", "2", "xxx", "xxx"); - List list3 = Arrays.asList("test", "1", "2", "3", "4", "5"); - List list4 = Arrays.asList("abc", null, "def", "ghi"); - List list5 = Arrays.asList("abc", null, "uvw", "xyz"); - - // when - List result1 = CollectionUtils.filterCommonStart(list1, list2); - List result2 = CollectionUtils.filterCommonStart(list1, list3); - List result3 = CollectionUtils.filterCommonStart(list1, list4); - List result4 = CollectionUtils.filterCommonStart(list4, list5); - - // then - assertThat(result1, contains("test", "1", "2")); - assertThat(result2, contains("test", "1", "2", "3", "4")); - assertThat(result3, empty()); - assertThat(result4, contains("abc", null)); - } -} diff --git a/src/test/java/ch/jalu/configme/utils/StreamUtilsTest.java b/src/test/java/ch/jalu/configme/utils/StreamUtilsTest.java new file mode 100644 index 00000000..087847fb --- /dev/null +++ b/src/test/java/ch/jalu/configme/utils/StreamUtilsTest.java @@ -0,0 +1,29 @@ +package ch.jalu.configme.utils; + +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; + +/** + * Test for {@link StreamUtils}. + */ +class StreamUtilsTest { + + @Test + void shouldCreateStreamWithSameElement() { + // given / when + List list1 = StreamUtils.repeat("3", 3).collect(Collectors.toList()); + List list2 = StreamUtils.repeat("1", 1).collect(Collectors.toList()); + List list3 = StreamUtils.repeat("0", 0).collect(Collectors.toList()); + + // then + assertThat(list1, contains("3", "3", "3")); + assertThat(list2, contains("1")); + assertThat(list3, empty()); + } +}