(This post has continuation at A string view over a Java String - improved take II.)
This time, I have a simple string view class for faster operation on substrings in actual string objects:
com.github.coderodde.util.StringView.java:
package com.github.coderodde.util; import java.util.Objects; /** * This class implements a string view that can be enlarged, shrinked or * shifted. * * @version 1.0.0 (Aug 27, 2024) * @since 1.0.0 (Aug 27, 2024) */ public final class StringView { private final String string; private int viewOffset; private int viewLength; /** * Constructs this string view. * * @param string the owner string. * @param viewOffset the offset in the owner string. * @param viewLength the length of the view. */ public StringView(final String string, final int viewOffset, final int viewLength) { this.string = Objects.requireNonNull(string, "The input string is null."); checkOnConstruction(viewOffset, viewLength); this.viewOffset = viewOffset; this.viewLength = viewLength; } /** * Constructs this string view. The resulting view will cover the entire * owner string. * * @param string the owner string. */ public StringView(final String string) { this(string, 0, string.length()); } /** * Accesses the {@code index}th character of this string view. * * @param index the index of the desired character. * * @return the {@code index}th character of this string view. */ public char charAt(final int index) { checkIndex(index); return string.charAt(viewOffset + index); } /** * Return a {@link java.lang.String} holding the contents of this string * view. * * @return the string representation of this string view. */ @Override public String toString() { final StringBuilder sb = new StringBuilder(viewLength); for (int index = 0; index < viewLength; index++) { sb.append(charAt(index)); } return sb.toString(); } public String getOwnerString() { return string; } public int getViewOffset() { return viewOffset; } public int getViewLength() { return viewLength; } /** * Attempts to shift the view by {@code steps} steps to the left. * * @param steps the number of steps to shift. */ public void shiftLeft(final int steps) { checkStepsNotBelowZero(steps); viewOffset = Math.max(0, viewOffset - steps); } /** * Attempts to shift the view by {@code steps} steps to the right. * * @param steps the number of steps to shift. */ public void shiftRight(final int steps) { checkStepsNotBelowZero(steps); viewOffset = Math.min(string.length(), viewOffset + steps); } /** * Shift either to left or right by {@code steps} steps. If the argument is * negative, shifts to the left. Otherwise, shifts to the right. * * @param steps the number of steps to shift. */ public void shift(final int steps) { if (steps < 0) { shiftLeft(-steps); } else { shiftRight(steps); } } /** * Shrinks this string view by {@code length} elements from the tail of the * string view. * * @param length the length to shrinky by. */ public void shrink(final int length) { checkLengthNotNegative(length); checkLengthIsNotLargerThanCurrentViewLength(length); viewLength -= length; } /** * Grows this string view by {@code length} elements towards the tail of the * string view. * * @param length the length to grow by. */ public void grow(final int length) { checkLengthNotNegative(length); checkViewDoesNotOutgrow(length); viewLength += length; } private void checkIndex(final int index) { if (index < 0) { final String exceptionMessage = String.format("index (%d) < 0", index); throw new IllegalArgumentException(exceptionMessage); } if (index >= viewLength) { final String exceptionMessage = String.format( "index (%d) >= viewLength (%d)", index, viewLength); throw new IndexOutOfBoundsException(exceptionMessage); } } private void checkViewDoesNotOutgrow(final int length) { if (viewOffset + length > string.length()) { final String exceptionMessage = String.format( "New view outgrows the string view parent by %d characters.", viewOffset + length - string.length()); throw new IllegalArgumentException(exceptionMessage); } } private void checkLengthNotNegative(final int length) { if (length < 0) { final String exceptionMessage = String.format("length (%d) < 0", length); throw new IllegalArgumentException(exceptionMessage); } } private void checkLengthIsNotLargerThanCurrentViewLength(final int length) { if (length > viewLength) { final String exceptionMessage = String.format( "length (%d) > viewLength (%d)", length, viewLength); throw new IllegalArgumentException(exceptionMessage); } } private void checkStepsNotBelowZero(final int steps) { if (steps < 0) { final String exceptionMessage = String.format("steps (%d) < 0", steps); throw new IllegalArgumentException(exceptionMessage); } } private void checkOnConstruction(final int viewOffset, final int viewLength) { if (viewOffset < 0) { final String exceptionMessage = String.format( "offset (%d) < 0", viewOffset); throw new IllegalArgumentException(exceptionMessage); } if (viewOffset > string.length()) { final String exceptionMessage = String.format( "offset (%d) > string.length (%d)", viewOffset, string.length()); throw new IllegalArgumentException(exceptionMessage); } if (viewLength < 0) { final String exceptionMessage = String.format( "viewLength (%d) < 0", viewLength); throw new IllegalArgumentException(exceptionMessage); } if (viewLength > string.length()) { final String exceptionMessage = String.format( "viewLength (%d) > string.length (%d)", viewLength, string.length()); throw new IllegalArgumentException(exceptionMessage); } if (viewOffset + viewLength > string.length()) { final String exceptionMessage = String.format( "View outside the right border by %d characters.", viewOffset + viewLength - string.length()); throw new IllegalArgumentException(exceptionMessage); } } }
com.github.coderodde.util.StringViewTest.java:
package com.github.coderodde.util; import org.junit.Test; import static org.junit.Assert.*; public class StringViewTest { @Test public void testCharAt() { final StringView view = new StringView("abcd"); assertEquals('a', view.charAt(0)); assertEquals('b', view.charAt(1)); assertEquals('c', view.charAt(2)); assertEquals('d', view.charAt(3)); view.shrink(1); view.shift(1); assertEquals('b', view.charAt(0)); assertEquals('c', view.charAt(1)); assertEquals('d', view.charAt(2)); } @Test public void testToString() { final StringView view = new StringView("abcde"); view.shrink(2); view.shiftRight(1); final String s = view.toString(); assertEquals("bcd", s); } @Test public void testShiftLeft() { final StringView view = new StringView("0123456789",3, 3); view.shiftLeft(2); assertEquals("123", view.toString()); } @Test public void testShiftRight() { final StringView view = new StringView("0123456", 3, 2); view.shiftRight(1); assertEquals("45", view.toString()); } @Test public void testShift() { final StringView view = new StringView("12345", 1, 3); view.shift(-1); assertEquals("123", view.toString()); view.shift(2); assertEquals("345", view.toString()); } @Test public void testShrink() { final StringView view = new StringView("abcde12345", 0, 6); view.shiftRight(2); view.shrink(4); assertEquals("cd", view.toString()); } @Test public void testGrow() { final StringView view = new StringView("abcde12345", 2, 4); view.grow(2); assertEquals("cde123", view.toString()); } }
Critique request
As always, I would like to receive any commentary.