diff --git a/src/java.base/share/classes/java/util/StringJoiner.java b/src/java.base/share/classes/java/util/StringJoiner.java --- a/src/java.base/share/classes/java/util/StringJoiner.java +++ b/src/java.base/share/classes/java/util/StringJoiner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2020, Oracle and/or its affiliates. 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 @@ -24,6 +24,8 @@ */ package java.util; +import jdk.internal.access.SharedSecrets; + /** * {@code StringJoiner} is used to construct a sequence of characters separated * by a delimiter and optionally starting with a supplied prefix @@ -60,8 +62,8 @@ * * @see java.util.stream.Collectors#joining(CharSequence) * @see java.util.stream.Collectors#joining(CharSequence, CharSequence, CharSequence) - * @since 1.8 -*/ + * @since 1.8 + */ public final class StringJoiner { private final String prefix; private final String delimiter; @@ -77,12 +79,14 @@ private int len; /** - * When overridden by the user to be non-null via {@link setEmptyValue}, the + * When overridden by the user to be non-null via {@link #setEmptyValue}, the * string returned by toString() when no elements have yet been added. * When null, prefix + suffix is used as the empty value. */ private String emptyValue; + private boolean allLatin1; + /** * Constructs a {@code StringJoiner} with no characters in it, with no * {@code prefix} or {@code suffix}, and a copy of the supplied @@ -125,6 +129,7 @@ this.prefix = prefix.toString(); this.delimiter = delimiter.toString(); this.suffix = suffix.toString(); + this.allLatin1 = isLatin1(this.prefix) && isLatin1(this.delimiter) && isLatin1(this.suffix); } /** @@ -144,6 +149,7 @@ public StringJoiner setEmptyValue(CharSequence emptyValue) { this.emptyValue = Objects.requireNonNull(emptyValue, "The empty value must not be null").toString(); + this.allLatin1 &= isLatin1(this.emptyValue); return this; } @@ -153,6 +159,13 @@ return len; } + @SuppressWarnings("deprecation") + private static int getBytes(String s, byte[] bytes, int start) { + int len = s.length(); + s.getBytes(0, len, bytes, start); + return len; + } + /** * Returns the current value, consisting of the {@code prefix}, the values * added so far separated by the {@code delimiter}, and the {@code suffix}, @@ -173,6 +186,13 @@ compactElts(); return size == 0 ? "" : elts[0]; } + if (allLatin1) { + return bytesToString(elts, size, addLen); + } + return charsToString(elts, size, addLen); + } + + private String charsToString(String[] elts, int size, int addLen) { final String delimiter = this.delimiter; final char[] chars = new char[len + addLen]; int k = getChars(prefix, chars, 0); @@ -183,10 +203,25 @@ k += getChars(elts[i], chars, k); } } - k += getChars(suffix, chars, k); + getChars(suffix, chars, k); return new String(chars); } + private String bytesToString(String[] elts, int size, int addLen) { + final String delimiter = this.delimiter; + final byte[] bytes = new byte[len + addLen]; + int k = getBytes(prefix, bytes, 0); + if (size > 0) { + k += getBytes(elts[0], bytes, k); + for (int i = 1; i < size; i++) { + k += getBytes(delimiter, bytes, k); + k += getBytes(elts[i], bytes, k); + } + } + getBytes(suffix, bytes, k); + return new String(bytes); + } + /** * Adds a copy of the given {@code CharSequence} value as the next * element of the {@code StringJoiner} value. If {@code newElement} is @@ -206,6 +241,7 @@ } len += elt.length(); elts[size++] = elt; + allLatin1 &= isLatin1(elt); return this; } @@ -239,18 +275,39 @@ private void compactElts() { if (size > 1) { - final char[] chars = new char[len]; - int i = 1, k = getChars(elts[0], chars, 0); - do { - k += getChars(delimiter, chars, k); - k += getChars(elts[i], chars, k); - elts[i] = null; - } while (++i < size); - size = 1; - elts[0] = new String(chars); + if (allLatin1) { + compactBytes(); + } else { + compactChars(); + } } } + private void compactChars() { + final char[] chars = new char[len]; + int i = 1, k = getChars(elts[0], chars, 0); + do { + k += getChars(delimiter, chars, k); + k += getChars(elts[i], chars, k); + elts[i] = null; + } while (++i < size); + size = 1; + elts[0] = new String(chars); + } + + private void compactBytes() { + final byte[] bytes = new byte[len]; + int i = 1; + int k = getBytes(elts[0], bytes, 0); + do { + k += getBytes(delimiter, bytes, k); + k += getBytes(elts[i], bytes, k); + elts[i] = null; + } while (++i < size); + size = 1; + elts[0] = new String(bytes); + } + /** * Returns the length of the {@code String} representation * of this {@code StringJoiner}. Note that if @@ -265,4 +322,8 @@ return (size == 0 && emptyValue != null) ? emptyValue.length() : len + prefix.length() + suffix.length(); } + + private static boolean isLatin1(String str) { + return SharedSecrets.getJavaLangStringAccess().isLatin1(str); + } } diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangStringAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangStringAccess.java new file mode 100644 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangStringAccess.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. 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 jdk.internal.access; + +/** + * An interface which gives access to internals of {@link String}. + */ +public interface JavaLangStringAccess { + + /** + * Returns true in case all characters of argument {@link String} are ASCII symbols + * + * @param str String to be checked + * @return whether all characters of argument {@link String} are ASCII symbols + */ + boolean isLatin1(String str); + +} diff --git a/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java b/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java --- a/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java +++ b/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2020, Oracle and/or its affiliates. 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 @@ -58,6 +58,7 @@ private static JavaLangModuleAccess javaLangModuleAccess; private static JavaLangRefAccess javaLangRefAccess; private static JavaLangReflectAccess javaLangReflectAccess; + private static JavaLangStringAccess javaLangStringAccess; private static JavaIOAccess javaIOAccess; private static JavaIOFileDescriptorAccess javaIOFileDescriptorAccess; private static JavaIOFilePermissionAccess javaIOFilePermissionAccess; @@ -139,6 +140,17 @@ return javaLangReflectAccess; } + public static JavaLangStringAccess getJavaLangStringAccess() { + if (javaLangStringAccess == null) { + unsafe.ensureClassInitialized(StringAccess.class); + } + return javaLangStringAccess; + } + + public static void setJavaLangStringAccess(JavaLangStringAccess stringAccess) { + javaLangStringAccess = stringAccess; + } + public static void setJavaNetUriAccess(JavaNetUriAccess jnua) { javaNetUriAccess = jnua; } diff --git a/src/java.base/share/classes/jdk/internal/access/StringAccess.java b/src/java.base/share/classes/jdk/internal/access/StringAccess.java new file mode 100644 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/access/StringAccess.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. 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 jdk.internal.access; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Package-private class implementing the + * jdk.internal.access.JavaLangStringAccess interface, + * allowing non-members of java.lang package + * to access internals of {@link java.lang.String}. + * */ +class StringAccess implements jdk.internal.access.JavaLangStringAccess { + + static { + SharedSecrets.setJavaLangStringAccess(new StringAccess()); + } + + private final Method isLatin1; + + StringAccess() { + this.isLatin1 = initIsLatin1Method(); + } + + @Override + public boolean isLatin1(String str) { + try { + return (boolean) isLatin1.invoke(str); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private static Method initIsLatin1Method() { + try { + final Method isLatin1 = String.class.getDeclaredMethod("isLatin1"); + isLatin1.setAccessible(true); + return isLatin1; + } catch (NoSuchMethodException e) { + throw new Error(e); + } + } +}