Repository: decorators-squad/eo-yaml
Pull request #228 (2020-03-24 21:16:52)

Finished implementing building of Scalars
PR for #227 

- implemented building of both folded and literal block scalars
- added unit tests
- left a few TODOs in the code


Patch:
diff --git a/src/main/java/com/amihaiemil/eoyaml/ComparableScalar.java b/src/main/java/com/amihaiemil/eoyaml/ComparableScalar.java
index d20f0d7..5b4b664 100644
--- a/src/main/java/com/amihaiemil/eoyaml/ComparableScalar.java
+++ b/src/main/java/com/amihaiemil/eoyaml/ComparableScalar.java
@@ -33,7 +33,11 @@ package com.amihaiemil.eoyaml;
  * These methods should be default methods on the interface,
  * but we are not allowed to have default implementations of java.lang.Object
  * methods.
- *
+ * @todo #227:30min Override the toString() method in each concrete type of
+ *  Scalar to print it in context. At the moment calling toString() on a
+ *  Scalar doesn't print a valid YAML. The printed YAML should contain a
+ *  Start Marker, followed by the proper scalar mark and finally the End
+ *  Marker.
  * @author Mihai Andronache (amihaiemil@gmail.com)
  * @version $Id$
  * @since 3.1.3
diff --git a/src/main/java/com/amihaiemil/eoyaml/ReadFoldedBlockScalar.java b/src/main/java/com/amihaiemil/eoyaml/ReadFoldedBlockScalar.java
index f214521..a01cb6f 100644
--- a/src/main/java/com/amihaiemil/eoyaml/ReadFoldedBlockScalar.java
+++ b/src/main/java/com/amihaiemil/eoyaml/ReadFoldedBlockScalar.java
@@ -41,8 +41,6 @@ package com.amihaiemil.eoyaml;
  * @author Sherif Waly (sherifwaly95@gmail.com)
  * @version $Id$
  * @since 1.0.2
- * @todo #109:30m/DEV Refactor the implementation of value() and indent()
- *  methods.
  */
 final class ReadFoldedBlockScalar extends ComparableScalar {
 
diff --git a/src/main/java/com/amihaiemil/eoyaml/RtYamlScalarBuilder.java b/src/main/java/com/amihaiemil/eoyaml/RtYamlScalarBuilder.java
index 5175126..3c0876b 100644
--- a/src/main/java/com/amihaiemil/eoyaml/RtYamlScalarBuilder.java
+++ b/src/main/java/com/amihaiemil/eoyaml/RtYamlScalarBuilder.java
@@ -37,11 +37,6 @@ import java.util.stream.Collectors;
  * @author Mihai Andronache (amihaiemil@gmail.com)
  * @version $Id$
  * @since 4.0.0
- * @todo #221:30min Finish the implementation of the methods in this interface.
- *  Figure out how to implement literal and built block scalars, also in the
- *  the context of indenting/printing a bigger YAML node, as well as printing
- *  only that single scalar. Maybe we will need a class called YamlPrinter,
- *  that looks for the type of the node to print.
  */
 final class RtYamlScalarBuilder implements YamlScalarBuilder {
 
@@ -83,11 +78,93 @@ final class RtYamlScalarBuilder implements YamlScalarBuilder {
 
     @Override
     public Scalar buildFoldedBlockScalar() {
-        throw new UnsupportedOperationException("Not yet implemented.");
+        return new BuiltBlockScalar(this.lines, Boolean.TRUE);
     }
 
     @Override
     public Scalar buildLiteralBlockScalar() {
-        throw new UnsupportedOperationException("Not yet implemented.");
+        return new BuiltBlockScalar(this.lines, Boolean.FALSE);
+    }
+
+    /**
+     * A built block Scalar. It can be folded or literal.
+     * @author Mihai Andronache (amihaiemil@gmail.com)
+     * @version $Id$
+     * @since 4.0.0
+     */
+    static class BuiltBlockScalar extends ComparableScalar {
+
+        /**
+         * Lines of this scalar.
+         */
+        private final List<String> lines;
+
+        /**
+         * Folded or not (literal)?
+         */
+        private final boolean folded;
+
+        /**
+         * Ctor.
+         * @param lines Given string lines.
+         * @param folded Folded or not (literal).
+         */
+        BuiltBlockScalar(final List<String> lines, final boolean folded) {
+            this.lines = lines;
+            this.folded = folded;
+        }
+
+        @Override
+        public String value() {
+            final String value;
+            if(this.folded) {
+                value = this.lines.stream().map(
+                    line -> line.replaceAll(System.lineSeparator(), " ")
+                ).collect(Collectors.joining(" "));
+            } else {
+                value = this.lines.stream().collect(
+                    Collectors.joining(System.lineSeparator())
+                );
+            }
+            return value;
+        }
+
+        /**
+         * Indent this block scalar. When indenting/printing, we're going to
+         * separate the lines.
+         * @param indentation Number of preceding spaces of each line.
+         * @return Indented Scalar.
+         */
+        public String indent(final int indentation) {
+            int spaces = indentation;
+            StringBuilder print = new StringBuilder();
+            StringBuilder alignment = new StringBuilder();
+            while (spaces > 0) {
+                alignment.append(" ");
+                spaces--;
+            }
+            for(int idx = 0; idx < this.lines.size(); idx++) {
+                final String line = this.lines.get(idx);
+                if(line.contains(System.lineSeparator())) {
+                    final String[] hardcodedNewLines = line.split(
+                        System.lineSeparator()
+                    );
+                    for(final String subline : hardcodedNewLines) {
+                        print
+                            .append(alignment)
+                            .append(subline)
+                            .append(System.lineSeparator());
+                    }
+                } else {
+                    print
+                        .append(alignment)
+                        .append(line);
+                    if (idx < this.lines.size() - 1) {
+                        print.append(System.lineSeparator());
+                    }
+                }
+            }
+            return print.toString();
+        }
     }
 }
diff --git a/src/main/java/com/amihaiemil/eoyaml/Scalar.java b/src/main/java/com/amihaiemil/eoyaml/Scalar.java
index 3d57c63..70556c4 100644
--- a/src/main/java/com/amihaiemil/eoyaml/Scalar.java
+++ b/src/main/java/com/amihaiemil/eoyaml/Scalar.java
@@ -59,8 +59,7 @@ public interface Scalar extends YamlNode {
     }
 
     /**
-     * Indent this scalar. This method is supposed to be default and applicable
-     * to any kind of scalar.
+     * Indent this scalar.
      * @param indentation Number of preceding spaces of each line.
      * @return Indented Scalar.
      */
diff --git a/src/main/java/com/amihaiemil/eoyaml/YamlMapping.java b/src/main/java/com/amihaiemil/eoyaml/YamlMapping.java
index aa13cde..27fbb95 100644
--- a/src/main/java/com/amihaiemil/eoyaml/YamlMapping.java
+++ b/src/main/java/com/amihaiemil/eoyaml/YamlMapping.java
@@ -109,6 +109,9 @@ public interface YamlMapping extends YamlNode {
      * Indent this YamlMapping. This is a default method since indentation
      * logic should be identical for any kind of YamlMapping, regardless of
      * its implementation.
+     * @todo #227:30min Modify this method in order to properly indent Scalar
+     *  nodes. They are of multiple types (plain, folded, literal) and their
+     *  indentation differs. Don't forget to add unit tests.
      * @param indentation Indentation to start with. Usually, it's 0, since we
      *  don't want to have spaces at the beginning. But in the case of nested
      *  YamlNodes, this value may be greater than 0.


Test patch:
diff --git a/src/test/java/com/amihaiemil/eoyaml/BuiltBlockScalarTest.java b/src/test/java/com/amihaiemil/eoyaml/BuiltBlockScalarTest.java
new file mode 100644
index 0000000..d33d028
--- /dev/null
+++ b/src/test/java/com/amihaiemil/eoyaml/BuiltBlockScalarTest.java
@@ -0,0 +1,127 @@
+/**
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for
+ * {@link com.amihaiemil.eoyaml.RtYamlScalarBuilder.BuiltBlockScalar}.
+ * @author Mihai Andronache (amihaiemil@gmail.com)
+ * @version $Id$
+ * @since 4.0.0
+ */
+public final class BuiltBlockScalarTest {
+
+    /**
+     * Indentation works when the user did not hardcode NewLines
+     * in the provided strings.
+     *
+     * Indentation should work the same for both folded and literal
+     * block scalars.
+     */
+    @Test
+    public void indentsProperlyNoHardcodedNewLines() {
+        final List<String> lines = new ArrayList<>();
+        lines.add("line1");
+        lines.add("line2");
+        lines.add("line3");
+        final Scalar folded = new RtYamlScalarBuilder.BuiltBlockScalar(
+            lines, true
+        );
+        MatcherAssert.assertThat(
+            folded.indent(2),
+            Matchers.equalTo(
+                "  line1"
+                + System.lineSeparator()
+                + "  line2"
+                + System.lineSeparator()
+                + "  line3"
+            )
+        );
+        final Scalar literal = new RtYamlScalarBuilder.BuiltBlockScalar(
+            lines, false
+        );
+        MatcherAssert.assertThat(
+            literal.indent(2),
+            Matchers.equalTo(
+                "  line1"
+                + System.lineSeparator()
+                + "  line2"
+                + System.lineSeparator()
+                + "  line3"
+            )
+        );
+    }
+
+    /**
+     * Indentation works when the user hardcoded NewLines
+     * in the provided strings.
+     *
+     * Indentation should work the same for both folded and literal
+     * block scalars.
+     */
+    @Test
+    public void indentsProperlyWithHardcodedNewLines() {
+        final List<String> lines = new ArrayList<>();
+        lines.add("line1" + System.lineSeparator() + "line2");
+        lines.add("line3");
+        final Scalar folded = new RtYamlScalarBuilder.BuiltBlockScalar(
+            lines, true
+        );
+        MatcherAssert.assertThat(
+            folded.indent(2),
+            Matchers.equalTo(
+                "  line1"
+                    + System.lineSeparator()
+                    + "  line2"
+                    + System.lineSeparator()
+                    + "  line3"
+            )
+        );
+        final Scalar literal = new RtYamlScalarBuilder.BuiltBlockScalar(
+            lines, false
+        );
+        MatcherAssert.assertThat(
+            literal.indent(2),
+            Matchers.equalTo(
+                "  line1"
+                    + System.lineSeparator()
+                    + "  line2"
+                    + System.lineSeparator()
+                    + "  line3"
+            )
+        );
+    }
+
+}
diff --git a/src/test/java/com/amihaiemil/eoyaml/RtYamlScalarBuilderTest.java b/src/test/java/com/amihaiemil/eoyaml/RtYamlScalarBuilderTest.java
index 1c05fde..233ca4f 100644
--- a/src/test/java/com/amihaiemil/eoyaml/RtYamlScalarBuilderTest.java
+++ b/src/test/java/com/amihaiemil/eoyaml/RtYamlScalarBuilderTest.java
@@ -109,4 +109,92 @@ public final class RtYamlScalarBuilderTest {
             Matchers.isEmptyString()
         );
     }
+
+    /**
+     * RtYamlScalarBuilder can build a folded block scalar
+     * on a single line of text.
+     */
+    @Test
+    public void buildsSingleLineFoldedBlockScalar() {
+        final Scalar scalar = new RtYamlScalarBuilder()
+            .addLine("value").buildFoldedBlockScalar();
+        MatcherAssert.assertThat(
+            scalar.value(),
+            Matchers.equalTo("value")
+        );
+    }
+
+    /**
+     * RtYamlScalarBuilder can build a literal block scalar
+     * on a single line of text.
+     */
+    @Test
+    public void buildsSingleLineLiteralBlockScalar() {
+        final Scalar scalar = new RtYamlScalarBuilder()
+            .addLine("value").buildLiteralBlockScalar();
+        MatcherAssert.assertThat(
+            scalar.value(),
+            Matchers.equalTo("value")
+        );
+    }
+
+    /**
+     * RtYamlScalarBuilder can build an empty folded block scalar.
+     */
+    @Test
+    public void buildsEmptyFoldedBlockScalar() {
+        final Scalar scalar = new RtYamlScalarBuilder()
+            .buildFoldedBlockScalar();
+        MatcherAssert.assertThat(
+            scalar.value(),
+            Matchers.isEmptyString()
+        );
+    }
+
+    /**
+     * RtYamlScalarBuilder can build an empty literal block scalar.
+     */
+    @Test
+    public void buildsEmptyLiteralBlockScalar() {
+        final Scalar scalar = new RtYamlScalarBuilder()
+            .buildLiteralBlockScalar();
+        MatcherAssert.assertThat(
+            scalar.value(),
+            Matchers.isEmptyString()
+        );
+    }
+
+    /**
+     * RtYamlScalarBuilder can build a folded block scalar with multiple lines.
+     * @checkstyle LineLength (30 lines)
+     */
+    @Test
+    public void buildsFoldedBlockScalarNoNewLines() {
+        final Scalar scalar = new RtYamlScalarBuilder()
+            .addLine("a folded scalar")
+            .addLine("that will be on the same line")
+            .buildPlainScalar();
+        MatcherAssert.assertThat(
+            scalar.value(), Matchers.equalTo("a folded scalar that will be on the same line")
+        );
+    }
+
+    /**
+     * RtYamlScalarBuilder can build a folded block scalar with multiple lines.
+     * @checkstyle LineLength (30 lines)
+     */
+    @Test
+    public void buildsFoldedBlockScalarRemovesAddedNewLines() {
+        final Scalar scalar = new RtYamlScalarBuilder()
+            .addLine("a folded block scalar")
+            .addLine("that will be" + System.lineSeparator() + "on the same line.")
+            .addLine("Its value will be on a single line because NEWLINE is omitted.")
+            .buildFoldedBlockScalar();
+        MatcherAssert.assertThat(
+            scalar.value(),
+            Matchers.equalTo(
+            "a folded block scalar that will be on the same line. Its value will be on a single line because NEWLINE is omitted."
+            )
+        );
+    }
 }