(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.