Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public GridStringBuilder(CharSequence seq) {
/**
*
* @param len Length to set.
* @throws UnsupportedOperationException if length limit is not supported by this GridStringBuilder
*/
public void setLength(int len) {
impl.setLength(len);
Expand Down Expand Up @@ -255,6 +256,7 @@ public GridStringBuilder appendCodePoint(int codePoint) {
* @param start Start position to delete from.
* @param end End position.
* @return This buffer for chaining method calls.
* @throws UnsupportedOperationException if not supported by this imlementation
*/
public GridStringBuilder d(int start, int end) {
impl.delete(start, end);
Expand All @@ -266,6 +268,7 @@ public GridStringBuilder d(int start, int end) {
*
* @param index Index to delete character at.
* @return This buffer for chaining method calls.
* @throws UnsupportedOperationException if not supported by this imlementation
*/
public GridStringBuilder d(int index) {
impl.deleteCharAt(index);
Expand All @@ -290,6 +293,7 @@ public GridStringBuilder nl() {
* @param end End position.
* @param str String to replace with.
* @return This buffer for chaining method calls.
* @throws UnsupportedOperationException if not supported by this imlementation
*/
public GridStringBuilder r(int start, int end, String str) {
impl.replace(start, end, str);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,10 @@

package org.apache.ignite.internal.util.tostring;

import java.util.Arrays;

/**
* Basic string builder over circular buffer.
*/
public class CircularStringBuilder {

/** Value */
private final char value[];

Expand All @@ -47,16 +44,6 @@ public class CircularStringBuilder {
value = new char[capacity];
}

/**
* Reset internal builder state
*/
public void reset() {
Arrays.fill(value, (char)0);
finishAt = -1;
full = false;
skipped = 0;
}

/**
* Returns the length (character count).
*
Expand Down Expand Up @@ -170,13 +157,102 @@ private CircularStringBuilder appendNull() {
return this;
}

/**
* Inserts a string into the buffer at the specified logical offset.
* This method is optimized to minimize the number of elements moved by choosing
* to shift elements from the closest end (left or right) to the insertion point.
*
* @param offset The logical position (accounting for skipped characters)
* at which to insert.
* @param valToInsert The string to be inserted.
* @throws StringIndexOutOfBoundsException if the offset is invalid.
*/
public void insert(int offset, String valToInsert) {
int curLength = length();
int offsetInsideBuf = offset - skipped;
if (offset < 0 || offsetInsideBuf > curLength)
throw new StringIndexOutOfBoundsException("Offset " + offset + " out of bounds for length " + curLength);
if (valToInsert == null)
valToInsert = "null";
int insertLength = valToInsert.length();
if (insertLength == 0)
return;
if (offsetInsideBuf == curLength) {
append(valToInsert);
return;
}
int spareSpace = value.length - curLength;
int insertCnt = Math.min(valToInsert.length(), spareSpace + offsetInsideBuf);
if (insertCnt <= 0) {
skipped += valToInsert.length();
return;
}
int bufStartShiftedOffset = full ? (finishAt + 1) % value.length : 0;
int shiftedOffset = (bufStartShiftedOffset + offsetInsideBuf) % value.length;
int moveRightCnt = ((shiftedOffset <= finishAt ? 0 : curLength) + finishAt + 1) - shiftedOffset;
if (!full || offset - skipped > curLength / 2) {
shiftRight(insertCnt, moveRightCnt);
int charsToSkip = Math.max(0, insertCnt - spareSpace);
finishAt = (finishAt + insertCnt) % value.length;
shiftedOffset = (shiftedOffset + insertCnt) % value.length;
full = curLength + insertCnt >= value.length;
insertStringTail(valToInsert, shiftedOffset, insertCnt);
skipped += charsToSkip;
}
else {
int moveLeftCnt = (curLength - moveRightCnt - insertCnt);
shiftLeft(insertCnt, moveLeftCnt);
insertStringTail(valToInsert, shiftedOffset, insertCnt);
skipped += valToInsert.length();
}
}

/**
* @return Count of skipped elements.
*/
public int getSkipped() {
return skipped;
}

/**
* Returns a substring from the logical sequence of characters, accounting for
* the circular buffer structure and any skipped characters.
*
* <p>This method first validates the indices against the total logical length
* (skipped + visible characters). If the requested range is empty or fully within
* the skipped portion, an empty string is returned for efficiency.
*
* <p>It then calculates the physical indices in the internal array. If the
* substring wraps around the end of the circular buffer, it performs a two-part
* copy operation to assemble the result.
*
* @param beginIdx the beginning index, inclusive.
* @param endIdx the ending index, exclusive.
* @return a new String containing the specified subsequence.
* @throws StringIndexOutOfBoundsException if beginIdx or endIdx are negative,
* or if endIdx is greater than the total logical length.
* @throws IllegalArgumentException if beginIdx is greater than endIdx.
*/
public String substring(int beginIdx, int endIdx) {
if (beginIdx < 0 || endIdx < 0 || endIdx > skipped + length())
throw new StringIndexOutOfBoundsException(
"Some of indexes is out of bounds. Begind index = " + beginIdx + " end index = " + endIdx);
if (beginIdx > endIdx)
throw new IllegalArgumentException(
"Begin index can not be greater then end index (begin = " + beginIdx + " end = " + endIdx + ")");
if (endIdx <= skipped || beginIdx == endIdx) return "";
char resultArr[] = new char[Math.max(skipped, endIdx) - Math.max(skipped, beginIdx)];
int effectiveBeginIdx = ((full ? (finishAt + 1) : 0) + Math.max(skipped, beginIdx) - skipped) % value.length;
int effectiveEndIdx = ((full ? (finishAt + 1) : 0) + Math.max(skipped, endIdx) - skipped) % value.length;
if (effectiveBeginIdx >= effectiveEndIdx) {
System.arraycopy(value, effectiveBeginIdx, resultArr, 0, length() - effectiveBeginIdx);
System.arraycopy(value, 0, resultArr, length() - effectiveBeginIdx, effectiveEndIdx);
}
else
System.arraycopy(value, effectiveBeginIdx, resultArr, 0, effectiveEndIdx - effectiveBeginIdx);
return new String(resultArr);
}

/** {@inheritDoc} */
@Override public String toString() {
// Create a copy, don't share the array
Expand All @@ -192,4 +268,53 @@ public int getSkipped() {
else
return new String(value, 0, finishAt + 1);
}

/**
* Performs an in-place rightward shift of elements within the circular buffer.
* This is used to create space for new data by moving a block of existing elements.
* <p>
* The shift is executed in reverse order (from the end of the block to the beginning)
* to prevent overwriting source elements before they are copied.
*
* @param shift The starting offset from the 'finishAt' index, defining the beginning
* of the block to be moved.
* @param moveSteps The number of elements to be shifted to the right.
*/
private void shiftRight(int shift, int moveSteps) {
for (int i = 0; i < moveSteps; i++) {
int pointer = (finishAt + shift - i) % value.length;
value[pointer] = value[(value.length + pointer - shift) % value.length];
}
}

/**
* Performs a leftward shift of elements in the circular buffer.
* Copies elements from a source position to a destination position,
* effectively overwriting a range of values.
* <p>
* @param shift The offset for the source element.
* @param shiftsCnt The count of elements to shift.
*/
private void shiftLeft(int shift, int shiftsCnt) {
for (int i = 0; i < shiftsCnt; i++) {
int pointer = (finishAt + 1 + i) % value.length;
value[pointer] = value[(pointer + shift) % value.length];
}
}

/**
* Inserts a substring from the source string into the buffer at the specified tail position.
* <p>
* The insertion is performed in reverse order (from the last character to the first) to
* prevent overwriting source data in the buffer before it is copied. This is a common
* technique for in-place buffer manipulation.
*
* @param src The source string to copy characters from.
* @param tailEndOffset The physical index in the buffer where the last character will be placed.
* @param insertCnt The number of characters from the source string to insert.
*/
private void insertStringTail(String src, int tailEndOffset, int insertCnt) {
for (int i = 0; i < insertCnt; i++)
value[(value.length + tailEndOffset - i - 1) % value.length] = src.charAt(src.length() - 1 - i);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.ignite.internal.util.tostring;

import org.apache.ignite.internal.util.GridStringBuilder;

import static org.apache.ignite.internal.util.tostring.GridToStringBuilder.COLLECTION_LIMIT;
import static org.apache.ignite.internal.util.tostring.GridToStringNodeFactory.getGridToStringNode;

/**
* A node that represents an array in the string representation.
* It creates nodes for each element of the array and formats the output with square brackets.
*/
class GridToStringArrayNode extends NodeRecursionMonitor {
/** An array of child nodes, each representing an element of the source array. */
private final GridToStringNode[] nodes;

/** The rule for appending a hint about skipped elements if the array is too large. */
private final LongSequenceSkipRule skipRule;

/** The class object representing the type of the array. */
private final Class<?> arrType;

/**
* Constructs a new array node.
* Iterates over the input array, creates a node for each element,
* and populates the internal array up to the collection size limit.
* @param propName The property name.
* @param arr The source array.
* @param arrType The class object of the array's type.
*/
GridToStringArrayNode(String propName, Object[] arr, Class<?> arrType) {
super(propName, arr);
try {
aqcuireRecursionMonitor(this);
this.arrType = arrType;
skipRule = new LongSequenceSkipRule(() -> arr.length);
nodes = new GridToStringNode[Math.min(COLLECTION_LIMIT, arr.length)];
for (int i = 0; i < nodes.length; i++) {
final int idx = i;
nodes[idx] = getGridToStringNode(null, () -> arr[idx], () -> arr[idx].getClass());
}
}
finally {
releaseRecursionMonitor();
}
}

/**
* Appends the string representation of the array to the builder.
* The format is: ArrayType [element1, element2, ...].
* Also appends a hint about skipped elements if necessary.
* @param sb The string builder to append to.
*/
@Override void appendNode(GridStringBuilder sb) {
super.appendNode(sb);
sb.a(arrType.getSimpleName()).a(" [");
for (int i = 0; i < nodes.length - 2; i++) {
nodes[i].appendNode(sb);
sb.a(", ");
}
if (nodes.length > 0)
nodes[nodes.length - 1].appendNode(sb);
skipRule.appendSkippedCountHint(sb);
sb.a("]");
}
}
Loading