Repository: decorators-squad/eo-yaml
Pull request #286 (2020-04-05 12:14:27)

Implemented ReadYamlMapping.comment()
For #272 
The comment above a YamlMapping can now be read via the ``comment()`` method


Patch:
diff --git a/src/main/java/com/amihaiemil/eoyaml/Backwards.java b/src/main/java/com/amihaiemil/eoyaml/Backwards.java
new file mode 100644
index 0000000..cb174ed
--- /dev/null
+++ b/src/main/java/com/amihaiemil/eoyaml/Backwards.java
@@ -0,0 +1,72 @@
+/**
+ * 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.*;
+
+/**
+ * YamlLines which are being iterated backwards.
+ * @author Mihai Andronache (amihaiemil@gmail.com)
+ * @version $Id$
+ * @since 4.2.0
+ */
+final class Backwards implements YamlLines {
+
+    /**
+     * YamlLines.
+     */
+    private final YamlLines lines;
+
+    /**
+     * Ctor.
+     * @param lines The Yaml lines.
+     */
+    Backwards(final YamlLines lines) {
+        this.lines = lines;
+    }
+
+    @Override
+    public Collection<YamlLine> original() {
+        return this.lines.original();
+    }
+
+    @Override
+    public YamlNode toYamlNode(final YamlLine prev) {
+        return this.lines.toYamlNode(prev);
+    }
+
+    @Override
+    public Iterator<YamlLine> iterator() {
+        final List<YamlLine> original = new ArrayList<>();
+        for(final YamlLine line : this.lines) {
+            original.add(line);
+        }
+        Collections.reverse(original);
+        return original.iterator();
+    }
+}
diff --git a/src/main/java/com/amihaiemil/eoyaml/FirstCommentFound.java b/src/main/java/com/amihaiemil/eoyaml/FirstCommentFound.java
new file mode 100644
index 0000000..b2d3961
--- /dev/null
+++ b/src/main/java/com/amihaiemil/eoyaml/FirstCommentFound.java
@@ -0,0 +1,89 @@
+/**
+ * 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;
+
+/**
+ * Iterate over the lines which are YAML comments (begin with "#") and break
+ * iteration when a non-comment line is found. In essence, this reads the lines
+ * of the first comment from a given YamlLines.
+ * @author Mihai Andronache (amihaiemil@gmail.com)
+ * @version $Id$
+ * @since 4.2.0
+ */
+final class FirstCommentFound implements YamlLines {
+
+    /**
+     * Lines where we look for the comment.
+     */
+    private final YamlLines lines;
+
+    /**
+     * Ctor.
+     * @param lines The Yaml lines where we look for the comment.
+     */
+    FirstCommentFound(final YamlLines lines) {
+        this.lines = lines;
+    }
+
+    /**
+     * Returns an iterator over the lines of the first found comment.
+     * @return Iterator over these yaml lines.
+     */
+    @Override
+    public Iterator<YamlLine> iterator() {
+        Iterator<YamlLine> iterator = this.lines.iterator();
+        if (iterator.hasNext()) {
+            final List<YamlLine> comment = new ArrayList<>();
+            while (iterator.hasNext()) {
+                YamlLine line = iterator.next();
+                if(line.trimmed().startsWith("#")) {
+                    comment.add(line);
+                } else {
+                    break;
+                }
+            }
+            iterator = comment.iterator();
+        }
+        return iterator;
+    }
+
+    @Override
+    public Collection<YamlLine> original() {
+        return this.lines.original();
+    }
+
+    @Override
+    public YamlNode toYamlNode(final YamlLine prev) {
+        return this.lines.toYamlNode(prev);
+    }
+}
diff --git a/src/main/java/com/amihaiemil/eoyaml/ReadComment.java b/src/main/java/com/amihaiemil/eoyaml/ReadComment.java
new file mode 100644
index 0000000..789ef69
--- /dev/null
+++ b/src/main/java/com/amihaiemil/eoyaml/ReadComment.java
@@ -0,0 +1,73 @@
+/**
+ * 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;
+
+/**
+ * A comment which has been read from somewhere.
+ * @author Mihai Andronache (amihaiemil@gmail.com)
+ * @version $Id$
+ * @since  4.2.0
+ */
+final class ReadComment implements Comment {
+
+    /**
+     * Lines of this comment.
+     */
+    private final YamlLines lines;
+
+    /**
+     * Node to which this comment refers.
+     */
+    private final YamlNode node;
+
+    /**
+     * Constructor.
+     * @param lines Lines of this comment.
+     * @param node Node to which it refers.
+     */
+    ReadComment(final YamlLines lines, final YamlNode node) {
+        this.lines = lines;
+        this.node = node;
+    }
+
+    @Override
+    public YamlNode yamlNode() {
+        return this.node;
+    }
+
+    @Override
+    public String value() {
+        final StringBuilder comment = new StringBuilder();
+        for(final YamlLine line : this.lines) {
+            comment
+                .append(line.trimmed().substring(1).trim())
+                .append(System.lineSeparator());
+        }
+        return comment.toString().trim();
+    }
+}
diff --git a/src/main/java/com/amihaiemil/eoyaml/ReadYamlMapping.java b/src/main/java/com/amihaiemil/eoyaml/ReadYamlMapping.java
index 5853b43..dd501b9 100644
--- a/src/main/java/com/amihaiemil/eoyaml/ReadYamlMapping.java
+++ b/src/main/java/com/amihaiemil/eoyaml/ReadYamlMapping.java
@@ -59,9 +59,14 @@ final class ReadYamlMapping extends BaseYamlMapping {
     private YamlLine previous;
 
     /**
-     * Lines read.
+     * Lines of this mapping also containing the comments.
      */
-    private YamlLines lines;
+    private YamlLines comments;
+
+    /**
+     * Only the significant lines of this YamlMapping.
+     */
+    private YamlLines significant;
 
     /**
      * Ctor.
@@ -78,7 +83,7 @@ final class ReadYamlMapping extends BaseYamlMapping {
      */
     ReadYamlMapping(final YamlLine previous, final AllYamlLines lines) {
         this.previous = previous;
-        this.lines = new SameIndentationLevel(
+        this.significant = new SameIndentationLevel(
             new WellIndented(
                 new Skip(
                     lines,
@@ -91,17 +96,40 @@ final class ReadYamlMapping extends BaseYamlMapping {
                 )
             )
         );
+        this.comments = new WellIndented(
+            new Skip(
+                lines,
+                line -> {
+                    final boolean skip;
+                    if(previous.number() < 0) {
+                        if(this.significant.iterator().hasNext()) {
+                            skip = line.number() >= this.significant
+                                    .iterator().next().number();
+                        } else {
+                            skip = false;
+                        }
+                    } else {
+                        skip = line.number() >= previous.number();
+                    }
+                    return skip;
+                },
+                line -> line.trimmed().startsWith("---"),
+                line -> line.trimmed().startsWith("..."),
+                line -> line.trimmed().startsWith("%"),
+                line -> line.trimmed().startsWith("!!")
+            )
+        );
     }
 
     @Override
     public Set<YamlNode> keys() {
         final Set<YamlNode> keys = new LinkedHashSet<>();
-        for (final YamlLine line : this.lines) {
+        for (final YamlLine line : this.significant) {
             final String trimmed = line.trimmed();
             if(trimmed.startsWith(":")) {
                 continue;
             } else if ("?".equals(trimmed)) {
-                keys.add(this.lines.toYamlNode(line));
+                keys.add(this.significant.toYamlNode(line));
             } else {
                 if(!trimmed.contains(":")) {
                     throw new YamlReadingException(
@@ -152,8 +180,14 @@ final class ReadYamlMapping extends BaseYamlMapping {
      */
     @Override
     public Comment comment() {
-        final Comment empty = new BuiltComment(this, "");
-        return empty;
+        return new ReadComment(
+            new FirstCommentFound(
+                new Backwards(
+                    this.comments
+                )
+            ),
+            this
+        );
     }
 
     @Override
@@ -227,13 +261,13 @@ final class ReadYamlMapping extends BaseYamlMapping {
      */
     private YamlNode valueOfStringKey(final String key) {
         YamlNode value = null;
-        for (final YamlLine line : this.lines) {
+        for (final YamlLine line : this.significant) {
             final String trimmed = line.trimmed();
             if(trimmed.endsWith(key + ":")
                 || trimmed.matches("^" + key + "\\:[ ]*\\>$")
                 || trimmed.matches("^" + key + "\\:[ ]*\\|$")
             ) {
-                value = this.lines.toYamlNode(line);
+                value = this.significant.toYamlNode(line);
             } else if(trimmed.startsWith(key + ":")
                 && trimmed.length() > 1
             ) {
@@ -251,19 +285,19 @@ final class ReadYamlMapping extends BaseYamlMapping {
      */
     private YamlNode valueOfNodeKey(final YamlNode key) {
         YamlNode value = null;
-        final Iterator<YamlLine> linesIt = this.lines.iterator();
+        final Iterator<YamlLine> linesIt = this.significant.iterator();
         while(linesIt.hasNext()) {
             final YamlLine line = linesIt.next();
             final String trimmed = line.trimmed();
             if("?".equals(trimmed)) {
-                final YamlNode keyNode = this.lines.toYamlNode(line);
+                final YamlNode keyNode = this.significant.toYamlNode(line);
                 if(keyNode.equals(key)) {
                     final YamlLine colonLine = linesIt.next();
                     if(":".equals(colonLine.trimmed())
                         || colonLine.trimmed().matches("^\\:[ ]*\\>$")
                         || colonLine.trimmed().matches("^\\:[ ]*\\|$")
                     ) {
-                        value = this.lines.toYamlNode(colonLine);
+                        value = this.significant.toYamlNode(colonLine);
                     } else if(colonLine.trimmed().startsWith(":")
                         && (colonLine.trimmed().length() > 1)
                     ){


Test patch:
diff --git a/src/test/java/com/amihaiemil/eoyaml/BackwardsTest.java b/src/test/java/com/amihaiemil/eoyaml/BackwardsTest.java
new file mode 100644
index 0000000..5fc605f
--- /dev/null
+++ b/src/test/java/com/amihaiemil/eoyaml/BackwardsTest.java
@@ -0,0 +1,133 @@
+/**
+ * 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 Backwards}.
+ * @author Mihai Andronache (amihaiemil@gmail.com)
+ * @version $Id$
+ * @since 4.2.0
+ */
+public final class BackwardsTest {
+
+    /**
+     * Backwards delegates the call to originals().
+     */
+    @Test
+    public void delegatesOriginals() {
+        final YamlLines initial = Mockito.mock(YamlLines.class);
+        final Collection<YamlLine> lines = Mockito.mock(Collection.class);
+        Mockito.when(initial.original()).thenReturn(lines);
+        MatcherAssert.assertThat(
+            new Backwards(initial).original(),
+            Matchers.is(lines)
+        );
+    }
+
+    /**
+     * Backwards delegates the call to toYamlNode().
+     */
+    @Test
+    public void delegatesToYamlNode() {
+        final YamlLines initial = Mockito.mock(YamlLines.class);
+        final YamlLine prev = Mockito.mock(YamlLine.class);
+        final YamlNode node = Mockito.mock(YamlNode.class);
+        Mockito.when(initial.toYamlNode(prev)).thenReturn(node);
+        MatcherAssert.assertThat(
+            new Backwards(initial).toYamlNode(prev),
+            Matchers.is(node)
+        );
+    }
+
+    /**
+     * Backwards can iterate over some empty lines.
+     */
+    @Test
+    public void iteratesEmptyLines() {
+        MatcherAssert.assertThat(
+            new Backwards(
+                new AllYamlLines(new ArrayList<>())
+            ),
+            Matchers.emptyIterable()
+        );
+    }
+
+    /**
+     * Backwards can iterate over one single line.
+     */
+    @Test
+    public void iteratesOneLines() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("one line", 0));
+        MatcherAssert.assertThat(
+            new Backwards(
+                new AllYamlLines(lines)
+            ),
+            Matchers.iterableWithSize(1)
+        );
+    }
+
+    /**
+     * Backwards can iterate over some lines.
+     */
+    @Test
+    public void iteratesLines() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("first line", 0));
+        lines.add(new RtYamlLine("second line", 1));
+        lines.add(new RtYamlLine("third line", 2));
+        final YamlLines back = new Backwards(new AllYamlLines(lines));
+        MatcherAssert.assertThat(
+            back,
+            Matchers.iterableWithSize(3)
+        );
+        final Iterator<YamlLine> backIt = back.iterator();
+        MatcherAssert.assertThat(
+            backIt.next().trimmed(),
+            Matchers.equalTo("third line")
+        );
+        MatcherAssert.assertThat(
+            backIt.next().trimmed(),
+            Matchers.equalTo("second line")
+        );
+        MatcherAssert.assertThat(
+            backIt.next().trimmed(),
+            Matchers.equalTo("first line")
+        );
+    }
+}
diff --git a/src/test/java/com/amihaiemil/eoyaml/FirstCommentFoundTest.java b/src/test/java/com/amihaiemil/eoyaml/FirstCommentFoundTest.java
new file mode 100644
index 0000000..ba0b175
--- /dev/null
+++ b/src/test/java/com/amihaiemil/eoyaml/FirstCommentFoundTest.java
@@ -0,0 +1,136 @@
+/**
+ * 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 FirstCommentFound}.
+ * @author Mihai Andronache (amihaiemil@gmail.com)
+ * @version $Id$
+ * @since 4.2.0
+ */
+public final class FirstCommentFoundTest {
+    /**
+     * FirstCommentFound delegates the call to originals().
+     */
+    @Test
+    public void delegatesOriginals() {
+        final YamlLines initial = Mockito.mock(YamlLines.class);
+        final Collection<YamlLine> lines = Mockito.mock(Collection.class);
+        Mockito.when(initial.original()).thenReturn(lines);
+        MatcherAssert.assertThat(
+            new FirstCommentFound(initial).original(),
+            Matchers.is(lines)
+        );
+    }
+
+    /**
+     * FirstCommentFound delegates the call to toYamlNode().
+     */
+    @Test
+    public void delegatesToYamlNode() {
+        final YamlLines initial = Mockito.mock(YamlLines.class);
+        final YamlLine prev = Mockito.mock(YamlLine.class);
+        final YamlNode node = Mockito.mock(YamlNode.class);
+        Mockito.when(initial.toYamlNode(prev)).thenReturn(node);
+        MatcherAssert.assertThat(
+            new FirstCommentFound(initial).toYamlNode(prev),
+            Matchers.is(node)
+        );
+    }
+
+    /**
+     * {@link FirstCommentFound} can work with empty lines.
+     */
+    @Test
+    public void worksWithEmptyLines() {
+        MatcherAssert.assertThat(
+            new FirstCommentFound(
+                new AllYamlLines(new ArrayList<>())
+            ),
+            Matchers.emptyIterable()
+        );
+    }
+
+    /**
+     * {@link FirstCommentFound} returns the lines of the first
+     * comment in the document.
+     */
+    @Test
+    public void findsFirstComment() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("# YAML document for", 0));
+        lines.add(new RtYamlLine("# test purposes: ", 1));
+        lines.add(new RtYamlLine("first: somethingElse", 2));
+        lines.add(new RtYamlLine("second: ", 3));
+        lines.add(new RtYamlLine("  fourth: some", 4));
+        lines.add(new RtYamlLine("  fifth: values", 5));
+        lines.add(new RtYamlLine("third: something", 6));
+        final YamlLines comment = new FirstCommentFound(
+            new AllYamlLines(lines)
+        );
+        MatcherAssert.assertThat(comment, Matchers.iterableWithSize(2));
+        final Iterator<YamlLine> commIt = comment.iterator();
+        MatcherAssert.assertThat(
+            commIt.next().trimmed(),
+            Matchers.equalTo("# YAML document for")
+        );
+        MatcherAssert.assertThat(
+            commIt.next().trimmed(),
+            Matchers.equalTo("# test purposes:")
+        );
+    }
+
+    /**
+     * {@link FirstCommentFound} returns no lines since there is
+     * no comment at the beginning.
+     */
+    @Test
+    public void noFirstComment() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("first: somethingElse", 0));
+        lines.add(new RtYamlLine("# second map", 1));
+        lines.add(new RtYamlLine("second: ", 2));
+        lines.add(new RtYamlLine("  fourth: some", 3));
+        lines.add(new RtYamlLine("  fifth: values", 4));
+        lines.add(new RtYamlLine("third: something", 5));
+        final YamlLines comment = new FirstCommentFound(
+                new AllYamlLines(lines)
+        );
+        MatcherAssert.assertThat(comment, Matchers.emptyIterable());
+    }
+}
diff --git a/src/test/java/com/amihaiemil/eoyaml/ReadCommentTest.java b/src/test/java/com/amihaiemil/eoyaml/ReadCommentTest.java
new file mode 100644
index 0000000..19fc8dd
--- /dev/null
+++ b/src/test/java/com/amihaiemil/eoyaml/ReadCommentTest.java
@@ -0,0 +1,158 @@
+/**
+ * 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.List;
+
+/**
+ * Unit tests for {@link ReadComment}.
+ * @author Mihai Andronache (amihaiemil@gmail.com)
+ * @version $Id$
+ * @since 4.2.0
+ */
+public final class ReadCommentTest {
+
+    /**
+     * ReadComment can return the YamlNode that it refers to.
+     */
+    @Test
+    public void returnsYamlNode() {
+        final YamlNode node = Mockito.mock(YamlNode.class);
+        MatcherAssert.assertThat(
+            new ReadComment(
+                new AllYamlLines(new ArrayList<>()), node
+            ).yamlNode(),
+            Matchers.is(node)
+        );
+    }
+
+    /**
+     * ReadComment can return the empty comment from empty yaml lines.
+     */
+    @Test
+    public void returnsValueFromEmptyLines() {
+        MatcherAssert.assertThat(
+            new ReadComment(
+                new AllYamlLines(new ArrayList<>()),
+                Mockito.mock(YamlNode.class)
+            ).value(),
+            Matchers.isEmptyString()
+        );
+    }
+
+    /**
+     * ReadComment can return the comment from a single YamlLine.
+     */
+    @Test
+    public void returnsValueFromSingleLine() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("# comment line", 0));
+        MatcherAssert.assertThat(
+            new ReadComment(
+                new AllYamlLines(lines),
+                Mockito.mock(YamlNode.class)
+            ).value(),
+            Matchers.equalTo("comment line")
+        );
+    }
+
+    /**
+     * ReadComment can return a comment spanning multiple lines.
+     */
+    @Test
+    public void returnsMultiLineComment() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("# comment", 0));
+        lines.add(new RtYamlLine("# on multiple", 1));
+        lines.add(new RtYamlLine("# lines", 2));
+        final StringBuilder expected = new StringBuilder();
+        expected
+            .append("comment").append(System.lineSeparator())
+            .append("on multiple").append(System.lineSeparator())
+            .append("lines");
+        MatcherAssert.assertThat(
+            new ReadComment(
+                new AllYamlLines(lines),
+                Mockito.mock(YamlNode.class)
+            ).value(),
+            Matchers.equalTo(expected.toString())
+        );
+    }
+
+    /**
+     * ReadComment can return a comment spanning multiple lines, that
+     * also has empty lines.
+     */
+    @Test
+    public void returnsMultiLineCommentWithEmptyLines() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("# comment", 0));
+        lines.add(new RtYamlLine("# ", 1));
+        lines.add(new RtYamlLine("# on multiple", 2));
+        lines.add(new RtYamlLine("# ", 2));
+        lines.add(new RtYamlLine("# lines", 3));
+        final StringBuilder expected = new StringBuilder();
+        expected
+            .append("comment").append(System.lineSeparator())
+            .append(System.lineSeparator())
+            .append("on multiple").append(System.lineSeparator())
+            .append(System.lineSeparator())
+            .append("lines");
+        MatcherAssert.assertThat(
+            new ReadComment(
+                new AllYamlLines(lines),
+                Mockito.mock(YamlNode.class)
+            ).value(),
+            Matchers.equalTo(expected.toString())
+        );
+    }
+
+    /**
+     * ReadComment can return an empty string if the comment
+     * line is empty.
+     */
+    @Test
+    public void returnsValueFromEmptySingleComment() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("#", 0));
+        MatcherAssert.assertThat(
+            new ReadComment(
+                new AllYamlLines(lines),
+                Mockito.mock(YamlNode.class)
+            ).value(),
+            Matchers.isEmptyString()
+        );
+    }
+
+}
diff --git a/src/test/java/com/amihaiemil/eoyaml/SkipTest.java b/src/test/java/com/amihaiemil/eoyaml/SkipTest.java
index 4afdb7e..13f54dd 100644
--- a/src/test/java/com/amihaiemil/eoyaml/SkipTest.java
+++ b/src/test/java/com/amihaiemil/eoyaml/SkipTest.java
@@ -30,8 +30,10 @@ 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;
 
@@ -142,4 +144,33 @@ public final class SkipTest {
             Matchers.equalTo("nextTemp: 15C")
         );
     }
+
+    /**
+     * Skip delegates the call to originals().
+     */
+    @Test
+    public void delegatesOriginals() {
+        final YamlLines initial = Mockito.mock(YamlLines.class);
+        final Collection<YamlLine> lines = Mockito.mock(Collection.class);
+        Mockito.when(initial.original()).thenReturn(lines);
+        MatcherAssert.assertThat(
+            new Skip(initial).original(),
+            Matchers.is(lines)
+        );
+    }
+
+    /**
+     * Skip delegates the call to toYamlNode().
+     */
+    @Test
+    public void delegatesToYamlNode() {
+        final YamlLines initial = Mockito.mock(YamlLines.class);
+        final YamlLine prev = Mockito.mock(YamlLine.class);
+        final YamlNode node = Mockito.mock(YamlNode.class);
+        Mockito.when(initial.toYamlNode(prev)).thenReturn(node);
+        MatcherAssert.assertThat(
+            new Skip(initial).toYamlNode(prev),
+            Matchers.is(node)
+        );
+    }
 }