Repository: yegor256/cactoos
Pull request #1743 (2024-11-01 12:50:43)

 Adding an OOP analogue for StringUtils.splitPreserveAllTokens ISSUE #1722
Rationale of some decisions made:
1) Functional Interface allows use of custom String splitter function avoiding the static method calls
2) A new class created as modifying already existing Split class would not be shorter : if we add some checking for flags would create just as many constructors as with 2 distinct classes
3) Tests only compare lengths of obtained Collections


Patch:
diff --git a/src/main/java/org/cactoos/TriFunc.java b/src/main/java/org/cactoos/TriFunc.java
new file mode 100644
index 00000000..e500276b
--- /dev/null
+++ b/src/main/java/org/cactoos/TriFunc.java
@@ -0,0 +1,47 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017-2024 Yegor Bugayenko
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package org.cactoos;
+
+/**
+ * Function that accepts three arguments.
+ *
+ * @param <X> Type of input
+ * @param <Y> Type of input
+ * @param <Z> Type of input
+ * @param <W> Type of output
+ * @since 0.0
+ */
+@FunctionalInterface
+public interface TriFunc<X, Y, Z, W> {
+
+    /**
+     * Apply it.
+     * @param first The first argument
+     * @param second The second argument
+     * @param third The third argument
+     * @return The result
+     * @throws Exception If fails
+     */
+    W apply(X first, Y second, Z third) throws Exception;
+}
diff --git a/src/main/java/org/cactoos/func/TriFuncSplitPreserve.java b/src/main/java/org/cactoos/func/TriFuncSplitPreserve.java
new file mode 100644
index 00000000..068f30b5
--- /dev/null
+++ b/src/main/java/org/cactoos/func/TriFuncSplitPreserve.java
@@ -0,0 +1,75 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017-2024 Yegor Bugayenko
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.cactoos.func;
+
+import java.util.Collection;
+import org.cactoos.TriFunc;
+import org.cactoos.list.ListOf;
+
+/**
+ * A String splitter preserving all tokens.
+ * Unlike regular Split, stores empty "" tokens
+ * created by adjacent regex separators.
+ *
+ * <p>
+ *     Examples:
+ *     1) text - " hello there ", regex - " "
+ *     result: ["", "hello", "there", ""]
+ *     2) text - "aaa", regex - "a"
+ *     result: ["", "", "", ""]
+ * </p>
+ *
+ * @since 0.0
+ */
+public final class TriFuncSplitPreserve
+    implements TriFunc
+        <String, String, Integer, Collection<String>> {
+    @Override
+    public Collection<String> apply(
+        final String str,
+        final String regex,
+        final Integer lmt
+    ) {
+        final ListOf<String> ret = new ListOf<>();
+        int start = 0;
+        int pos = str.indexOf(regex);
+        while (pos >= start) {
+            if (lmt > 0 && ret.size() == lmt || regex.isEmpty()) {
+                break;
+            }
+            ret.add(str.substring(start, pos));
+            start = pos + regex.length();
+            pos = str.indexOf(regex, start);
+        }
+        if (lmt <= 0 || ret.size() < lmt || regex.isEmpty()) {
+            if (start < str.length()) {
+                ret.add(str.substring(start));
+            } else if (start == str.length()) {
+                ret.add("");
+            }
+        }
+        return ret;
+    }
+}
diff --git a/src/main/java/org/cactoos/text/SplitPreserveAllTokens.java b/src/main/java/org/cactoos/text/SplitPreserveAllTokens.java
new file mode 100644
index 00000000..b3b54bde
--- /dev/null
+++ b/src/main/java/org/cactoos/text/SplitPreserveAllTokens.java
@@ -0,0 +1,179 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017-2024 Yegor Bugayenko
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package org.cactoos.text;
+
+import org.cactoos.Text;
+import org.cactoos.func.TriFuncSplitPreserve;
+import org.cactoos.iterable.IterableEnvelope;
+import org.cactoos.iterable.IterableOf;
+import org.cactoos.iterable.Mapped;
+import org.cactoos.iterator.IteratorOf;
+
+/**
+ * Splits the Text into an array, including empty
+ * tokens created by adjacent separators.
+ *
+ * @since 0.0
+ */
+public final class SplitPreserveAllTokens extends IterableEnvelope<Text> {
+    /**
+     * Ctor.
+     *
+     * @param text The text
+     */
+    public SplitPreserveAllTokens(final CharSequence text) {
+        this(new TextOf(text), new TextOf(" "));
+    }
+
+    /**
+     * Ctor.
+     *
+     * @param text The text
+     */
+    public SplitPreserveAllTokens(final Text text) {
+        this(text, new TextOf(" "));
+    }
+
+    /**
+     * Ctor.
+     *
+     * @param text The text
+     * @param lmt The limit
+     */
+    public SplitPreserveAllTokens(final CharSequence text, final int lmt) {
+        this(new TextOf(text), new TextOf(" "), lmt);
+    }
+
+    /**
+     * Ctor.
+     *
+     * @param text The text
+     * @param lmt The limit
+     */
+    public SplitPreserveAllTokens(final Text text, final int lmt) {
+        this(text, new TextOf(" "), lmt);
+    }
+
+    /**
+     * Ctor.
+     *
+     * @param text The text
+     * @param rgx The regex
+     */
+    public SplitPreserveAllTokens(final CharSequence text, final CharSequence rgx) {
+        this(new TextOf(text), new TextOf(rgx));
+    }
+
+    /**
+     * Ctor.
+     *
+     * @param text The text
+     * @param rgx The regex
+     * @param lmt The limit
+     */
+    public SplitPreserveAllTokens(final CharSequence text, final CharSequence rgx, final int lmt) {
+        this(new TextOf(text), new TextOf(rgx), lmt);
+    }
+
+    /**
+     * Ctor.
+     *
+     * @param text The text
+     * @param rgx The regex
+     */
+    public SplitPreserveAllTokens(final CharSequence text, final Text rgx) {
+        this(new TextOf(text), rgx);
+    }
+
+    /**
+     * Ctor.
+     *
+     * @param text The text
+     * @param rgx The regex
+     * @param lmt The limit
+     */
+    public SplitPreserveAllTokens(final CharSequence text, final Text rgx, final int lmt) {
+        this(new TextOf(text), rgx, lmt);
+    }
+
+    /**
+     * Ctor.
+     *
+     * @param text The text
+     * @param rgx The regex
+     */
+    public SplitPreserveAllTokens(final Text text, final CharSequence rgx) {
+        this(text, new TextOf(rgx));
+    }
+
+    /**
+     * Ctor.
+     *
+     * @param text The text
+     * @param rgx The regex
+     * @param lmt The limit
+     */
+    public SplitPreserveAllTokens(final Text text, final CharSequence rgx, final int lmt) {
+        this(text, new TextOf(rgx), lmt);
+    }
+
+    /**
+     * Ctor.
+     *
+     * @param text The text
+     * @param rgx The regex
+     */
+    public SplitPreserveAllTokens(final Text text, final Text rgx) {
+        this(text, rgx, 0);
+    }
+
+    /**
+     * Ctor.
+     *
+     * @param text The text
+     * @param rgx The regex
+     * @param lmt The limit
+     */
+    public SplitPreserveAllTokens(
+        final Text text,
+        final Text rgx,
+        final int lmt
+    ) {
+        super(
+            new Mapped<>(
+                TextOf::new,
+                new IterableOf<>(
+                    () -> new IteratorOf<>(
+                        new TriFuncSplitPreserve()
+                            .apply(
+                                text.toString(),
+                                rgx.toString(),
+                                lmt
+                            ).toArray(new String[0])
+                    )
+                )
+            )
+        );
+    }
+}


Test patch:
diff --git a/src/test/java/org/cactoos/text/SplitPreserveTest.java b/src/test/java/org/cactoos/text/SplitPreserveTest.java
new file mode 100644
index 00000000..01dc0a4c
--- /dev/null
+++ b/src/test/java/org/cactoos/text/SplitPreserveTest.java
@@ -0,0 +1,173 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017-2024 Yegor Bugayenko
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.cactoos.text;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import org.cactoos.Text;
+import org.cactoos.iterable.IterableOf;
+import org.hamcrest.Matchers;
+import org.hamcrest.core.IsNot;
+import org.junit.jupiter.api.Test;
+import org.llorllale.cactoos.matchers.Assertion;
+
+/**
+ * Testing correctness of SplitPreserveAllTokens.
+ * Compare with Split class in specified cases.
+ *
+ * @since 0.0
+ */
+final class SplitPreserveTest {
+    @Test
+    void checkingSplit() {
+        String txt = "aaa";
+        final String msg = "Adjacent separators must create an empty element";
+        ArrayList<Text> array = new ArrayList<>(4);
+        array.add(new TextOf(""));
+        array.add(new TextOf(""));
+        array.add(new TextOf(""));
+        array.add(new TextOf(""));
+        new Assertion<>(
+            msg,
+            this.getLength(
+                new Split(
+                    new TextOf(txt),
+                    new TextOf("a")
+                ).iterator()
+            ),
+            IsNot.not(
+                Matchers.equalTo(
+                    this.getLength(
+                        new IterableOf<Text>(
+                            array.iterator()
+                        ).iterator()
+                    )
+                )
+            )
+        ).affirm();
+        txt = " how ";
+        array = new ArrayList<>(3);
+        array.add(new TextOf(""));
+        array.add(new TextOf("how"));
+        array.add(new TextOf(""));
+        new Assertion<>(
+            msg,
+            this.getLength(
+                new Split(
+                    new TextOf(txt),
+                    new TextOf(" ")
+                ).iterator()
+            ),
+            IsNot.not(
+                Matchers.equalTo(
+                    this.getLength(
+                        new IterableOf<Text>(
+                            array.iterator()
+                        ).iterator()
+                    )
+                )
+            )
+        ).affirm();
+    }
+
+    int getLength(final Iterator<Text> iter) {
+        int count = 0;
+        while (iter.hasNext()) {
+            iter.next();
+            count += 1;
+        }
+        return count;
+    }
+
+    @Test
+    void checkingSplitPreserveTokens() {
+        String txt = "aaa";
+        final String msg = "Adjacent separators must create an empty element";
+        ArrayList<Text> array = new ArrayList<>(4);
+        array.add(new TextOf(""));
+        array.add(new TextOf(""));
+        array.add(new TextOf(""));
+        array.add(new TextOf(""));
+        new Assertion<>(
+            msg,
+            this.getLength(
+                new SplitPreserveAllTokens(
+                    new TextOf(txt),
+                    new TextOf("a")
+                ).iterator()
+            ),
+            Matchers.equalTo(
+                this.getLength(
+                    new IterableOf<Text>(
+                        array.iterator()
+                    ).iterator()
+                )
+            )
+        ).affirm();
+        txt = "lol\\  / dude";
+        array = new ArrayList<>(4);
+        array.add(new TextOf("lol\\"));
+        array.add(new TextOf(""));
+        array.add(new TextOf("/"));
+        array.add(new TextOf("dude"));
+        new Assertion<>(
+            msg,
+            this.getLength(
+                new SplitPreserveAllTokens(
+                    new TextOf(txt),
+                    new TextOf(" ")
+                ).iterator()
+            ),
+            Matchers.equalTo(
+                this.getLength(
+                    new IterableOf<Text>(
+                        array.iterator()
+                    ).iterator()
+                )
+            )
+        ).affirm();
+        txt = " how ";
+        array = new ArrayList<>(3);
+        array.add(new TextOf(""));
+        array.add(new TextOf("how"));
+        array.add(new TextOf(""));
+        new Assertion<>(
+            msg,
+            this.getLength(
+                new SplitPreserveAllTokens(
+                    new TextOf(txt),
+                    new TextOf(" ")
+                ).iterator()
+            ),
+            Matchers.equalTo(
+                this.getLength(
+                    new IterableOf<Text>(
+                        array.iterator()
+                    ).iterator()
+                )
+            )
+        ).affirm();
+    }
+}