diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/util/GridStringBuilder.java b/modules/commons/src/main/java/org/apache/ignite/internal/util/GridStringBuilder.java index 75f0913b0aaf2..19e81896d6f86 100644 --- a/modules/commons/src/main/java/org/apache/ignite/internal/util/GridStringBuilder.java +++ b/modules/commons/src/main/java/org/apache/ignite/internal/util/GridStringBuilder.java @@ -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); @@ -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); @@ -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); @@ -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); diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/util/tostring/CircularStringBuilder.java b/modules/commons/src/main/java/org/apache/ignite/internal/util/tostring/CircularStringBuilder.java index 1bd54ba879b2d..af726f9009eca 100644 --- a/modules/commons/src/main/java/org/apache/ignite/internal/util/tostring/CircularStringBuilder.java +++ b/modules/commons/src/main/java/org/apache/ignite/internal/util/tostring/CircularStringBuilder.java @@ -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[]; @@ -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). * @@ -170,6 +157,56 @@ 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. */ @@ -177,6 +214,45 @@ public int getSkipped() { return skipped; } + /** + * Returns a substring from the logical sequence of characters, accounting for + * the circular buffer structure and any skipped characters. + * + *
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. + * + *
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 @@ -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. + *
+ * 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. + *
+ * @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. + *
+ * 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);
+ }
}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/util/tostring/GridToStringArrayNode.java b/modules/commons/src/main/java/org/apache/ignite/internal/util/tostring/GridToStringArrayNode.java
new file mode 100644
index 0000000000000..df17b422cb77a
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/util/tostring/GridToStringArrayNode.java
@@ -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("]");
+ }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/util/tostring/GridToStringBuilder.java b/modules/commons/src/main/java/org/apache/ignite/internal/util/tostring/GridToStringBuilder.java
index 22a4053afa03f..c700a786852b9 100644
--- a/modules/commons/src/main/java/org/apache/ignite/internal/util/tostring/GridToStringBuilder.java
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/util/tostring/GridToStringBuilder.java
@@ -20,7 +20,9 @@
import java.io.Externalizable;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.PrintWriter;
import java.io.Serializable;
+import java.io.StringWriter;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
@@ -29,7 +31,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.EventListener;
-import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -42,10 +43,8 @@
import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.ignite.IgniteCommonsSystemProperties;
-import org.apache.ignite.IgniteException;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.SB;
-import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static java.util.Objects.nonNull;
@@ -127,27 +126,6 @@ public class GridToStringBuilder {
public static final int COLLECTION_LIMIT =
IgniteCommonsSystemProperties.getInteger(IGNITE_TO_STRING_COLLECTION_LIMIT, DFLT_TO_STRING_COLLECTION_LIMIT);
- /** Every thread has its own string builder. */
- private static ThreadLocal