jdk/src/java.base/share/classes/java/lang/StringConcatHelper.java
Shaojin Wen b0a371b085 8343925: [BACKOUT] JDK-8342650 Move getChars to DecimalDigits
Reviewed-by: jpai, alanb, liach
2024-11-11 15:14:10 +00:00

774 lines
28 KiB
Java

/*
* Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang;
import jdk.internal.misc.Unsafe;
import jdk.internal.util.DecimalDigits;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Stable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
/**
* Helper for string concatenation. These methods are mostly looked up with private lookups
* from {@link java.lang.invoke.StringConcatFactory}, and used in {@link java.lang.invoke.MethodHandle}
* combinators there.
*/
final class StringConcatHelper {
static abstract class StringConcatBase {
@Stable
final String[] constants;
final int length;
final byte coder;
StringConcatBase(String[] constants) {
int length = 0;
byte coder = String.LATIN1;
for (String c : constants) {
length += c.length();
coder |= c.coder();
}
this.constants = constants;
this.length = length;
this.coder = coder;
}
}
static final class Concat1 extends StringConcatBase {
Concat1(String[] constants) {
super(constants);
}
@ForceInline
String concat0(String value) {
int length = stringSize(this.length, value);
byte coder = (byte) (this.coder | value.coder());
byte[] buf = newArray(length << coder);
String prefix = constants[0];
prefix.getBytes(buf, 0, coder);
value.getBytes(buf, prefix.length(), coder);
constants[1].getBytes(buf, prefix.length() + value.length(), coder);
return new String(buf, coder);
}
@ForceInline
String concat(boolean value) {
int length = stringSize(this.length, value);
String suffix = constants[1];
length -= suffix.length();
byte[] buf = newArrayWithSuffix(suffix, length, coder);
prepend(length, coder, buf, value, constants[0]);
return new String(buf, coder);
}
@ForceInline
String concat(char value) {
int length = stringSize(this.length, value);
byte coder = (byte) (this.coder | stringCoder(value));
String suffix = constants[1];
length -= suffix.length();
byte[] buf = newArrayWithSuffix(suffix, length, coder);
prepend(length, coder, buf, value, constants[0]);
return new String(buf, coder);
}
@ForceInline
String concat(int value) {
int length = stringSize(this.length, value);
String suffix = constants[1];
length -= suffix.length();
byte[] buf = newArrayWithSuffix(suffix, length, coder);
prepend(length, coder, buf, value, constants[0]);
return new String(buf, coder);
}
@ForceInline
String concat(long value) {
int length = stringSize(this.length, value);
String suffix = constants[1];
length -= suffix.length();
byte[] buf = newArrayWithSuffix(suffix, length, coder);
prepend(length, coder, buf, value, constants[0]);
return new String(buf, coder);
}
@ForceInline
String concat(Object value) {
return concat0(stringOf(value));
}
@ForceInline
String concat(float value) {
return concat0(Float.toString(value));
}
@ForceInline
String concat(double value) {
return concat0(Double.toString(value));
}
}
private StringConcatHelper() {
// no instantiation
}
/**
* Return the coder for the character.
* @param value character
* @return coder
*/
static long coder(char value) {
return StringLatin1.canEncode(value) ? LATIN1 : UTF16;
}
/**
* Check for overflow, throw exception on overflow.
*
* @param lengthCoder String length with coder packed into higher bits
* the upper word.
* @return the given parameter value, if valid
*/
private static long checkOverflow(long lengthCoder) {
if ((int)lengthCoder >= 0) {
return lengthCoder;
}
throw new OutOfMemoryError("Overflow: String length out of range");
}
/**
* Mix value length and coder into current length and coder.
* @param lengthCoder String length with coder packed into higher bits
* the upper word.
* @param value value to mix in
* @return new length and coder
*/
static long mix(long lengthCoder, boolean value) {
return checkOverflow(lengthCoder + (value ? 4 : 5));
}
/**
* Mix value length and coder into current length and coder.
* @param lengthCoder String length with coder packed into higher bits
* the upper word.
* @param value value to mix in
* @return new length and coder
*/
static long mix(long lengthCoder, char value) {
return checkOverflow(lengthCoder + 1) | coder(value);
}
/**
* Mix value length and coder into current length and coder.
* @param lengthCoder String length with coder packed into higher bits
* the upper word.
* @param value value to mix in
* @return new length and coder
*/
static long mix(long lengthCoder, int value) {
return checkOverflow(lengthCoder + DecimalDigits.stringSize(value));
}
/**
* Mix value length and coder into current length and coder.
* @param lengthCoder String length with coder packed into higher bits
* the upper word.
* @param value value to mix in
* @return new length and coder
*/
static long mix(long lengthCoder, long value) {
return checkOverflow(lengthCoder + DecimalDigits.stringSize(value));
}
/**
* Mix value length and coder into current length and coder.
* @param lengthCoder String length with coder packed into higher bits
* the upper word.
* @param value value to mix in
* @return new length and coder
*/
static long mix(long lengthCoder, String value) {
lengthCoder += value.length();
if (!value.isLatin1()) {
lengthCoder |= UTF16;
}
return checkOverflow(lengthCoder);
}
/**
* Prepends constant and the stringly representation of value into buffer,
* given the coder and final index. Index is measured in chars, not in bytes!
*
* @param indexCoder final char index in the buffer, along with coder packed
* into higher bits.
* @param buf buffer to append to
* @param value boolean value to encode
* @param prefix a constant to prepend before value
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, boolean value, String prefix) {
int index = (int)indexCoder;
if (indexCoder < UTF16) {
if (value) {
index -= 4;
StringLatin1.putCharsAt(buf, index, 't', 'r', 'u', 'e');
} else {
index -= 5;
StringLatin1.putCharsAt(buf, index, 'f', 'a', 'l', 's', 'e');
}
index -= prefix.length();
prefix.getBytes(buf, index, String.LATIN1);
return index;
} else {
if (value) {
index -= 4;
StringUTF16.putCharsAt(buf, index, 't', 'r', 'u', 'e');
} else {
index -= 5;
StringUTF16.putCharsAt(buf, index, 'f', 'a', 'l', 's', 'e');
}
index -= prefix.length();
prefix.getBytes(buf, index, String.UTF16);
return index | UTF16;
}
}
/**
* Prepends constant and the stringly representation of value into buffer,
* given the coder and final index. Index is measured in chars, not in bytes!
*
* @param indexCoder final char index in the buffer, along with coder packed
* into higher bits.
* @param buf buffer to append to
* @param value char value to encode
* @param prefix a constant to prepend before value
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, char value, String prefix) {
int index = (int)indexCoder;
if (indexCoder < UTF16) {
buf[--index] = (byte) (value & 0xFF);
index -= prefix.length();
prefix.getBytes(buf, index, String.LATIN1);
return index;
} else {
StringUTF16.putChar(buf, --index, value);
index -= prefix.length();
prefix.getBytes(buf, index, String.UTF16);
return index | UTF16;
}
}
/**
* Prepends constant and the stringly representation of value into buffer,
* given the coder and final index. Index is measured in chars, not in bytes!
*
* @param indexCoder final char index in the buffer, along with coder packed
* into higher bits.
* @param buf buffer to append to
* @param value int value to encode
* @param prefix a constant to prepend before value
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, int value, String prefix) {
int index = (int)indexCoder;
if (indexCoder < UTF16) {
index = StringLatin1.getChars(value, index, buf);
index -= prefix.length();
prefix.getBytes(buf, index, String.LATIN1);
return index;
} else {
index = StringUTF16.getChars(value, index, buf);
index -= prefix.length();
prefix.getBytes(buf, index, String.UTF16);
return index | UTF16;
}
}
/**
* Prepends constant and the stringly representation of value into buffer,
* given the coder and final index. Index is measured in chars, not in bytes!
*
* @param indexCoder final char index in the buffer, along with coder packed
* into higher bits.
* @param buf buffer to append to
* @param value long value to encode
* @param prefix a constant to prepend before value
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, long value, String prefix) {
int index = (int)indexCoder;
if (indexCoder < UTF16) {
index = StringLatin1.getChars(value, index, buf);
index -= prefix.length();
prefix.getBytes(buf, index, String.LATIN1);
return index;
} else {
index = StringUTF16.getChars(value, index, buf);
index -= prefix.length();
prefix.getBytes(buf, index, String.UTF16);
return index | UTF16;
}
}
/**
* Prepends constant and the stringly representation of value into buffer,
* given the coder and final index. Index is measured in chars, not in bytes!
*
* @param indexCoder final char index in the buffer, along with coder packed
* into higher bits.
* @param buf buffer to append to
* @param value boolean value to encode
* @param prefix a constant to prepend before value
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, String value, String prefix) {
int index = ((int)indexCoder) - value.length();
if (indexCoder < UTF16) {
value.getBytes(buf, index, String.LATIN1);
index -= prefix.length();
prefix.getBytes(buf, index, String.LATIN1);
return index;
} else {
value.getBytes(buf, index, String.UTF16);
index -= prefix.length();
prefix.getBytes(buf, index, String.UTF16);
return index | UTF16;
}
}
/**
* Instantiates the String with given buffer and coder
* @param buf buffer to use
* @param indexCoder remaining index (should be zero) and coder
* @return String resulting string
*/
static String newString(byte[] buf, long indexCoder) {
// Use the private, non-copying constructor (unsafe!)
if (indexCoder == LATIN1) {
return new String(buf, String.LATIN1);
} else if (indexCoder == UTF16) {
return new String(buf, String.UTF16);
} else {
throw new InternalError("Storage is not completely initialized, " +
(int)indexCoder + " bytes left");
}
}
/**
* Perform a simple concatenation between two objects. Added for startup
* performance, but also demonstrates the code that would be emitted by
* {@code java.lang.invoke.StringConcatFactory$MethodHandleInlineCopyStrategy}
* for two Object arguments.
*
* @param first first argument
* @param second second argument
* @return String resulting string
*/
@ForceInline
static String simpleConcat(Object first, Object second) {
String s1 = stringOf(first);
String s2 = stringOf(second);
if (s1.isEmpty()) {
// newly created string required, see JLS 15.18.1
return new String(s2);
}
if (s2.isEmpty()) {
// newly created string required, see JLS 15.18.1
return new String(s1);
}
return doConcat(s1, s2);
}
/**
* Perform a simple concatenation between two non-empty strings.
*
* @param s1 first argument
* @param s2 second argument
* @return String resulting string
*/
@ForceInline
static String doConcat(String s1, String s2) {
byte coder = (byte) (s1.coder() | s2.coder());
int newLength = (s1.length() + s2.length()) << coder;
byte[] buf = newArray(newLength);
s1.getBytes(buf, 0, coder);
s2.getBytes(buf, s1.length(), coder);
return new String(buf, coder);
}
/**
* Produce a String from a concatenation of single argument, which we
* end up using for trivial concatenations like {@code "" + arg}.
*
* This will always create a new Object to comply with JLS {@jls 15.18.1}:
* "The String object is newly created unless the expression is a
* compile-time constant expression".
*
* @param arg the only argument
* @return String resulting string
*/
@ForceInline
static String newStringOf(Object arg) {
return new String(stringOf(arg));
}
/**
* We need some additional conversion for Objects in general, because
* {@code String.valueOf(Object)} may return null. String conversion rules
* in Java state we need to produce "null" String in this case, so we
* provide a customized version that deals with this problematic corner case.
*/
static String stringOf(Object value) {
String s;
return (value == null || (s = value.toString()) == null) ? "null" : s;
}
private static final long LATIN1 = (long)String.LATIN1 << 32;
private static final long UTF16 = (long)String.UTF16 << 32;
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
static String stringOf(float value) {
return Float.toString(value);
}
static String stringOf(double value) {
return Double.toString(value);
}
/**
* return add stringSize of value
* @param length length
* @param value value to add stringSize
* @return new length
*/
static int stringSize(int length, char value) {
return checkOverflow(length + 1);
}
/**
* return add stringSize of value
* @param length length
* @param value value to add stringSize
* @return new length
*/
static int stringSize(int length, boolean value) {
return checkOverflow(length + (value ? 4 : 5));
}
/**
* return add stringSize of value
* @param length length
* @param value value
* @return new length
*/
static int stringSize(int length, int value) {
return checkOverflow(length + DecimalDigits.stringSize(value));
}
/**
* return add stringSize of value
* @param length length
* @param value value to add stringSize
* @return new length
*/
static int stringSize(int length, long value) {
return checkOverflow(length + DecimalDigits.stringSize(value));
}
/**
* return add stringSize of value
* @param length length
* @param value value to add stringSize
* @return new length
*/
static int stringSize(int length, String value) {
return checkOverflow(length + value.length());
}
/**
* Allocates an uninitialized byte array based on the length and coder
* information, then prepends the given suffix string at the end of the
* byte array before returning it. The calling code must adjust the
* indexCoder so that it's taken the coder of the suffix into account, but
* subtracted the length of the suffix.
*
* @param suffix
* @param indexCoder
* @return the newly allocated byte array
*/
@ForceInline
static byte[] newArrayWithSuffix(String suffix, long indexCoder) {
byte[] buf = newArray(indexCoder + suffix.length());
if (indexCoder < UTF16) {
suffix.getBytes(buf, (int)indexCoder, String.LATIN1);
} else {
suffix.getBytes(buf, (int)indexCoder, String.UTF16);
}
return buf;
}
/**
* Allocates an uninitialized byte array based on the length and coder information
* in indexCoder
* @param indexCoder
* @return the newly allocated byte array
*/
@ForceInline
static byte[] newArray(long indexCoder) {
byte coder = (byte)(indexCoder >> 32);
int index = ((int)indexCoder) << coder;
return newArray(index);
}
/**
* Allocates an uninitialized byte array based on the length
* @param length
* @return the newly allocated byte array
*/
@ForceInline
static byte[] newArray(int length) {
if (length < 0) {
throw new OutOfMemoryError("Overflow: String length out of range");
}
return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, length);
}
/**
* Provides the initial coder for the String.
* @return initial coder, adjusted into the upper half
*/
static long initialCoder() {
return String.COMPACT_STRINGS ? LATIN1 : UTF16;
}
static MethodHandle lookupStatic(String name, MethodType methodType) {
try {
return MethodHandles.lookup()
.findStatic(StringConcatHelper.class, name, methodType);
} catch (NoSuchMethodException|IllegalAccessException e) {
throw new AssertionError(e);
}
}
/**
* Allocates an uninitialized byte array based on the length and coder
* information, then prepends the given suffix string at the end of the
* byte array before returning it. The calling code must adjust the
* indexCoder so that it's taken the coder of the suffix into account, but
* subtracted the length of the suffix.
*
* @param suffix
* @param indexCoder
* @return the newly allocated byte array
*/
@ForceInline
static byte[] newArrayWithSuffix(String suffix, int index, byte coder) {
byte[] buf = newArray((index + suffix.length()) << coder);
if (coder == String.LATIN1) {
suffix.getBytes(buf, index, String.LATIN1);
} else {
suffix.getBytes(buf, index, String.UTF16);
}
return buf;
}
/**
* Return the coder for the character.
* @param value character
* @return coder
*/
static byte stringCoder(char value) {
return StringLatin1.canEncode(value) ? String.LATIN1 : String.UTF16;
}
/**
* Prepends constant and the stringly representation of value into buffer,
* given the coder and final index. Index is measured in chars, not in bytes!
*
* @param index final char index in the buffer
* @param coder coder of the buffer
* @param buf buffer to append to
* @param value boolean value to encode
* @param prefix a constant to prepend before value
* @return updated index
*/
static int prepend(int index, byte coder, byte[] buf, boolean value, String prefix) {
if (coder == String.LATIN1) {
if (value) {
index -= 4;
StringLatin1.putCharsAt(buf, index, 't', 'r', 'u', 'e');
} else {
index -= 5;
StringLatin1.putCharsAt(buf, index, 'f', 'a', 'l', 's', 'e');
}
index -= prefix.length();
prefix.getBytes(buf, index, String.LATIN1);
} else {
if (value) {
index -= 4;
StringUTF16.putCharsAt(buf, index, 't', 'r', 'u', 'e');
} else {
index -= 5;
StringUTF16.putCharsAt(buf, index, 'f', 'a', 'l', 's', 'e');
}
index -= prefix.length();
prefix.getBytes(buf, index, String.UTF16);
}
return index;
}
/**
* Prepends constant and the stringly representation of value into buffer,
* given the coder and final index. Index is measured in chars, not in bytes!
*
* @param index final char index in the buffer
* @param coder coder of the buffer
* @param buf buffer to append to
* @param value char value to encode
* @param prefix a constant to prepend before value
* @return updated index
*/
static int prepend(int index, byte coder, byte[] buf, char value, String prefix) {
if (coder == String.LATIN1) {
buf[--index] = (byte) (value & 0xFF);
index -= prefix.length();
prefix.getBytes(buf, index, String.LATIN1);
} else {
StringUTF16.putChar(buf, --index, value);
index -= prefix.length();
prefix.getBytes(buf, index, String.UTF16);
}
return index;
}
/**
* Prepends constant and the stringly representation of value into buffer,
* given the coder and final index. Index is measured in chars, not in bytes!
*
* @param index final char index in the buffer
* @param coder coder of the buffer
* @param buf buffer to append to
* @param value int value to encode
* @param prefix a constant to prepend before value
* @return updated index
*/
static int prepend(int index, byte coder, byte[] buf, int value, String prefix) {
if (coder == String.LATIN1) {
index = StringLatin1.getChars(value, index, buf);
index -= prefix.length();
prefix.getBytes(buf, index, String.LATIN1);
} else {
index = StringUTF16.getChars(value, index, buf);
index -= prefix.length();
prefix.getBytes(buf, index, String.UTF16);
}
return index;
}
/**
* Prepends constant and the stringly representation of value into buffer,
* given the coder and final index. Index is measured in chars, not in bytes!
*
* @param index final char index in the buffer
* @param coder coder of the buffer
* @param buf buffer to append to
* @param value long value to encode
* @param prefix a constant to prepend before value
* @return updated index
*/
static int prepend(int index, byte coder, byte[] buf, long value, String prefix) {
if (coder == String.LATIN1) {
index = StringLatin1.getChars(value, index, buf);
index -= prefix.length();
prefix.getBytes(buf, index, String.LATIN1);
} else {
index = StringUTF16.getChars(value, index, buf);
index -= prefix.length();
prefix.getBytes(buf, index, String.UTF16);
}
return index;
}
/**
* Prepends constant and the stringly representation of value into buffer,
* given the coder and final index. Index is measured in chars, not in bytes!
*
* @param index final char index in the buffer
* @param coder coder of the buffer
* @param buf buffer to append to
* @param value boolean value to encode
* @param prefix a constant to prepend before value
* @return updated index
*/
static int prepend(int index, byte coder, byte[] buf, String value, String prefix) {
index -= value.length();
if (coder == String.LATIN1) {
value.getBytes(buf, index, String.LATIN1);
index -= prefix.length();
prefix.getBytes(buf, index, String.LATIN1);
} else {
value.getBytes(buf, index, String.UTF16);
index -= prefix.length();
prefix.getBytes(buf, index, String.UTF16);
}
return index;
}
/**
* Check for overflow, throw exception on overflow.
*
* @param value
* @return the given parameter value, if valid
*/
@ForceInline
static int checkOverflow(int value) {
if (value >= 0) {
return value;
}
throw new OutOfMemoryError("Overflow: String length out of range");
}
@ForceInline
private static String concat0(String prefix, String str, String suffix) {
byte coder = (byte) (prefix.coder() | str.coder() | suffix.coder());
int len = prefix.length() + str.length();
byte[] buf = newArrayWithSuffix(suffix, len, coder);
prepend(len, coder, buf, str, prefix);
return new String(buf, coder);
}
@ForceInline
static String concat(String prefix, Object value, String suffix) {
if (prefix == null) prefix = "null";
if (suffix == null) suffix = "null";
return concat0(prefix, stringOf(value), suffix);
}
}