diff --git a/src/main/java/org/cactoos/text/Multiline.java b/src/main/java/org/cactoos/text/Multiline.java new file mode 100644 index 000000000..71adf3c84 --- /dev/null +++ b/src/main/java/org/cactoos/text/Multiline.java @@ -0,0 +1,139 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017-2022 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.iterable.IterableEnvelope; +import org.cactoos.iterable.IterableOf; +import org.cactoos.iterable.Mapped; +import org.cactoos.iterator.IteratorOf; + +/** + * Break a Text according a limit. + * + *

There is no thread-safety guarantee. + * + * @since 0.56.0 + */ +public final class Multiline extends IterableEnvelope { + /** + * New line character. + */ + private static final String NEW_LINE = "\n"; + + /** + * Word separator. + */ + private static final String SPACE = " "; + + /** + * Split regular expression. + */ + private static final String SPLIT_REGEX = "\\s+"; + + /** + * Default max limit. + */ + private static final int MAX_LIMIT = 80; + + /** + * Ctor. + * + *

By default, the limit is 80 characters. + * + * @param text A CharSequence + */ + public Multiline(final CharSequence text) { + this(new TextOf(text)); + } + + /** + * Ctor. + * + *

By default, the limit is 80 characters. + * + * @param text The Text + */ + public Multiline(final Text text) { + this(text, Multiline.MAX_LIMIT); + } + + /** + * Ctor. + * + * @param text A CharSequence + * @param limit Limit of the result string + */ + public Multiline(final CharSequence text, final int limit) { + this(new TextOf(text), limit); + } + + /** + * Ctor. + * + * @param text The Text + * @param limit Limit of the result string + */ + public Multiline(final Text text, final int limit) { + super( + new Mapped<>( + TextOf::new, + new IterableOf<>( + () -> { + final String[] words = text.asString().split( + Multiline.SPLIT_REGEX + ); + final StringBuilder lines = new StringBuilder(); + final StringBuilder word = new StringBuilder(0); + int length = 0; + for (int idx = 0; idx < words.length; ++idx) { + word.setLength(0); + word.append(words[idx]); + final int size = Multiline.SPACE.length() + + word.length(); + if (length + size > limit) { + if (idx > 0) { + lines.append(Multiline.NEW_LINE); + } + length = 0; + } + if ( + idx < words.length - 1 + && length + size + words[idx + 1].length() + <= limit + ) { + word.append(Multiline.SPACE); + } + lines.append(word); + length = length + word.length(); + } + return new IteratorOf<>( + lines.toString().split(Multiline.NEW_LINE) + ); + } + ) + ) + ); + } +} diff --git a/src/test/java/org/cactoos/text/MultilineTest.java b/src/test/java/org/cactoos/text/MultilineTest.java new file mode 100644 index 000000000..d2d55228a --- /dev/null +++ b/src/test/java/org/cactoos/text/MultilineTest.java @@ -0,0 +1,113 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017-2022 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.iterable.IterableOf; +import org.hamcrest.core.IsEqual; +import org.junit.jupiter.api.Test; +import org.llorllale.cactoos.matchers.Assertion; + +/** + * Test case for {@link Multiline}. + * @since 0.56.0 + * @checkstyle JavadocMethodCheck (500 lines) + */ +final class MultilineTest { + @Test + void multilineAnEmptyText() { + final String content = ""; + new Assertion<>( + "Must multiline an empty text", + new Multiline(content, 8), + new IsEqual<>(new IterableOf<>(new TextOf(content))) + ).affirm(); + } + + @Test + void multilineText() { + final Text content = new Joined( + " ", + "Lorem ea et aliquip culpa aute amet elit nostrud culpa veniam", + "dolore eu irure incididunt. Velit officia occaecat est", + "adipisicing mollit veniam. Minim sunt est culpa labore." + ); + new Assertion<>( + "Must multiline a text at limit 50 characters", + new Multiline(content, 50), + new IsEqual<>( + new IterableOf<>( + new TextOf("Lorem ea et aliquip culpa aute amet elit nostrud"), + new TextOf("culpa veniam dolore eu irure incididunt. Velit"), + new TextOf("officia occaecat est adipisicing mollit veniam."), + new TextOf("Minim sunt est culpa labore.") + ) + ) + ).affirm(); + } + + @Test + void multilineTextOneCharSmaller() { + final String msg = "Hello World!"; + new Assertion<>( + "Must multiline a text with same length", + new Multiline(msg, 5), + new IsEqual<>(new IterableOf<>(new TextOf("Hello"), new TextOf("World!"))) + ).affirm(); + } + + @Test + void multilineTextWithLimitBiggerThanLength() { + final String msg = "cactoos framework"; + new Assertion<>( + "Must multiline a text with limit bigger than length", + new Multiline(msg, 50), + new IsEqual<>(new IterableOf<>(new TextOf("cactoos framework"))) + ).affirm(); + } + + @Test + void multilineTextBiggerThanDefaultLimit() { + new Assertion<>( + "Must abbreviate a text bigger than default max width", + new Multiline( + new Joined( + " ", + "The quick brown fox jumps over the lazy black dog", + "and after that returned to the cave" + ) + ), + new IsEqual<>( + new IterableOf<>( + new Joined( + " ", + "The quick brown fox jumps over the lazy", + "black dog and after that returned to " + ), + new TextOf("the cave") + ) + ) + ).affirm(); + } +}