Repository: decorators-squad/eo-yaml (java)

Issue:
Implement Scalar Readers
At the moment, it is not possible to read a single Scalar of any kind, even though the following YAMLs are valid:

```yaml
---
somePlainScalar
...
```

```yaml
---
>
 a YAML Document containing
 only a Folded Block
 Scalar value
...
```

```yaml
---
|
  only
  one literal
  block scalar
...
```

We should have readers for these, following the pattern of mappings and sequences. 

Pull request description:
#224 scalar readers implemented
Fixes #224 
Added scalar readers and unit tests for all cases.

Patch:
diff --git a/src/main/java/com/amihaiemil/eoyaml/NoScalarMarkers.java b/src/main/java/com/amihaiemil/eoyaml/NoScalarMarkers.java
new file mode 100644
index 0000000..911e066
--- /dev/null
+++ b/src/main/java/com/amihaiemil/eoyaml/NoScalarMarkers.java
@@ -0,0 +1,104 @@
+/**
+ * Copyright (c) 2016-2020, Mihai Emil Andronache
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+package com.amihaiemil.eoyaml;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * When reading a single Scalar, the first of the given lines
+ * dictates the type of the scalar: '>' is folded, '|' is literal etc.
+ *
+ * The lines will be passed altogether to the read scalar, so we have
+ * to ignore these markers as they are not part of the scalar itself.
+ * This class can be used as follows:
+ * <pre>
+ *   YamlLines noScalarMarkers = new NoScalarMarkers(
+ *     new AllYamlLines(lines)
+ *   );
+ * </pre>
+ * @author Mihai Andronache (amihaiemil@gmail.com)
+ * @version $Id$
+ * @since 4.0.0
+ */
+final class NoScalarMarkers implements YamlLines {
+
+    /**
+     * YamlLines.
+     */
+    private final YamlLines yamlLines;
+
+    /**
+     * Ctor.
+     * @param yamlLines The Yaml lines.
+     */
+    NoScalarMarkers(final YamlLines yamlLines) {
+        this.yamlLines = yamlLines;
+    }
+
+    /**
+     * Returns an iterator over these Yaml lines.
+     * It ignores the Scalar start markers ('>', '|' etc).
+     * @return Iterator over these yaml lines.
+     */
+    @Override
+    public Iterator<YamlLine> iterator() {
+        Iterator<YamlLine> iterator = this.yamlLines.iterator();
+        if (iterator.hasNext()) {
+            final List<YamlLine> noScalarMarkers = new ArrayList<>();
+            while (iterator.hasNext()) {
+                final YamlLine current = iterator.next();
+                final String line = current.trimmed();
+                if (line.endsWith(">") || line.endsWith("|")) {
+                    continue;
+                } else {
+                    noScalarMarkers.add(current);
+                }
+            }
+            iterator = noScalarMarkers.iterator();
+        }
+        return iterator;
+    }
+
+    @Override
+    public Collection<YamlLine> lines() {
+        return this.yamlLines.lines();
+    }
+
+    @Override
+    public AllYamlLines nested(final int after) {
+        return this.yamlLines.nested(after);
+    }
+
+    @Override
+    public YamlNode toYamlNode(final YamlLine prev) {
+        return this.yamlLines.toYamlNode(prev);
+    }
+}
diff --git a/src/main/java/com/amihaiemil/eoyaml/ReadFoldedBlockScalar.java b/src/main/java/com/amihaiemil/eoyaml/ReadFoldedBlockScalar.java
index ff62b58..4cf8932 100644
--- a/src/main/java/com/amihaiemil/eoyaml/ReadFoldedBlockScalar.java
+++ b/src/main/java/com/amihaiemil/eoyaml/ReadFoldedBlockScalar.java
@@ -27,7 +27,6 @@
  */
 package com.amihaiemil.eoyaml;
 
-
 /**
  * Read Yaml folded block Scalar. This is a Scalar spanning multiple lines.
  * This Scalar's newlines will be ignored ("folded"), the scalar's value
diff --git a/src/main/java/com/amihaiemil/eoyaml/ReadLiteralBlockScalar.java b/src/main/java/com/amihaiemil/eoyaml/ReadLiteralBlockScalar.java
index 79e6455..6dcff1d 100644
--- a/src/main/java/com/amihaiemil/eoyaml/ReadLiteralBlockScalar.java
+++ b/src/main/java/com/amihaiemil/eoyaml/ReadLiteralBlockScalar.java
@@ -85,7 +85,9 @@ final class ReadLiteralBlockScalar extends BaseScalar {
             builder.append(line.trimmed());
             builder.append(System.lineSeparator());
         }
-        builder.delete(builder.length()-1, builder.length());
+        if(builder.length() > 0) {
+            builder.delete(builder.length() - 1, builder.length());
+        }
         return builder.toString();
     }
 
diff --git a/src/main/java/com/amihaiemil/eoyaml/RtYamlInput.java b/src/main/java/com/amihaiemil/eoyaml/RtYamlInput.java
index 0a13eaa..1483790 100644
--- a/src/main/java/com/amihaiemil/eoyaml/RtYamlInput.java
+++ b/src/main/java/com/amihaiemil/eoyaml/RtYamlInput.java
@@ -32,6 +32,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 
 /**
@@ -70,6 +71,42 @@ final class RtYamlInput implements YamlInput {
         return new ReadYamlStream(this.readInput());
     }
 
+    @Override
+    public Scalar readPlainScalar() throws IOException {
+        final ReadPlainScalarValue read;
+        final Iterator<YamlLine> iterator = new NoDirectivesOrMarkers(
+            this.readInput()
+        ).iterator();
+        if(!iterator.hasNext()) {
+            read = new ReadPlainScalarValue(new YamlLine.NullYamlLine());
+        } else {
+            read = new ReadPlainScalarValue(iterator.next());
+        }
+        return read;
+    }
+
+    @Override
+    public Scalar readFoldedBlockScalar() throws IOException {
+        return new ReadFoldedBlockScalar(
+            new NoScalarMarkers(
+                new NoDirectivesOrMarkers(
+                    this.readInput()
+                )
+            )
+        );
+    }
+
+    @Override
+    public Scalar readLiteralBlockScalar() throws IOException {
+        return new ReadLiteralBlockScalar(
+            new NoScalarMarkers(
+                new NoDirectivesOrMarkers(
+                    this.readInput()
+                )
+            )
+        );
+    }
+
     /**
      * Read the input's lines.
      * @return All read YamlLines
diff --git a/src/main/java/com/amihaiemil/eoyaml/RtYamlScalarBuilder.java b/src/main/java/com/amihaiemil/eoyaml/RtYamlScalarBuilder.java
index a9a21d1..5af6aa9 100644
--- a/src/main/java/com/amihaiemil/eoyaml/RtYamlScalarBuilder.java
+++ b/src/main/java/com/amihaiemil/eoyaml/RtYamlScalarBuilder.java
@@ -114,6 +114,15 @@ final class RtYamlScalarBuilder implements YamlScalarBuilder {
             this.folded = folded;
         }
 
+        /**
+         * Return the value of this block scalar.
+         * @todo #224:30min This method is not correct in the case of folded
+         *  scalar. When returning the value we should indeed fold the
+         *  new lines, with the exception that we should preserve
+         *  the blank lines and new lines of more inner indented lines.
+         *  See how ReadFoldedBlockScalar.value() is implemented.
+         * @return String value.
+         */
         @Override
         public String value() {
             final String value;
diff --git a/src/main/java/com/amihaiemil/eoyaml/YamlInput.java b/src/main/java/com/amihaiemil/eoyaml/YamlInput.java
index f6eacf4..ffe2038 100644
--- a/src/main/java/com/amihaiemil/eoyaml/YamlInput.java
+++ b/src/main/java/com/amihaiemil/eoyaml/YamlInput.java
@@ -58,4 +58,45 @@ public interface YamlInput {
      */
     YamlStream readYamlStream() throws IOException;
 
+    /**
+     * Read the given input as a plain scalar. e.g.
+     * <pre>
+     * ---
+     * lonelyScalar
+     * ...
+     * </pre>
+     * @return Read Scalar.
+     * @throws IOException if the input cannot be read for some reason
+     */
+    Scalar readPlainScalar() throws IOException;
+
+    /**
+     * Read the given input as a folded block scalar. e.g.
+     * <pre>
+     * ---
+     * >
+     *   Long scalar which
+     *   has been folded for
+     *   readability
+     * ...
+     * </pre>
+     * @return Read Scalar.
+     * @throws IOException if the input cannot be read for some reason
+     */
+    Scalar readFoldedBlockScalar() throws IOException;
+
+    /**
+     * Read the given input as a literal block scalar. e.g.
+     * <pre>
+     * ---
+     * |
+     *   line1
+     *   line2
+     *   line3
+     * ...
+     * </pre>
+     * @return Read Scalar.
+     * @throws IOException if the input cannot be read for some reason
+     */
+    Scalar readLiteralBlockScalar() throws IOException;
 }
diff --git a/src/main/java/com/amihaiemil/eoyaml/YamlLine.java b/src/main/java/com/amihaiemil/eoyaml/YamlLine.java
index 9cf49fd..d93fa6f 100644
--- a/src/main/java/com/amihaiemil/eoyaml/YamlLine.java
+++ b/src/main/java/com/amihaiemil/eoyaml/YamlLine.java
@@ -73,7 +73,7 @@ interface YamlLine extends Comparable<YamlLine> {
 
         @Override
         public int number() {
-            return -1;
+            return 0;
         }
 
         @Override
diff --git a/src/main/java/com/amihaiemil/eoyaml/YamlLines.java b/src/main/java/com/amihaiemil/eoyaml/YamlLines.java
index bf47fc7..489491b 100644
--- a/src/main/java/com/amihaiemil/eoyaml/YamlLines.java
+++ b/src/main/java/com/amihaiemil/eoyaml/YamlLines.java
@@ -50,8 +50,6 @@ interface YamlLines extends Iterable<YamlLine> {
      * Turn these lines into a YamlNode.
      * @param prev Previous YamlLine
      * @return YamlNode
-     * @todo #107:30min/DEV Add more tests to cover all the nested node
-     *  possibilities.
      */
     YamlNode toYamlNode(final YamlLine prev);
 


Test patch:
diff --git a/src/test/java/com/amihaiemil/eoyaml/NoScalarMarkersTest.java b/src/test/java/com/amihaiemil/eoyaml/NoScalarMarkersTest.java
new file mode 100644
index 0000000..732af88
--- /dev/null
+++ b/src/test/java/com/amihaiemil/eoyaml/NoScalarMarkersTest.java
@@ -0,0 +1,179 @@
+/**
+ * Copyright (c) 2016-2020, Mihai Emil Andronache
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+package com.amihaiemil.eoyaml;
+
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Unit tests for {@link NoScalarMarkers}.
+ * @author Mihai Andronache (amihaiemil@gmail.com)
+ * @version $Id$
+ * @since 4.0.0
+ */
+public final class NoScalarMarkersTest {
+    /**
+     * {@link NoScalarMarkers} can return the encapsulated lines.
+     */
+    @Test
+    @SuppressWarnings("unchecked")
+    public void returnsLines() {
+        final YamlLines lines = Mockito.mock(YamlLines.class);
+        final Collection<YamlLine> collection = Mockito.mock(Collection.class);
+        Mockito.when(lines.lines()).thenReturn(collection);
+        MatcherAssert.assertThat(
+            new NoScalarMarkers(lines).lines(),
+            Matchers.is(collection)
+        );
+    }
+
+    /**
+     * {@link NoScalarMarkers} can turn itself into YamlNode.
+     */
+    @Test
+    public void turnsIntoYamlNode() {
+        final YamlLines lines = Mockito.mock(YamlLines.class);
+        final YamlLine prev = Mockito.mock(YamlLine.class);
+        final YamlNode result = Mockito.mock(YamlNode.class);
+        Mockito.when(lines.toYamlNode(prev)).thenReturn(result);
+        MatcherAssert.assertThat(
+            new NoScalarMarkers(lines).toYamlNode(prev),
+            Matchers.is(result)
+        );
+    }
+
+    /**
+     * {@link NoScalarMarkers} should ignore the '|' marker.
+     */
+    @Test
+    public void ignoresLiteralScalarMarker() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("|", 0));
+        lines.add(new RtYamlLine("first", 1));
+        lines.add(new RtYamlLine("second", 2));
+
+        final YamlLines ndmLines = new NoScalarMarkers(
+            new AllYamlLines(lines)
+        );
+        MatcherAssert.assertThat(
+            ndmLines,
+            Matchers.iterableWithSize(2)
+        );
+        final Iterator<YamlLine> iterator = ndmLines.iterator();
+        MatcherAssert.assertThat(
+            iterator.next().trimmed(),
+            Matchers.equalTo("first")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().trimmed(),
+            Matchers.equalTo("second")
+        );
+    }
+
+
+    /**
+     * {@link NoScalarMarkers} should ignore the '>' marker.
+     */
+    @Test
+    public void ignoresFoldedScalarMarker() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine(">", 0));
+        lines.add(new RtYamlLine("first", 1));
+        lines.add(new RtYamlLine("second", 2));
+
+        final YamlLines ndmLines = new NoScalarMarkers(
+            new AllYamlLines(lines)
+        );
+        MatcherAssert.assertThat(
+            ndmLines,
+            Matchers.iterableWithSize(2)
+        );
+        final Iterator<YamlLine> iterator = ndmLines.iterator();
+        MatcherAssert.assertThat(
+            iterator.next().trimmed(),
+            Matchers.equalTo("first")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().trimmed(),
+            Matchers.equalTo("second")
+        );
+    }
+
+    /**
+     * {@link NoScalarMarkers} iterates over all the lines, since
+     * there are actually no directives to ignore.
+     */
+    @Test
+    public void noDirectivesAndMarkersToIgnore() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("first", 0));
+        lines.add(new RtYamlLine("second", 1));
+        lines.add(new RtYamlLine("third", 2));
+        final YamlLines ndmLines = new NoScalarMarkers(
+            new AllYamlLines(lines)
+        );
+        MatcherAssert.assertThat(
+            ndmLines,
+            Matchers.iterableWithSize(3)
+        );
+        final Iterator<YamlLine> iterator = ndmLines.iterator();
+        MatcherAssert.assertThat(
+            iterator.next().trimmed(),
+            Matchers.equalTo("first")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().trimmed(),
+            Matchers.equalTo("second")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().trimmed(),
+            Matchers.equalTo("third")
+        );
+    }
+
+    /**
+     * {@link NoScalarMarkers} works with no lines.
+     */
+    @Test
+    public void iteratesEmptyLines() {
+        final YamlLines ndmLines = new NoScalarMarkers(
+            new AllYamlLines(new ArrayList<>())
+        );
+        MatcherAssert.assertThat(
+            ndmLines,
+            Matchers.iterableWithSize(0)
+        );
+    }
+}
diff --git a/src/test/java/com/amihaiemil/eoyaml/RtYamlInputTest.java b/src/test/java/com/amihaiemil/eoyaml/RtYamlInputTest.java
index 5fb359b..c5c1ed2 100644
--- a/src/test/java/com/amihaiemil/eoyaml/RtYamlInputTest.java
+++ b/src/test/java/com/amihaiemil/eoyaml/RtYamlInputTest.java
@@ -501,6 +501,115 @@ public final class RtYamlInputTest {
         );
     }
 
+    /**
+     * RtYamlInput can read empty scalars.
+     * @throws Exception If something goes wrong.
+     */
+    @Test
+    public void readsEmptyScalars() throws Exception {
+        MatcherAssert.assertThat(
+            Yaml.createYamlInput("").readPlainScalar().value(),
+            Matchers.isEmptyString()
+        );
+        MatcherAssert.assertThat(
+            Yaml.createYamlInput("").readFoldedBlockScalar().value(),
+            Matchers.isEmptyString()
+        );
+        MatcherAssert.assertThat(
+            Yaml.createYamlInput("").readLiteralBlockScalar().value(),
+            Matchers.isEmptyString()
+        );
+        MatcherAssert.assertThat(
+            Yaml.createYamlInput("---" + System.lineSeparator() + "...")
+                .readPlainScalar().value(),
+            Matchers.isEmptyString()
+        );
+        MatcherAssert.assertThat(
+            Yaml.createYamlInput("---" + System.lineSeparator() + " ...")
+                .readFoldedBlockScalar().value(),
+            Matchers.isEmptyString()
+        );
+        MatcherAssert.assertThat(
+            Yaml.createYamlInput("---" + System.lineSeparator() + "...")
+                .readLiteralBlockScalar().value(),
+            Matchers.isEmptyString()
+        );
+    }
+
+    /**
+     * RtYamlInput can read a plain scalar.
+     * @throws Exception If something goes wrong.
+     */
+    @Test
+    public void readsPlainScalar() throws Exception {
+        MatcherAssert.assertThat(
+            Yaml.createYamlInput("scalar").readPlainScalar().value(),
+            Matchers.equalTo("scalar")
+        );
+        MatcherAssert.assertThat(
+            Yaml.createYamlInput(
+                "---"
+              + System.lineSeparator()
+              + "scalar"
+              + System.lineSeparator()
+              + "..."
+            ).readPlainScalar().value(),
+            Matchers.equalTo("scalar")
+        );
+    }
+
+    /**
+     * RtYamlInput can read a folded block scalar.
+     * @throws Exception If something goes wrong.
+     */
+    @Test
+    public void readsFoldedBlockScalar() throws Exception {
+        MatcherAssert.assertThat(
+            Yaml.createYamlInput(
+                ">" + System.lineSeparator()
+              + "some folded" + System.lineSeparator()
+              + "block scalar"
+            ).readFoldedBlockScalar().value(),
+            Matchers.equalTo("some folded block scalar")
+        );
+        MatcherAssert.assertThat(
+            Yaml.createYamlInput(
+                "---" + System.lineSeparator()
+              + ">" + System.lineSeparator()
+              + "some folded" + System.lineSeparator()
+              + "block scalar" + System.lineSeparator()
+              + "..."
+            ).readFoldedBlockScalar().value(),
+            Matchers.equalTo("some folded block scalar")
+        );
+    }
+
+    /**
+     * RtYamlInput can read a folded block scalar.
+     * @throws Exception If something goes wrong.
+     */
+    @Test
+    public void readsLiteralBlockScalar() throws Exception {
+        MatcherAssert.assertThat(
+            Yaml.createYamlInput(
+                "|" + System.lineSeparator()
+              + "line1" + System.lineSeparator()
+              + "line2"
+            ).readLiteralBlockScalar().value(),
+            Matchers.equalTo("line1" + System.lineSeparator() + "line2")
+        );
+        MatcherAssert.assertThat(
+            Yaml.createYamlInput(
+                "---" + System.lineSeparator()
+              + "|" + System.lineSeparator()
+              + "line1" + System.lineSeparator()
+              + "line2" + System.lineSeparator()
+              + "..."
+            ).readLiteralBlockScalar().value(),
+            Matchers.equalTo("line1" + System.lineSeparator() + "line2")
+        );
+    }
+
     /**
      * Read a test resource file's contents.
      * @param fileName File to read.