|
18 | 18 | import static java.lang.Character.isSurrogatePair; |
19 | 19 | import static java.lang.Character.toCodePoint; |
20 | 20 |
|
| 21 | +import java.lang.reflect.Method; |
21 | 22 | import java.nio.BufferOverflowException; |
22 | 23 | import java.nio.ByteBuffer; |
| 24 | +import java.nio.charset.Charset; |
23 | 25 | import java.nio.charset.StandardCharsets; |
24 | 26 | import java.util.Arrays; |
25 | 27 |
|
@@ -52,10 +54,20 @@ final class Utf8 { |
52 | 54 | * depending on what is available on the platform. The processor is the platform-optimized |
53 | 55 | * delegate for which all methods are delegated directly to. |
54 | 56 | */ |
55 | | - private static final Processor processor = |
56 | | - (!Android.isOnAndroidDevice() && UnsafeProcessor.isAvailable()) |
57 | | - ? new UnsafeProcessor() |
58 | | - : new SafeProcessor(); |
| 57 | + private static final Processor processor = createProcessor(); |
| 58 | + |
| 59 | + private static Processor createProcessor() { |
| 60 | + if (Android.isOnAndroidDevice()) { |
| 61 | + return new SafeProcessor(); |
| 62 | + } |
| 63 | + if (UnsafeProcessorWithEncodedLength.isAvailable()) { |
| 64 | + return new UnsafeProcessorWithEncodedLength(); |
| 65 | + } |
| 66 | + if (UnsafeProcessor.isAvailable()) { |
| 67 | + return new UnsafeProcessor(); |
| 68 | + } |
| 69 | + return new SafeProcessor(); |
| 70 | + } |
59 | 71 |
|
60 | 72 | /** |
61 | 73 | * A mask used when performing unsafe reads to determine if a long value contains any non-ASCII |
@@ -139,41 +151,7 @@ private static class UnpairedSurrogateException extends Exception { |
139 | 151 | * time and space. |
140 | 152 | */ |
141 | 153 | static int encodedLength(String string) { |
142 | | - // Warning to maintainers: this implementation is highly optimized. |
143 | | - int utf16Length = string.length(); |
144 | | - int utf8Length = utf16Length; |
145 | | - int i = 0; |
146 | | - |
147 | | - // This loop optimizes for pure ASCII. |
148 | | - while (i < utf16Length && string.charAt(i) < 0x80) { |
149 | | - i++; |
150 | | - } |
151 | | - |
152 | | - // This loop optimizes for chars less than 0x800. |
153 | | - for (; i < utf16Length; i++) { |
154 | | - char c = string.charAt(i); |
155 | | - if (c < 0x800) { |
156 | | - utf8Length += ((0x7f - c) >>> 31); // branch free! |
157 | | - } else { |
158 | | - try { |
159 | | - utf8Length += encodedLengthGeneral(string, i); |
160 | | - } catch (UnpairedSurrogateException e) { |
161 | | - // Our hand rolled loops don't handle unpaired surrogates here. This should be |
162 | | - // exceptionally rare, so we fallback to the naive implementation to find out the |
163 | | - // length that the Java internal implementation will return for this string after |
164 | | - // replacement characters. |
165 | | - return string.getBytes(StandardCharsets.UTF_8).length; |
166 | | - } |
167 | | - break; |
168 | | - } |
169 | | - } |
170 | | - |
171 | | - if (utf8Length < utf16Length) { |
172 | | - // Necessary and sufficient condition for overflow because of maximum 3x expansion |
173 | | - throw new IllegalArgumentException( |
174 | | - "UTF-8 length does not fit in int: " + (utf8Length + (1L << 32))); |
175 | | - } |
176 | | - return utf8Length; |
| 154 | + return processor.encodedLength(string); |
177 | 155 | } |
178 | 156 |
|
179 | 157 | private static int encodedLengthGeneral(String string, int start) |
@@ -543,6 +521,44 @@ final void encodeUtf8(String in, ByteBuffer out) { |
543 | 521 |
|
544 | 522 | /** Encodes the input character sequence to a direct {@link ByteBuffer} instance. */ |
545 | 523 | protected abstract void encodeUtf8Internal(String in, ByteBuffer out); |
| 524 | + |
| 525 | + int encodedLength(String string) { |
| 526 | + // Warning to maintainers: this implementation is highly optimized. |
| 527 | + int utf16Length = string.length(); |
| 528 | + int utf8Length = utf16Length; |
| 529 | + int i = 0; |
| 530 | + |
| 531 | + // This loop optimizes for pure ASCII. |
| 532 | + while (i < utf16Length && string.charAt(i) < 0x80) { |
| 533 | + i++; |
| 534 | + } |
| 535 | + |
| 536 | + // This loop optimizes for chars less than 0x800. |
| 537 | + for (; i < utf16Length; i++) { |
| 538 | + char c = string.charAt(i); |
| 539 | + if (c < 0x800) { |
| 540 | + utf8Length += ((0x7f - c) >>> 31); // branch free! |
| 541 | + } else { |
| 542 | + try { |
| 543 | + utf8Length += encodedLengthGeneral(string, i); |
| 544 | + } catch (UnpairedSurrogateException e) { |
| 545 | + // Our hand rolled loops don't handle unpaired surrogates here. This should be |
| 546 | + // exceptionally rare, so we fallback to the naive implementation to find out the |
| 547 | + // length that the Java internal implementation will return for this string after |
| 548 | + // replacement characters. |
| 549 | + return string.getBytes(StandardCharsets.UTF_8).length; |
| 550 | + } |
| 551 | + break; |
| 552 | + } |
| 553 | + } |
| 554 | + |
| 555 | + if (utf8Length < utf16Length) { |
| 556 | + // Necessary and sufficient condition for overflow because of maximum 3x expansion |
| 557 | + throw new IllegalArgumentException( |
| 558 | + "UTF-8 length does not fit in int: " + (utf8Length + (1L << 32))); |
| 559 | + } |
| 560 | + return utf8Length; |
| 561 | + } |
546 | 562 | } |
547 | 563 |
|
548 | 564 | /** {@link Processor} implementation that does not use any {@code sun.misc.Unsafe} methods. */ |
@@ -832,7 +848,7 @@ private static boolean isValidUtf8NonAscii(byte[] bytes, int index, int limit) { |
832 | 848 | } |
833 | 849 |
|
834 | 850 | /** {@link Processor} that uses {@code sun.misc.Unsafe} where possible to improve performance. */ |
835 | | - static final class UnsafeProcessor extends Processor { |
| 851 | + static class UnsafeProcessor extends Processor { |
836 | 852 | /** Indicates whether or not all required unsafe operations are supported on this platform. */ |
837 | 853 | static boolean isAvailable() { |
838 | 854 | if (!hasUnsafeArrayOperations() || !hasUnsafeByteBufferOperations()) { |
@@ -1206,6 +1222,38 @@ private static int unsafeEstimateConsecutiveAscii(long address, final int maxCha |
1206 | 1222 | } |
1207 | 1223 | } |
1208 | 1224 |
|
| 1225 | + /** |
| 1226 | + * {@link Processor} that extends {@link UnsafeProcessor} and uses {@code |
| 1227 | + * java.lang.String#encodedLength(Charset)} on JDK versions that support it. |
| 1228 | + */ |
| 1229 | + static final class UnsafeProcessorWithEncodedLength extends UnsafeProcessor { |
| 1230 | + |
| 1231 | + private static final Method encodedLengthMethod = createEncodedLengthMethod(); |
| 1232 | + |
| 1233 | + private static Method createEncodedLengthMethod() { |
| 1234 | + try { |
| 1235 | + // This method was added in JDK 27 in https://bugs.openjdk.org/browse/JDK-8372353 |
| 1236 | + // Use reflection to allow compiling against JDK and AOSP versions that don't support it |
| 1237 | + return String.class.getMethod("encodedLength", Charset.class); |
| 1238 | + } catch (ReflectiveOperationException e) { |
| 1239 | + return null; |
| 1240 | + } |
| 1241 | + } |
| 1242 | + |
| 1243 | + static boolean isAvailable() { |
| 1244 | + return encodedLengthMethod != null && UnsafeProcessor.isAvailable(); |
| 1245 | + } |
| 1246 | + |
| 1247 | + @Override |
| 1248 | + int encodedLength(String in) { |
| 1249 | + try { |
| 1250 | + return (int) encodedLengthMethod.invoke(in, StandardCharsets.UTF_8); |
| 1251 | + } catch (ReflectiveOperationException e) { |
| 1252 | + throw new IllegalStateException(e); |
| 1253 | + } |
| 1254 | + } |
| 1255 | + } |
| 1256 | + |
1209 | 1257 | /** |
1210 | 1258 | * Utility methods for decoding bytes into {@link String}. Callers are responsible for extracting |
1211 | 1259 | * bytes (possibly using Unsafe methods), and checking remaining bytes. All other UTF-8 validity |
|
0 commit comments