Skip to content
182 changes: 142 additions & 40 deletions internal-api/src/main/java/datadog/trace/util/SubSequence.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package datadog.trace.util;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
* A <code>CharSequence</code> that is a view into a sub-sequence of a <code>String</code>. Unlike
* <code>String.subSequence</code>, this class doesn't allocate an additional <code>String</code>,
Expand Down Expand Up @@ -66,8 +68,10 @@ public int length() {

@Override
public SubSequence subSequence(int start, int end) {
// start/end are offsets in THIS view's coordinates (CharSequence contract), so the absolute
// end is beginIndex + end -- NOT beginIndex + start + end (which overshoots by `start`).
int newBeginIndex = this.beginIndex + start;
int newEndIndex = this.beginIndex + start + end;
int newEndIndex = this.beginIndex + end;
Comment thread
bric3 marked this conversation as resolved.

return new SubSequence(this.str, newBeginIndex, newEndIndex);
}
Expand All @@ -81,71 +85,169 @@ public void appendTo(StringBuilder builder) {
if (beginIndex != endIndex) builder.append(this.str, beginIndex, endIndex);
}

/** Returns the hash code as <code>backingStr.substr(beginIndex, endIndex).hashCode()</code> */
/**
* The same value as {@code toString().hashCode()} -- the {@link String} hash polynomial over this
* window -- but computed directly over the backing characters so hashing a view does not
* materialize a substring. Stays consistent with {@link #equals}: a view, its content-equal
* {@code String}, and an equal-content view all share this hash.
*/
@Override
public int hashCode() {
return this.toString().hashCode();
int h = 0;
for (int i = this.beginIndex; i < this.endIndex; ++i) {
h = 31 * h + this.str.charAt(i);
}
return h;
}

/**
* Also handles String comparisons this.equals(backingStr.substr(beginIndex, endIndex)) is true
* Dispatches on the argument's runtime type: a {@code String} takes the {@link #equals(String)}
* region-compare fast path; any other {@code CharSequence} is compared via {@link
* #contentEquals(CharSequence)}. So {@code this.equals(backingStr.substring(beginIndex,
* endIndex))} is true, and two views with equal content are equal.
*/
// Intentionally asymmetric: a SubSequence equals any String/CharSequence with the same content,
// so subSeq.equals(str) can be true while str.equals(subSeq) is false. This view-equality
// convenience is by design (see class javadoc); accepted despite the equals() symmetry contract.
@Override
@SuppressFBWarnings("EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS")
public boolean equals(Object obj) {
if (!(obj instanceof CharSequence)) return false;
if (obj instanceof String) return this.equals((String) obj);
if (obj instanceof CharSequence) return this.contentEquals((CharSequence) obj);

return this.equals((CharSequence) obj);
return false;
}

public final boolean equals(CharSequence that) {
int thisLen = this.length();
int thatLen = that.length();
/**
* Equivalent to {@code toString().equals(other)}. A thin alias for {@link
* #contentEquals(String)}, kept for callers working in terms of {@code equals}; prefer {@link
* #contentEquals(String)} directly.
*/
public final boolean equals(String other) {
return this.contentEquals(other);
}

if (thisLen != thatLen) return false;
/**
* Equivalent to {@code toString().contentEquals(that)}: true when {@code that} has the same
* length and characters as this window. The general char-by-char comparison for any {@code
* CharSequence}; prefer {@link #contentEquals(String)} when the argument is known to be a {@code
* String}.
*/
public final boolean contentEquals(CharSequence that) {
if (that == null) return false;

int len = this.length();
if (len != that.length()) return false;

for (int i = 0; i < Math.min(this.length(), that.length()); ++i) {
for (int i = 0; i < len; ++i) {
if (this.charAt(i) != that.charAt(i)) return false;
}
return true;
}

/**
* True if this sub-sequence contains {@code needle} -- the zero-copy equivalent of {@code
* toString().contains(needle)}, with no substring materialized.
* String-specialized {@link #contentEquals(CharSequence)}: true when {@code other} has the same
* length and characters as this window, via {@link String#regionMatches} (backing-array compare,
* no per-char loop). Prefer this to {@link #equals(Object)} -- it states content-comparison
* intent and is free of the {@code equals} contract's cross-type asymmetry.
*/
public final boolean contains(String needle) {
return Strings.regionContains(this.str, this.beginIndex, this.endIndex, needle);
public final boolean contentEquals(String other) {
return other != null
&& other.length() == this.length()
&& this.str.regionMatches(this.beginIndex, other, 0, other.length());
}

/** Case-insensitive content comparison; mirrors {@link String#equalsIgnoreCase(String)}. */
public final boolean equalsIgnoreCase(CharSequence that) {
int len = this.length();
if (that == null || len != that.length()) return false;
/**
* Case-insensitive counterpart of {@link #equals(String)}. Like {@link
* String#equalsIgnoreCase(String)}, a {@code null} argument is {@code false} rather than an
* error.
*/
public final boolean equalsIgnoreCase(String other) {
return other != null
&& other.length() == this.length()
&& this.str.regionMatches(true, this.beginIndex, other, 0, other.length());
}

for (int i = 0; i < len; ++i) {
char a = this.charAt(i);
char b = that.charAt(i);
if (a != b) {
// Same two-way fold String.regionMatches(ignoreCase) uses (handles locale edge cases).
char au = Character.toUpperCase(a);
char bu = Character.toUpperCase(b);
if (au != bu && Character.toLowerCase(au) != Character.toLowerCase(bu)) {
return false;
}
}
}
return true;
/**
* Equivalent to {@code toString().startsWith(prefix)}. The window guard ({@code prefix.length()
* <= length()}) keeps the delegated read inside {@code [beginIndex, endIndex)}.
*/
public final boolean startsWith(String prefix) {
return prefix.length() <= this.length() && this.str.startsWith(prefix, this.beginIndex);
}

/** True if this sub-sequence begins with {@code prefix} (content comparison, no allocation). */
public final boolean startsWith(CharSequence prefix) {
int prefixLen = prefix.length();
if (prefixLen > this.length()) return false;
/**
* Equivalent to {@code length() > 0 && charAt(0) == c}, the single-character {@link
* #startsWith(String)}.
*/
public final boolean startsWith(char c) {
return this.beginIndex < this.endIndex && this.str.charAt(this.beginIndex) == c;
}

for (int i = 0; i < prefixLen; ++i) {
if (this.charAt(i) != prefix.charAt(i)) return false;
}
return true;
/**
* Equivalent to {@code toString().endsWith(suffix)}. Implemented as a prefix match anchored at
* {@code endIndex - suffix.length()} so the read stays inside this window.
*/
public final boolean endsWith(String suffix) {
int suffixLen = suffix.length();
return suffixLen <= this.length() && this.str.startsWith(suffix, this.endIndex - suffixLen);
}

/**
* Equivalent to {@code length() > 0 && charAt(length() - 1) == c}, the single-character {@link
* #endsWith(String)}.
*/
public final boolean endsWith(char c) {
return this.beginIndex < this.endIndex && this.str.charAt(this.endIndex - 1) == c;
}

/**
* Equivalent to {@code toString().indexOf(needle)}: the offset of the first full occurrence of
* {@code needle} within this window relative to the window start, or {@code -1} if it does not
* occur fully in range. {@link String#indexOf(String, int)} returns the earliest occurrence at or
* after {@code beginIndex}, so a single bound check against {@code endIndex} is exact.
*/
public final int indexOf(String needle) {
int idx = this.str.indexOf(needle, this.beginIndex);
return (idx >= 0 && idx + needle.length() <= this.endIndex) ? idx - this.beginIndex : -1;
}

/**
* Equivalent to {@code toString().indexOf(c)}: the offset of the first {@code c} within this
* window relative to the window start, or {@code -1} if it does not occur in range.
*/
public final int indexOf(char c) {
int idx = this.str.indexOf(c, this.beginIndex);
return (idx >= 0 && idx < this.endIndex) ? idx - this.beginIndex : -1;
}

/**
* Equivalent to {@code toString().lastIndexOf(needle)}: the offset of the last full occurrence of
* {@code needle} within this window relative to the window start, or {@code -1} if it does not
* occur fully in range. Searches back from {@code endIndex - needle.length()} -- the latest start
* whose end still fits the window -- so the lower bound is a single check against {@code
* beginIndex}.
*/
public final int lastIndexOf(String needle) {
int idx = this.str.lastIndexOf(needle, this.endIndex - needle.length());
return (idx >= this.beginIndex) ? idx - this.beginIndex : -1;
}

/**
* Equivalent to {@code toString().lastIndexOf(c)}: the offset of the last {@code c} within this
* window relative to the window start, or {@code -1} if it does not occur in range.
*/
public final int lastIndexOf(char c) {
int idx = this.str.lastIndexOf(c, this.endIndex - 1);
return (idx >= this.beginIndex) ? idx - this.beginIndex : -1;
}

/**
* True if this sub-sequence contains {@code needle} -- the zero-copy equivalent of {@code
* toString().contains(needle)}, with no substring materialized.
*/
public final boolean contains(String needle) {
return Strings.regionContains(this.str, this.beginIndex, this.endIndex, needle);
}

@Override
Expand Down
141 changes: 125 additions & 16 deletions internal-api/src/test/java/datadog/trace/util/SubSequenceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,27 +125,136 @@ public void contains() {
}

@Test
public void equalsIgnoreCase() {
SubSequence call = SubSequence.of("xx CALL yy", 3, 7); // "CALL"
public void subSequenceOfView() {
// Instance subSequence(start, end): start/end are in THIS view's coordinates (CharSequence
// contract), regardless of where the view sits in the backing string.
SubSequence view = SubSequence.of("abcdefghij", 2, 8); // "cdefgh"
SubSequence mid = view.subSequence(1, 4); // chars [1, 4) of "cdefgh" -> "def"
assertEquals("def", mid.toString());
assertEquals(3, mid.beginIndex()); // absolute begin = 2 + 1
assertEquals(6, mid.endIndex()); // absolute end = 2 + 4 (NOT 2 + 1 + 4)

// full window and empty are exact
assertEquals("cdefgh", view.subSequence(0, view.length()).toString());
assertEquals("", view.subSequence(2, 2).toString());

// nested: subSequence of a non-zero-start view stays correct (the case the old bug broke worst)
assertEquals("ef", mid.subSequence(1, 3).toString()); // chars [1, 3) of "def" -> "ef"
}

@Test
public void equalsString() {
// "call" sits at [6, 10) inside the backing string, flanked by other text.
SubSequence call = SubSequence.of("xxxxx call yyyyy", 6, 10);
assertTrue(call.equals("call"));
assertFalse(call.equals("CALL")); // case-sensitive
assertFalse(call.equals("cal")); // shorter
assertFalse(call.equals("calls")); // longer (would overshoot endIndex)
assertFalse(call.equals((Object) null));

// equals(Object) routes a String through the region-compare fast path...
assertTrue(call.equals((Object) "call"));
// ...and any other CharSequence (incl. another SubSequence) through contentEquals.
assertTrue(call.equals((Object) new StringBuilder("call")));
assertTrue(call.equals((Object) SubSequence.of("xxxxx call yyyyy", 6, 10)));
assertFalse(call.equals((Object) Integer.valueOf(4)));
}

@Test
public void contentEqualsCharSequence() {
SubSequence call = SubSequence.of("xxxxx call yyyyy", 6, 10); // "call"
assertTrue(call.contentEquals("call")); // String is a CharSequence
assertTrue(call.contentEquals(new StringBuilder("call")));
assertTrue(call.contentEquals(SubSequence.of("a call b", 2, 6)));
assertFalse(call.contentEquals("CALL")); // case-sensitive
assertFalse(call.contentEquals("cal")); // length mismatch
assertFalse(call.contentEquals(null));
}

@Test
public void equalsIgnoreCaseString() {
SubSequence call = SubSequence.of("xxxxx CaLl yyyyy", 6, 10);
assertTrue(call.equalsIgnoreCase("call"));
assertTrue(call.equalsIgnoreCase("CALL"));
assertTrue(call.equalsIgnoreCase("CaLl"));
assertFalse(call.equalsIgnoreCase("calls")); // length differs
assertFalse(call.equalsIgnoreCase("cant")); // same length, content differs
assertFalse(call.equalsIgnoreCase("cal"));
assertFalse(call.equalsIgnoreCase("calls"));
assertFalse(call.equalsIgnoreCase(null)); // matches String.equalsIgnoreCase(null)
}

@Test
public void startsWithString() {
SubSequence view = SubSequence.of("xx{call}xx", 2, 8); // "{call}"
assertTrue(view.startsWith("{"));
assertTrue(view.startsWith("{call"));
assertTrue(view.startsWith("{call}"));
assertFalse(view.startsWith("call"));
assertFalse(view.startsWith("{call}x")); // overshoots endIndex even though backing has 'x'
assertTrue(view.startsWith("")); // empty prefix
}

// case-sensitive equals stays case-sensitive
assertFalse(call.equals("call"));
assertTrue(call.equals("CALL"));
@Test
public void endsWithString() {
SubSequence view = SubSequence.of("xx{call}xx", 2, 8); // "{call}"
assertTrue(view.endsWith("}"));
assertTrue(view.endsWith("call}"));
assertTrue(view.endsWith("{call}"));
assertFalse(view.endsWith("call"));
assertFalse(view.endsWith("x{call}")); // undershoots beginIndex even though backing has 'x'
assertTrue(view.endsWith("")); // empty suffix
}

@Test
public void indexOfString() {
SubSequence view = SubSequence.of("aa-bc-bc-aa", 3, 8); // "bc-bc"
assertEquals(0, view.indexOf("bc")); // window-relative offset of the first occurrence
assertEquals(2, view.indexOf("-bc")); // non-zero relative offset
assertEquals(2, view.indexOf("-"));
assertEquals(-1, view.indexOf("aa")); // present in backing string but outside the window
assertEquals(-1, view.indexOf("bc-bc-")); // overshoots endIndex
}

@Test
public void lastIndexOfString() {
SubSequence view = SubSequence.of("aa-bc-bc-aa", 3, 8); // "bc-bc"
assertEquals(3, view.lastIndexOf("bc")); // last "bc" -> relative 3
assertEquals(2, view.lastIndexOf("-"));
assertEquals(-1, view.lastIndexOf("aa")); // outside the window on both ends
assertEquals(-1, view.lastIndexOf("bc-bc-")); // overshoots endIndex
}

@Test
public void startsWithChar() {
SubSequence view = SubSequence.of("xx{call}xx", 2, 8); // "{call}"
assertTrue(view.startsWith('{'));
assertFalse(view.startsWith('c')); // 'c' is at offset 1, not the start
assertFalse(view.startsWith('x')); // backing char before beginIndex, outside the window
assertFalse(SubSequence.EMPTY.startsWith('x')); // empty window
}

@Test
public void endsWithChar() {
SubSequence view = SubSequence.of("xx{call}xx", 2, 8); // "{call}"
assertTrue(view.endsWith('}'));
assertFalse(view.endsWith('l')); // 'l' is one before the end
assertFalse(view.endsWith('x')); // backing char at endIndex, outside the window
assertFalse(SubSequence.EMPTY.endsWith('x')); // empty window
}

@Test
public void indexOfChar() {
SubSequence view = SubSequence.of("aa-bc-bc-aa", 3, 8); // "bc-bc"
assertEquals(0, view.indexOf('b')); // window-relative offset of the first occurrence
assertEquals(1, view.indexOf('c'));
assertEquals(2, view.indexOf('-'));
assertEquals(-1, view.indexOf('a')); // present in backing string but outside the window
}

@Test
public void startsWith() {
SubSequence braceCall = SubSequence.of("xx{call} yy", 2, 7); // "{call"
assertTrue(braceCall.startsWith(""));
assertTrue(braceCall.startsWith("{"));
assertTrue(braceCall.startsWith("{ca"));
assertTrue(braceCall.startsWith("{call"));
assertFalse(braceCall.startsWith("call")); // not the prefix
assertFalse(braceCall.startsWith("{calls and more")); // prefix longer than sequence
public void lastIndexOfChar() {
SubSequence view = SubSequence.of("aa-bc-bc-aa", 3, 8); // "bc-bc"
assertEquals(3, view.lastIndexOf('b')); // last 'b' -> relative 3
assertEquals(4, view.lastIndexOf('c'));
assertEquals(2, view.lastIndexOf('-'));
assertEquals(-1, view.lastIndexOf('a')); // outside the window on both ends
}
}