Repository: decorators-squad/eo-yaml
Pull request #482 (2021-12-11 09:07:43)

#480 Fixed literal block scalar read bug.
Fixed bug where literal block scalar were not read correctly from string/file.

PR for #480 


Patch:
diff --git a/README.md b/README.md
index ba7de8c..e37421a 100644
--- a/README.md
+++ b/README.md
@@ -23,16 +23,16 @@ To get the latest release from Maven Central, simply add the following to your `
 <dependency>
     <groupId>com.amihaiemil.web</groupId>
     <artifactId>eo-yaml</artifactId>
-    <version>6.0.0</version>
+    <version>5.2.2</version>
 </dependency>
 ```
 
-or download the <a href="https://oss.sonatype.org/service/local/repositories/releases/content/com/amihaiemil/web/eo-yaml/6.0.0/eo-yaml-6.0.0-jar-with-dependencies.jar">fat</a> jar.
+or download the <a href="https://oss.sonatype.org/service/local/repositories/releases/content/com/amihaiemil/web/eo-yaml/5.2.2/eo-yaml-5.2.2-jar-with-dependencies.jar">fat</a> jar.
 
 If you use Gradle, add this to your dependencies:
 
 ```gradle
-implementation group: 'com.amihaiemil.web', name: 'eo-yaml', version: '6.0.0'
+implementation group: 'com.amihaiemil.web', name: 'eo-yaml', version: '5.2.2'
 ```
 
 The releases are also available on [Github Packages](https://github.com/decorators-squad/eo-yaml/packages)!
diff --git a/pom.xml b/pom.xml
index b1bd7d4..dcc02cc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.amihaiemil.web</groupId>
     <artifactId>eo-yaml</artifactId>
-    <version>6.0.1-SNAPSHOT</version><!--rrv-sed-flag-->
+    <version>5.2.3-SNAPSHOT</version><!--rrv-sed-flag-->
     <packaging>jar</packaging>
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
diff --git a/src/main/java/com/amihaiemil/eoyaml/BaseScalar.java b/src/main/java/com/amihaiemil/eoyaml/BaseScalar.java
index d6f36e4..d810bfc 100644
--- a/src/main/java/com/amihaiemil/eoyaml/BaseScalar.java
+++ b/src/main/java/com/amihaiemil/eoyaml/BaseScalar.java
@@ -108,7 +108,7 @@ abstract class BaseScalar extends BaseYamlNode implements Scalar {
     }
 
     @Override
-    public final boolean isEmpty() {
+    final boolean isEmpty() {
         return this.value() == null || this.value().isEmpty();
     }
 }
diff --git a/src/main/java/com/amihaiemil/eoyaml/BaseYamlMapping.java b/src/main/java/com/amihaiemil/eoyaml/BaseYamlMapping.java
index 2c8a407..12386f2 100644
--- a/src/main/java/com/amihaiemil/eoyaml/BaseYamlMapping.java
+++ b/src/main/java/com/amihaiemil/eoyaml/BaseYamlMapping.java
@@ -139,7 +139,7 @@ public abstract class BaseYamlMapping
     }
 
     @Override
-    public final boolean isEmpty() {
+    final boolean isEmpty() {
         return this.keys().isEmpty();
     }
 }
diff --git a/src/main/java/com/amihaiemil/eoyaml/BaseYamlNode.java b/src/main/java/com/amihaiemil/eoyaml/BaseYamlNode.java
index ca9082b..9847d69 100644
--- a/src/main/java/com/amihaiemil/eoyaml/BaseYamlNode.java
+++ b/src/main/java/com/amihaiemil/eoyaml/BaseYamlNode.java
@@ -29,6 +29,7 @@ package com.amihaiemil.eoyaml;
 
 import com.amihaiemil.eoyaml.exceptions.YamlPrintException;
 import com.amihaiemil.eoyaml.exceptions.YamlReadingException;
+
 import java.io.IOException;
 import java.io.StringWriter;
 
@@ -46,6 +47,12 @@ import java.io.StringWriter;
  */
 abstract class BaseYamlNode implements YamlNode {
 
+    /**
+     * Is this YamlNode empty?
+     * @return True or false.
+     */
+    abstract boolean isEmpty();
+
     @Override
     public final Scalar asScalar()
         throws YamlReadingException, ClassCastException {
diff --git a/src/main/java/com/amihaiemil/eoyaml/BaseYamlSequence.java b/src/main/java/com/amihaiemil/eoyaml/BaseYamlSequence.java
index fdbcc42..5e89180 100644
--- a/src/main/java/com/amihaiemil/eoyaml/BaseYamlSequence.java
+++ b/src/main/java/com/amihaiemil/eoyaml/BaseYamlSequence.java
@@ -125,7 +125,7 @@ public abstract class BaseYamlSequence
     }
 
     @Override
-    public final boolean isEmpty() {
+    final boolean isEmpty() {
         return this.values().isEmpty();
     }
 }
diff --git a/src/main/java/com/amihaiemil/eoyaml/BaseYamlStream.java b/src/main/java/com/amihaiemil/eoyaml/BaseYamlStream.java
index 07ea33f..ebaf126 100644
--- a/src/main/java/com/amihaiemil/eoyaml/BaseYamlStream.java
+++ b/src/main/java/com/amihaiemil/eoyaml/BaseYamlStream.java
@@ -126,7 +126,7 @@ abstract class BaseYamlStream extends BaseYamlNode implements YamlStream {
     }
 
     @Override
-    public final boolean isEmpty() {
+    final boolean isEmpty() {
         return this.values().isEmpty();
     }
 }
diff --git a/src/main/java/com/amihaiemil/eoyaml/FirstCommentFound.java b/src/main/java/com/amihaiemil/eoyaml/FirstCommentFound.java
index 25922d6..ff33a72 100644
--- a/src/main/java/com/amihaiemil/eoyaml/FirstCommentFound.java
+++ b/src/main/java/com/amihaiemil/eoyaml/FirstCommentFound.java
@@ -146,10 +146,12 @@ final class FirstCommentFound implements YamlLines {
                         if(line.trimmed().startsWith("#")) {
                             comment.add(line);
                         }
-                    } else if (!line.trimmed().isEmpty()){
+                    } else {
                         break;
                     }
                 }
+            }
+            if(comment.size() > 0) {
                 break;
             }
         }
diff --git a/src/main/java/com/amihaiemil/eoyaml/ReadLiteralBlockScalar.java b/src/main/java/com/amihaiemil/eoyaml/ReadLiteralBlockScalar.java
index 6f6e798..ab7da7e 100644
--- a/src/main/java/com/amihaiemil/eoyaml/ReadLiteralBlockScalar.java
+++ b/src/main/java/com/amihaiemil/eoyaml/ReadLiteralBlockScalar.java
@@ -87,6 +87,17 @@ final class ReadLiteralBlockScalar extends BaseScalar {
             new Skip(
                 lines,
                 line -> line.number() <= previous.number(),
+                line -> {
+                    final YamlLine key = previous;
+                    final Skip.Line skipLine = (Skip.Line) line;
+                    if(skipLine.indentation() == key.indentation()) {
+                        // mark that we finished the block by storing the
+                        // block's key line
+                        skipLine.store(key);
+                    }
+                    // if the key is set then we can safely skip remaining lines
+                    return skipLine.getStored().equals(key);
+                },
                 line -> line.trimmed().endsWith("|"),
                 line -> line.trimmed().startsWith("---"),
                 line -> line.trimmed().startsWith("..."),
diff --git a/src/main/java/com/amihaiemil/eoyaml/ReadYamlMapping.java b/src/main/java/com/amihaiemil/eoyaml/ReadYamlMapping.java
index 614a17e..8fc39a5 100644
--- a/src/main/java/com/amihaiemil/eoyaml/ReadYamlMapping.java
+++ b/src/main/java/com/amihaiemil/eoyaml/ReadYamlMapping.java
@@ -309,7 +309,7 @@ final class ReadYamlMapping extends BaseYamlMapping {
                         throw new YamlReadingException(
                             "No value found for existing complex key: "
                           + System.lineSeparator()
-                          + key.toString()
+                          + ((BaseYamlNode) key).toString()
                         );
                     }
                     break;
diff --git a/src/main/java/com/amihaiemil/eoyaml/ReadYamlStream.java b/src/main/java/com/amihaiemil/eoyaml/ReadYamlStream.java
index 6d2995d..8cdc558 100644
--- a/src/main/java/com/amihaiemil/eoyaml/ReadYamlStream.java
+++ b/src/main/java/com/amihaiemil/eoyaml/ReadYamlStream.java
@@ -81,6 +81,7 @@ final class ReadYamlStream extends BaseYamlStream {
         );
         this.all = new Skip(
             lines,
+            line -> line.trimmed().startsWith("#"),
             line -> line.trimmed().startsWith("%")
         );
         this.guessIndentation = guessIndentation;
@@ -109,17 +110,11 @@ final class ReadYamlStream extends BaseYamlStream {
      */
     private YamlLines readDocument(final YamlLine start) {
         final List<YamlLine> yamlDocLines = new ArrayList<>();
-        final YamlLines docComment = new Backwards(
-            new FirstCommentFound(
-                new Backwards(
-                    new Skip(this.all, (line) -> line.number() > start.number())
-                ), true
-            )
-        );
-        for (final YamlLine line: docComment){
-            yamlDocLines.add(line);
+        final YamlLine startLine = this.all.line(start.number());
+        if(!"---".equals(startLine.trimmed())) {
+            yamlDocLines.add(startLine);
         }
-        for(final YamlLine line : this.all) {
+        for(final YamlLine line : this.all.original()) {
             if(line.number() > start.number()) {
                 final String current = line.trimmed();
                 if("---".equals(current) || "...".equals(current)) {
diff --git a/src/main/java/com/amihaiemil/eoyaml/RtYamlMappingBuilder.java b/src/main/java/com/amihaiemil/eoyaml/RtYamlMappingBuilder.java
index dafbf15..d98247d 100644
--- a/src/main/java/com/amihaiemil/eoyaml/RtYamlMappingBuilder.java
+++ b/src/main/java/com/amihaiemil/eoyaml/RtYamlMappingBuilder.java
@@ -80,7 +80,7 @@ final class RtYamlMappingBuilder implements YamlMappingBuilder {
 
     @Override
     public YamlMappingBuilder add(final YamlNode key, final YamlNode value) {
-        if(key == null || key.isEmpty()) {
+        if(key == null || ((BaseYamlNode) key).isEmpty()) {
             throw new IllegalArgumentException(
                 "The key in YamlMapping cannot be null or empty!"
             );
diff --git a/src/main/java/com/amihaiemil/eoyaml/RtYamlPrinter.java b/src/main/java/com/amihaiemil/eoyaml/RtYamlPrinter.java
index afa3de4..4da5e33 100644
--- a/src/main/java/com/amihaiemil/eoyaml/RtYamlPrinter.java
+++ b/src/main/java/com/amihaiemil/eoyaml/RtYamlPrinter.java
@@ -273,7 +273,7 @@ final class RtYamlPrinter implements YamlPrinter {
         final boolean onNewLine,
         final int indentation
     ) throws IOException {
-        if (node == null || node.isEmpty()) {
+        if (node == null || ((BaseYamlNode) node).isEmpty()) {
             if (node instanceof EmptyYamlSequence) {
                 this.writer.append(" ").append("[]");
             } else if (node instanceof EmptyYamlMapping) {
diff --git a/src/main/java/com/amihaiemil/eoyaml/Skip.java b/src/main/java/com/amihaiemil/eoyaml/Skip.java
index 96f19a4..660ac13 100644
--- a/src/main/java/com/amihaiemil/eoyaml/Skip.java
+++ b/src/main/java/com/amihaiemil/eoyaml/Skip.java
@@ -67,9 +67,12 @@ final class Skip implements YamlLines {
         Iterator<YamlLine> iterator = this.yamlLines.iterator();
         if (iterator.hasNext()) {
             final List<YamlLine> notSkipped = new ArrayList<>();
+            YamlLine previous = new YamlLine.NullYamlLine();
             while (iterator.hasNext()) {
                 boolean skip = false;
-                final YamlLine current = iterator.next();
+                final YamlLine next = iterator.next();
+                final Skip.Line current = new Skip.Line(next, previous);
+                previous = current;
                 for(int idx = 0; idx < this.conditions.length; idx++) {
                     if(this.conditions[idx].isMet(current)) {
                         skip = true;
@@ -77,7 +80,7 @@ final class Skip implements YamlLines {
                     }
                 }
                 if(!skip) {
-                    notSkipped.add(current);
+                    notSkipped.add(current.unwrap());
                 }
             }
             iterator = notSkipped.iterator();
@@ -115,4 +118,121 @@ final class Skip implements YamlLines {
         boolean isMet(final YamlLine line);
 
     }
+
+    /**
+     * A wrapper line that keeps track of current and previous line.
+     * <br/>
+     * It also can arbitrary store a line in order to be used as a reference
+     * downstream when evaluating the current iteration line condition.
+     * <br/>
+     * The current line is used as delegate for {@link YamlLine} API
+     * and previous line is a {@link Line} in order to carry over the
+     * "stored" state.
+     * <br/>
+     * <br/>
+     * <br/>
+     * Used by {@link Skip#iterator()} to feed current line {@link Condition}.
+     */
+    static final class Line implements YamlLine {
+
+        /**
+         * Current line.
+         */
+        private final YamlLine current;
+
+        /**
+         * Previous line.
+         */
+        private final YamlLine previous;
+
+        /**
+         * Arbitrary stored line.
+         */
+        private YamlLine stored;
+
+        /**
+         * Ctor.
+         * @param current Current line.
+         * @param previous Previous line.
+         */
+        private Line(final YamlLine current, final YamlLine previous) {
+            this.current = current;
+            this.previous = previous;
+            final YamlLine store;
+            if (previous instanceof Line) {
+                final Line prevLine = (Line) previous;
+                store = prevLine.stored;
+            } else {
+                store = new NullYamlLine();
+            }
+            this.stored = store;
+        }
+
+        @Override
+        public String trimmed() {
+            return this.current.trimmed();
+        }
+
+        @Override
+        public String contents(final int previousIndent) {
+            return this.current.contents(previousIndent);
+        }
+
+        @Override
+        public String comment() {
+            return this.current.comment();
+        }
+
+        @Override
+        public int number() {
+            return this.current.number();
+        }
+
+        @Override
+        public int indentation() {
+            return this.current.indentation();
+        }
+
+        @Override
+        public boolean requireNestedIndentation() {
+            return this.current.requireNestedIndentation();
+        }
+
+        @Override
+        public int compareTo(final YamlLine other) {
+            return this.current.compareTo(other);
+        }
+
+        /**
+         * Previous line. Usually a skip {@link Line}
+         * @return YamlLine.
+         */
+        YamlLine getPrevious() {
+            return this.previous;
+        }
+
+        /**
+         * Stores a line.
+         * @param line A {@link YamlLine}.
+         */
+        void store(final YamlLine line){
+            this.stored = line;
+        }
+
+        /**
+         * Get currently stored line.
+         * @return YamlLine
+         */
+        YamlLine getStored() {
+            return this.stored;
+        }
+
+        /**
+         * Unwraps to current line.
+         * @return YamlLine.
+         */
+        private YamlLine unwrap() {
+            return this.current;
+        }
+    }
 }
diff --git a/src/main/java/com/amihaiemil/eoyaml/YamlNode.java b/src/main/java/com/amihaiemil/eoyaml/YamlNode.java
index 6f2d803..a4e5690 100644
--- a/src/main/java/com/amihaiemil/eoyaml/YamlNode.java
+++ b/src/main/java/com/amihaiemil/eoyaml/YamlNode.java
@@ -38,12 +38,6 @@ import com.amihaiemil.eoyaml.exceptions.YamlReadingException;
  */
 public interface YamlNode extends Comparable<YamlNode> {
 
-    /**
-     * Is this YamlNode empty?
-     * @return True or false.
-     */
-    boolean isEmpty();
-
     /**
      * Comment referring to this Node.
      * @return Comment. If there is no comment, it will return


Test patch:
diff --git a/src/test/java/com/amihaiemil/eoyaml/ReadLiteralBlockScalarTest.java b/src/test/java/com/amihaiemil/eoyaml/ReadLiteralBlockScalarTest.java
index a0fbc6f..f12284f 100644
--- a/src/test/java/com/amihaiemil/eoyaml/ReadLiteralBlockScalarTest.java
+++ b/src/test/java/com/amihaiemil/eoyaml/ReadLiteralBlockScalarTest.java
@@ -27,8 +27,12 @@
  */
 package com.amihaiemil.eoyaml;
 
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.util.*;
 
+import org.apache.commons.io.IOUtils;
 import org.hamcrest.MatcherAssert;
 import org.hamcrest.Matchers;
 import org.junit.Test;
@@ -206,4 +210,45 @@ public final class ReadLiteralBlockScalarTest {
             )
         );
     }
+
+    /**
+     * Prints a block scalar from string.
+     * <br/>
+     * <a href="https://github.com/decorators-squad/eo-yaml/issues/480">
+     *     See bug</a>
+     * @throws IOException If something is wrong.
+     */
+    @Test
+    public void printsLiteralBlockScalarFromString() throws IOException {
+        final String input = readTestResource("issue_480_bug_printing.yml");
+
+        YamlMapping mapping = Yaml.createYamlInput(input, true)
+            .readYamlMapping();
+        MatcherAssert.assertThat(
+            mapping.string("test1"),
+            Matchers.equalTo("  this is a test message 1"
+                + System.lineSeparator())
+        );
+        MatcherAssert.assertThat(
+            mapping.string("test2"),
+            Matchers.equalTo("  this is a test message 2"
+                + System.lineSeparator())
+        );
+    }
+
+    /**
+     * Read a test resource file's contents.
+     * @param fileName File to read.
+     * @return File's contents as String.
+     * @throws FileNotFoundException If something is wrong.
+     * @throws IOException If something is wrong.
+     */
+    private String readTestResource(final String fileName)
+        throws FileNotFoundException, IOException {
+        return new String(
+            IOUtils.toByteArray(
+                new FileInputStream("src/test/resources/" + fileName)
+            )
+        );
+    }
 }
diff --git a/src/test/java/com/amihaiemil/eoyaml/ReadYamlStreamTest.java b/src/test/java/com/amihaiemil/eoyaml/ReadYamlStreamTest.java
index c2f1639..0b107c8 100644
--- a/src/test/java/com/amihaiemil/eoyaml/ReadYamlStreamTest.java
+++ b/src/test/java/com/amihaiemil/eoyaml/ReadYamlStreamTest.java
@@ -31,11 +31,8 @@ import org.hamcrest.MatcherAssert;
 import org.hamcrest.Matchers;
 import org.junit.Test;
 
-import java.io.File;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.stream.Collectors;
 
 /**
  * Unit tests for {@link ReadYamlStream}.
@@ -166,51 +163,4 @@ public final class ReadYamlStreamTest {
         );
         MatcherAssert.assertThat(stream.values(), Matchers.iterableWithSize(3));
     }
-
-    /**
-     * YamlStream should print the comment only once for each document/first
-     * node (in case doc starter is missing).
-     * <br/>
-     * Related to bug: <a href="https://github.com/decorators-squad/eo-yaml/issues/473">#473</a>.
-     * @throws IOException When something goes wrong.
-     */
-    @Test
-    public void shouldPrintTheFirstNodeOrDocumentCommentOnce()
-        throws IOException {
-        YamlStream yamlStream = Yaml
-            .createYamlInput(new File("src/test/resources"
-                + "/streamWithDocumentComments.yml"))
-            .readYamlStream();
-
-        List<YamlNode> collect = yamlStream.collect(Collectors.toList());
-
-        MatcherAssert.assertThat(collect, Matchers.iterableWithSize(4));
-
-        MatcherAssert.assertThat(
-            collect.get(0).comment().value(),
-            Matchers.isEmptyString()
-        );
-        MatcherAssert.assertThat(
-            collect.get(0).asMapping()
-                .value("architect")
-                .comment()
-                .value(),
-            Matchers.equalTo("Node comment")
-        );
-
-        MatcherAssert.assertThat(
-            collect.get(1).comment().value(),
-            Matchers.equalTo("Second whole document comment")
-        );
-
-        MatcherAssert.assertThat(
-            collect.get(2).comment().value(),
-            Matchers.equalTo("Third whole document trimmed")
-        );
-
-        MatcherAssert.assertThat(
-            collect.get(3).comment().value(),
-            Matchers.isEmptyString()
-        );
-    }
 }
diff --git a/src/test/resources/issue_480_bug_printing.yml b/src/test/resources/issue_480_bug_printing.yml
new file mode 100644
index 0000000..960c164
--- /dev/null
+++ b/src/test/resources/issue_480_bug_printing.yml
@@ -0,0 +1,5 @@
+test1: |
+    this is a test message 1
+test2: |
+    this is a test message 2
+
diff --git a/src/test/resources/streamWithDocumentComments.yml b/src/test/resources/streamWithDocumentComments.yml
deleted file mode 100644
index 8dfd091..0000000
--- a/src/test/resources/streamWithDocumentComments.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-#Node comment
-architect: mihai
-developers:
-  - rultor
-  - salikjan
-  - sherif
-name: eo-yaml
-
-
-
-#Second whole document comment
-
-
-
----
-architect: vlad
-developers:
-  - andrei
-name: eo-json-impl
-#Third whole document trimmed
----
-architect: felicia
-developer: sara
-name: docker-java-api
----
-architect: john
-developer: dan
-name: docker-java-api