1 /* 2 * Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.lang; 27 28 import java.io.UnsupportedEncodingException; 29 import java.lang.ref.SoftReference; 30 import java.nio.ByteBuffer; 31 import java.nio.CharBuffer; 32 import java.nio.charset.Charset; 33 import java.nio.charset.CharsetDecoder; 34 import java.nio.charset.CharsetEncoder; 35 import java.nio.charset.CharacterCodingException; 36 import java.nio.charset.CoderResult; 37 import java.nio.charset.CodingErrorAction; 38 import java.nio.charset.IllegalCharsetNameException; 39 import java.nio.charset.UnsupportedCharsetException; 40 import java.util.Arrays; 41 import jdk.internal.HotSpotIntrinsicCandidate; 42 import sun.nio.cs.HistoricallyNamedCharset; 43 import sun.nio.cs.ArrayDecoder; 44 import sun.nio.cs.ArrayEncoder; 45 import sun.nio.cs.StandardCharsets; 46 47 import static java.lang.String.LATIN1; 48 import static java.lang.String.UTF16; 49 import static java.lang.String.COMPACT_STRINGS; 50 51 /** 52 * Utility class for string encoding and decoding. 53 */ 54 55 class StringCoding { 56 57 private StringCoding() { } 58 59 /** The cached coders for each thread */ 60 private static final ThreadLocal<SoftReference<StringDecoder>> decoder = 61 new ThreadLocal<>(); 62 private static final ThreadLocal<SoftReference<StringEncoder>> encoder = 63 new ThreadLocal<>(); 64 65 private static final Charset ISO_8859_1 = sun.nio.cs.ISO_8859_1.INSTANCE; 66 private static final Charset US_ASCII = sun.nio.cs.US_ASCII.INSTANCE; 67 private static final Charset UTF_8 = sun.nio.cs.UTF_8.INSTANCE; 68 69 private static boolean warnUnsupportedCharset = true; 70 71 private static <T> T deref(ThreadLocal<SoftReference<T>> tl) { 72 SoftReference<T> sr = tl.get(); 73 if (sr == null) 74 return null; 75 return sr.get(); 76 } 77 78 private static <T> void set(ThreadLocal<SoftReference<T>> tl, T ob) { 79 tl.set(new SoftReference<>(ob)); 80 } 81 82 // Trim the given byte array to the given length 83 // 84 private static byte[] safeTrim(byte[] ba, int len, boolean isTrusted) { 85 if (len == ba.length && (isTrusted || System.getSecurityManager() == null)) 86 return ba; 87 else 88 return Arrays.copyOf(ba, len); 89 } 90 91 private static int scale(int len, float expansionFactor) { 92 // We need to perform double, not float, arithmetic; otherwise 93 // we lose low order bits when len is larger than 2**24. 94 return (int)(len * (double)expansionFactor); 95 } 96 97 private static Charset lookupCharset(String csn) { 98 if (Charset.isSupported(csn)) { 99 try { 100 return Charset.forName(csn); 101 } catch (UnsupportedCharsetException x) { 102 throw new Error(x); 103 } 104 } 105 return null; 106 } 107 108 private static void warnUnsupportedCharset(String csn) { 109 if (warnUnsupportedCharset) { 110 // Use err(String) rather than the Logging API or System.err 111 // since this method may be called during VM initialization 112 // before either is available. 113 err("WARNING: Default charset " + csn + 114 " not supported, using ISO-8859-1 instead\n"); 115 warnUnsupportedCharset = false; 116 } 117 } 118 119 static class Result { 120 byte[] value; 121 byte coder; 122 123 Result with() { 124 coder = COMPACT_STRINGS ? LATIN1 : UTF16; 125 value = new byte[0]; 126 return this; 127 } 128 129 Result with(char[] val, int off, int len) { 130 if (String.COMPACT_STRINGS) { 131 byte[] bs = StringUTF16.compress(val, off, len); 132 if (bs != null) { 133 value = bs; 134 coder = LATIN1; 135 return this; 136 } 137 } 138 coder = UTF16; 139 value = StringUTF16.toBytes(val, off, len); 140 return this; 141 } 142 143 Result with(byte[] val, byte coder) { 144 this.coder = coder; 145 value = val; 146 return this; 147 } 148 } 149 150 @HotSpotIntrinsicCandidate 151 public static boolean hasNegatives(byte[] ba, int off, int len) { 152 for (int i = off; i < off + len; i++) { 153 if (ba[i] < 0) { 154 return true; 155 } 156 } 157 return false; 158 } 159 160 // -- Decoding -- 161 static class StringDecoder { 162 private final String requestedCharsetName; 163 private final Charset cs; 164 private final boolean isASCIICompatible; 165 private final CharsetDecoder cd; 166 protected final Result result; 167 168 StringDecoder(Charset cs, String rcn) { 169 this.requestedCharsetName = rcn; 170 this.cs = cs; 171 this.cd = cs.newDecoder() 172 .onMalformedInput(CodingErrorAction.REPLACE) 173 .onUnmappableCharacter(CodingErrorAction.REPLACE); 174 this.result = new Result(); 175 this.isASCIICompatible = (cd instanceof ArrayDecoder) && 176 ((ArrayDecoder)cd).isASCIICompatible(); 177 } 178 179 String charsetName() { 180 if (cs instanceof HistoricallyNamedCharset) 181 return ((HistoricallyNamedCharset)cs).historicalName(); 182 return cs.name(); 183 } 184 185 final String requestedCharsetName() { 186 return requestedCharsetName; 187 } 188 189 Result decode(byte[] ba, int off, int len) { 190 if (len == 0) { 191 return result.with(); 192 } 193 // fastpath for ascii compatible 194 if (isASCIICompatible && !hasNegatives(ba, off, len)) { 195 if (COMPACT_STRINGS) { 196 return result.with(Arrays.copyOfRange(ba, off, off + len), 197 LATIN1); 198 } else { 199 return result.with(StringLatin1.inflate(ba, off, len), UTF16); 200 } 201 } 202 int en = scale(len, cd.maxCharsPerByte()); 203 char[] ca = new char[en]; 204 if (cd instanceof ArrayDecoder) { 205 int clen = ((ArrayDecoder)cd).decode(ba, off, len, ca); 206 return result.with(ca, 0, clen); 207 } 208 cd.reset(); 209 ByteBuffer bb = ByteBuffer.wrap(ba, off, len); 210 CharBuffer cb = CharBuffer.wrap(ca); 211 try { 212 CoderResult cr = cd.decode(bb, cb, true); 213 if (!cr.isUnderflow()) 214 cr.throwException(); 215 cr = cd.flush(cb); 216 if (!cr.isUnderflow()) 217 cr.throwException(); 218 } catch (CharacterCodingException x) { 219 // Substitution is always enabled, 220 // so this shouldn't happen 221 throw new Error(x); 222 } 223 return result.with(ca, 0, cb.position()); 224 } 225 } 226 227 private static class StringDecoder8859_1 extends StringDecoder { 228 StringDecoder8859_1(Charset cs, String rcn) { 229 super(cs, rcn); 230 } 231 Result decode(byte[] ba, int off, int len) { 232 if (COMPACT_STRINGS) { 233 return result.with(Arrays.copyOfRange(ba, off, off + len), LATIN1); 234 } else { 235 return result.with(StringLatin1.inflate(ba, off, len), UTF16); 236 } 237 } 238 } 239 240 static Result decode(String charsetName, byte[] ba, int off, int len) 241 throws UnsupportedEncodingException 242 { 243 StringDecoder sd = deref(decoder); 244 String csn = (charsetName == null) ? "ISO-8859-1" : charsetName; 245 if ((sd == null) || !(csn.equals(sd.requestedCharsetName()) 246 || csn.equals(sd.charsetName()))) { 247 sd = null; 248 try { 249 Charset cs = lookupCharset(csn); 250 if (cs != null) { 251 if (cs == UTF_8) { 252 sd = new StringDecoderUTF8(cs, csn); 253 } else if (cs == ISO_8859_1) { 254 sd = new StringDecoder8859_1(cs, csn); 255 } else { 256 sd = new StringDecoder(cs, csn); 257 } 258 } 259 } catch (IllegalCharsetNameException x) {} 260 if (sd == null) 261 throw new UnsupportedEncodingException(csn); 262 set(decoder, sd); 263 } 264 return sd.decode(ba, off, len); 265 } 266 267 static Result decode(Charset cs, byte[] ba, int off, int len) { 268 // (1)We never cache the "external" cs, the only benefit of creating 269 // an additional StringDe/Encoder object to wrap it is to share the 270 // de/encode() method. These SD/E objects are short-lived, the young-gen 271 // gc should be able to take care of them well. But the best approach 272 // is still not to generate them if not really necessary. 273 // (2)The defensive copy of the input byte/char[] has a big performance 274 // impact, as well as the outgoing result byte/char[]. Need to do the 275 // optimization check of (sm==null && classLoader0==null) for both. 276 // (3)There might be a timing gap in isTrusted setting. getClassLoader0() 277 // is only checked (and then isTrusted gets set) when (SM==null). It is 278 // possible that the SM==null for now but then SM is NOT null later 279 // when safeTrim() is invoked...the "safe" way to do is to redundant 280 // check (... && (isTrusted || SM == null || getClassLoader0())) in trim 281 // but it then can be argued that the SM is null when the operation 282 // is started... 283 if (cs == UTF_8) { 284 return StringDecoderUTF8.decode(ba, off, len, new Result()); 285 } 286 CharsetDecoder cd = cs.newDecoder(); 287 // ascii fastpath 288 if (cs == ISO_8859_1 || ((cd instanceof ArrayDecoder) && 289 ((ArrayDecoder)cd).isASCIICompatible() && 290 !hasNegatives(ba, off, len))) { 291 if (COMPACT_STRINGS) { 292 return new Result().with(Arrays.copyOfRange(ba, off, off + len), 293 LATIN1); 294 } else { 295 return new Result().with(StringLatin1.inflate(ba, off, len), UTF16); 296 } 297 } 298 int en = scale(len, cd.maxCharsPerByte()); 299 if (len == 0) { 300 return new Result().with(); 301 } 302 if (cs.getClass().getClassLoader0() != null && 303 System.getSecurityManager() != null) { 304 ba = Arrays.copyOfRange(ba, off, off + len); 305 off = 0; 306 } 307 cd.onMalformedInput(CodingErrorAction.REPLACE) 308 .onUnmappableCharacter(CodingErrorAction.REPLACE) 309 .reset(); 310 311 char[] ca = new char[en]; 312 if (cd instanceof ArrayDecoder) { 313 int clen = ((ArrayDecoder)cd).decode(ba, off, len, ca); 314 return new Result().with(ca, 0, clen); 315 } 316 ByteBuffer bb = ByteBuffer.wrap(ba, off, len); 317 CharBuffer cb = CharBuffer.wrap(ca); 318 try { 319 CoderResult cr = cd.decode(bb, cb, true); 320 if (!cr.isUnderflow()) 321 cr.throwException(); 322 cr = cd.flush(cb); 323 if (!cr.isUnderflow()) 324 cr.throwException(); 325 } catch (CharacterCodingException x) { 326 // Substitution is always enabled, 327 // so this shouldn't happen 328 throw new Error(x); 329 } 330 return new Result().with(ca, 0, cb.position()); 331 } 332 333 static Result decode(byte[] ba, int off, int len) { 334 String csn = Charset.defaultCharset().name(); 335 try { 336 // use charset name decode() variant which provides caching. 337 return decode(csn, ba, off, len); 338 } catch (UnsupportedEncodingException x) { 339 warnUnsupportedCharset(csn); 340 } 341 try { 342 return decode("ISO-8859-1", ba, off, len); 343 } catch (UnsupportedEncodingException x) { 344 // If this code is hit during VM initialization, err(String) is 345 // the only way we will be able to get any kind of error message. 346 err("ISO-8859-1 charset not available: " + x.toString() + "\n"); 347 // If we can not find ISO-8859-1 (a required encoding) then things 348 // are seriously wrong with the installation. 349 System.exit(1); 350 return null; 351 } 352 } 353 354 // -- Encoding -- 355 private static class StringEncoder { 356 private Charset cs; 357 private CharsetEncoder ce; 358 private final boolean isASCIICompatible; 359 private final String requestedCharsetName; 360 private final boolean isTrusted; 361 362 private StringEncoder(Charset cs, String rcn) { 363 this.requestedCharsetName = rcn; 364 this.cs = cs; 365 this.ce = cs.newEncoder() 366 .onMalformedInput(CodingErrorAction.REPLACE) 367 .onUnmappableCharacter(CodingErrorAction.REPLACE); 368 this.isTrusted = (cs.getClass().getClassLoader0() == null); 369 this.isASCIICompatible = (ce instanceof ArrayEncoder) && 370 ((ArrayEncoder)ce).isASCIICompatible(); 371 } 372 373 String charsetName() { 374 if (cs instanceof HistoricallyNamedCharset) 375 return ((HistoricallyNamedCharset)cs).historicalName(); 376 return cs.name(); 377 } 378 379 final String requestedCharsetName() { 380 return requestedCharsetName; 381 } 382 383 byte[] encode(byte coder, byte[] val) { 384 // fastpath for ascii compatible 385 if (coder == LATIN1 && isASCIICompatible && 386 !hasNegatives(val, 0, val.length)) { 387 return Arrays.copyOf(val, val.length); 388 } 389 int len = val.length >> coder; // assume LATIN1=0/UTF16=1; 390 int en = scale(len, ce.maxBytesPerChar()); 391 byte[] ba = new byte[en]; 392 if (len == 0) { 393 return ba; 394 } 395 if (ce instanceof ArrayEncoder) { 396 if (!isTrusted) { 397 val = Arrays.copyOf(val, val.length); 398 } 399 int blen = (coder == LATIN1 ) ? ((ArrayEncoder)ce).encodeFromLatin1(val, 0, len, ba) 400 : ((ArrayEncoder)ce).encodeFromUTF16(val, 0, len, ba); 401 if (blen != -1) { 402 return safeTrim(ba, blen, isTrusted); 403 } 404 } 405 char[] ca = (coder == LATIN1 ) ? StringLatin1.toChars(val) 406 : StringUTF16.toChars(val); 407 ce.reset(); 408 ByteBuffer bb = ByteBuffer.wrap(ba); 409 CharBuffer cb = CharBuffer.wrap(ca, 0, len); 410 try { 411 CoderResult cr = ce.encode(cb, bb, true); 412 if (!cr.isUnderflow()) 413 cr.throwException(); 414 cr = ce.flush(bb); 415 if (!cr.isUnderflow()) 416 cr.throwException(); 417 } catch (CharacterCodingException x) { 418 // Substitution is always enabled, 419 // so this shouldn't happen 420 throw new Error(x); 421 } 422 return safeTrim(ba, bb.position(), isTrusted); 423 } 424 } 425 426 @HotSpotIntrinsicCandidate 427 private static int implEncodeISOArray(byte[] sa, int sp, 428 byte[] da, int dp, int len) { 429 int i = 0; 430 for (; i < len; i++) { 431 char c = StringUTF16.getChar(sa, sp++); 432 if (c > '\u00FF') 433 break; 434 da[dp++] = (byte)c; 435 } 436 return i; 437 } 438 439 static byte[] encode8859_1(byte coder, byte[] val) { 440 if (coder == LATIN1) { 441 return Arrays.copyOf(val, val.length); 442 } 443 int len = val.length >> 1; 444 byte[] dst = new byte[len]; 445 int dp = 0; 446 int sp = 0; 447 int sl = len; 448 while (sp < sl) { 449 int ret = implEncodeISOArray(val, sp, dst, dp, len); 450 sp = sp + ret; 451 dp = dp + ret; 452 if (ret != len) { 453 char c = StringUTF16.getChar(val, sp++); 454 if (Character.isHighSurrogate(c) && sp < sl && 455 Character.isLowSurrogate(StringUTF16.getChar(val, sp))) { 456 sp++; 457 } 458 dst[dp++] = '?'; 459 len = sl - sp; 460 } 461 } 462 if (dp == dst.length) { 463 return dst; 464 } 465 return Arrays.copyOf(dst, dp); 466 } 467 468 static byte[] encodeASCII(byte coder, byte[] val) { 469 if (coder == LATIN1) { 470 byte[] dst = new byte[val.length]; 471 for (int i = 0; i < val.length; i++) { 472 if (val[i] < 0) { 473 dst[i] = '?'; 474 } else { 475 dst[i] = val[i]; 476 } 477 } 478 return dst; 479 } 480 int len = val.length >> 1; 481 byte[] dst = new byte[len]; 482 int dp = 0; 483 for (int i = 0; i < len; i++) { 484 char c = StringUTF16.getChar(val, i); 485 if (c < 0x80) { 486 dst[dp++] = (byte)c; 487 continue; 488 } 489 if (Character.isHighSurrogate(c) && i + 1 < len && 490 Character.isLowSurrogate(StringUTF16.getChar(val, i + 1))) { 491 i++; 492 } 493 dst[dp++] = '?'; 494 } 495 if (len == dp) { 496 return dst; 497 } 498 return Arrays.copyOf(dst, dp); 499 } 500 501 static byte[] encodeUTF8(byte coder, byte[] val) { 502 int dp = 0; 503 byte[] dst; 504 if (coder == LATIN1) { 505 dst = new byte[val.length << 1]; 506 for (int sp = 0; sp < val.length; sp++) { 507 byte c = val[sp]; 508 if (c < 0) { 509 dst[dp++] = (byte)(0xc0 | ((c & 0xff) >> 6)); 510 dst[dp++] = (byte)(0x80 | (c & 0x3f)); 511 } else { 512 dst[dp++] = c; 513 } 514 } 515 } else { 516 int sp = 0; 517 int sl = val.length >> 1; 518 dst = new byte[sl * 3]; 519 char c; 520 while (sp < sl && (c = StringUTF16.getChar(val, sp)) < '\u0080') { 521 // ascii fast loop; 522 dst[dp++] = (byte)c; 523 sp++; 524 } 525 while (sp < sl) { 526 c = StringUTF16.getChar(val, sp++); 527 if (c < 0x80) { 528 dst[dp++] = (byte)c; 529 } else if (c < 0x800) { 530 dst[dp++] = (byte)(0xc0 | (c >> 6)); 531 dst[dp++] = (byte)(0x80 | (c & 0x3f)); 532 } else if (Character.isSurrogate(c)) { 533 int uc = -1; 534 char c2; 535 if (Character.isHighSurrogate(c) && sp < sl && 536 Character.isLowSurrogate(c2 = StringUTF16.getChar(val, sp))) { 537 uc = Character.toCodePoint(c, c2); 538 } 539 if (uc < 0) { 540 dst[dp++] = '?'; 541 } else { 542 dst[dp++] = (byte)(0xf0 | ((uc >> 18))); 543 dst[dp++] = (byte)(0x80 | ((uc >> 12) & 0x3f)); 544 dst[dp++] = (byte)(0x80 | ((uc >> 6) & 0x3f)); 545 dst[dp++] = (byte)(0x80 | (uc & 0x3f)); 546 sp++; // 2 chars 547 } 548 } else { 549 // 3 bytes, 16 bits 550 dst[dp++] = (byte)(0xe0 | ((c >> 12))); 551 dst[dp++] = (byte)(0x80 | ((c >> 6) & 0x3f)); 552 dst[dp++] = (byte)(0x80 | (c & 0x3f)); 553 } 554 } 555 } 556 if (dp == dst.length) { 557 return dst; 558 } 559 return Arrays.copyOf(dst, dp); 560 } 561 562 static byte[] encode(String charsetName, byte coder, byte[] val) 563 throws UnsupportedEncodingException 564 { 565 StringEncoder se = deref(encoder); 566 String csn = (charsetName == null) ? "ISO-8859-1" : charsetName; 567 if ((se == null) || !(csn.equals(se.requestedCharsetName()) 568 || csn.equals(se.charsetName()))) { 569 se = null; 570 try { 571 Charset cs = lookupCharset(csn); 572 if (cs != null) { 573 if (cs == UTF_8) { 574 return encodeUTF8(coder, val); 575 } else if (cs == ISO_8859_1) { 576 return encode8859_1(coder, val); 577 } else if (cs == US_ASCII) { 578 return encodeASCII(coder, val); 579 } 580 se = new StringEncoder(cs, csn); 581 } 582 } catch (IllegalCharsetNameException x) {} 583 if (se == null) { 584 throw new UnsupportedEncodingException (csn); 585 } 586 set(encoder, se); 587 } 588 return se.encode(coder, val); 589 } 590 591 static byte[] encode(Charset cs, byte coder, byte[] val) { 592 if (cs == UTF_8) { 593 return encodeUTF8(coder, val); 594 } else if (cs == ISO_8859_1) { 595 return encode8859_1(coder, val); 596 } else if (cs == US_ASCII) { 597 return encodeASCII(coder, val); 598 } 599 CharsetEncoder ce = cs.newEncoder(); 600 // fastpath for ascii compatible 601 if (coder == LATIN1 && (((ce instanceof ArrayEncoder) && 602 ((ArrayEncoder)ce).isASCIICompatible() && 603 !hasNegatives(val, 0, val.length)))) { 604 return Arrays.copyOf(val, val.length); 605 } 606 int len = val.length >> coder; // assume LATIN1=0/UTF16=1; 607 int en = scale(len, ce.maxBytesPerChar()); 608 byte[] ba = new byte[en]; 609 if (len == 0) { 610 return ba; 611 } 612 boolean isTrusted = cs.getClass().getClassLoader0() == null || 613 System.getSecurityManager() == null; 614 ce.onMalformedInput(CodingErrorAction.REPLACE) 615 .onUnmappableCharacter(CodingErrorAction.REPLACE) 616 .reset(); 617 if (ce instanceof ArrayEncoder) { 618 if (!isTrusted) { 619 val = Arrays.copyOf(val, val.length); 620 } 621 int blen = (coder == LATIN1 ) ? ((ArrayEncoder)ce).encodeFromLatin1(val, 0, len, ba) 622 : ((ArrayEncoder)ce).encodeFromUTF16(val, 0, len, ba); 623 if (blen != -1) { 624 return safeTrim(ba, blen, isTrusted); 625 } 626 } 627 char[] ca = (coder == LATIN1 ) ? StringLatin1.toChars(val) 628 : StringUTF16.toChars(val); 629 ByteBuffer bb = ByteBuffer.wrap(ba); 630 CharBuffer cb = CharBuffer.wrap(ca, 0, len); 631 try { 632 CoderResult cr = ce.encode(cb, bb, true); 633 if (!cr.isUnderflow()) 634 cr.throwException(); 635 cr = ce.flush(bb); 636 if (!cr.isUnderflow()) 637 cr.throwException(); 638 } catch (CharacterCodingException x) { 639 throw new Error(x); 640 } 641 return safeTrim(ba, bb.position(), isTrusted); 642 } 643 644 static byte[] encode(byte coder, byte[] val) { 645 String csn = Charset.defaultCharset().name(); 646 try { 647 // use charset name encode() variant which provides caching. 648 return encode(csn, coder, val); 649 } catch (UnsupportedEncodingException x) { 650 warnUnsupportedCharset(csn); 651 } 652 try { 653 return encode("ISO-8859-1", coder, val); 654 } catch (UnsupportedEncodingException x) { 655 // If this code is hit during VM initialization, err(String) is 656 // the only way we will be able to get any kind of error message. 657 err("ISO-8859-1 charset not available: " + x.toString() + "\n"); 658 // If we can not find ISO-8859-1 (a required encoding) then things 659 // are seriously wrong with the installation. 660 System.exit(1); 661 return null; 662 } 663 } 664 665 /** 666 * Print a message directly to stderr, bypassing all character conversion 667 * methods. 668 * @param msg message to print 669 */ 670 private static native void err(String msg); 671 }