changeset 49148:3d46472f292f raw-string-literal

0000000: Raw String Literals in corelibs
author jlaskey
date Thu, 15 Feb 2018 11:26:45 -0400
parents 452e6e327383
children 4d6cca28dbe0
files src/java.base/share/classes/java/lang/MalformedEscapeException.java src/java.base/share/classes/java/lang/String.java src/java.base/share/classes/java/lang/StringLatin1.java src/java.base/share/classes/java/lang/StringUTF16.java
diffstat 4 files changed, 789 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/lang/MalformedEscapeException.java	Thu Feb 15 11:26:45 2018 -0400
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018, 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 java.lang;
+
+/**
+ * Unchecked exception thrown when a string escape sequence does not
+ * adhere to escape rules defined in sections 3.3 and 3.10.6 of the
+ * <cite>The Java&trade; Language Specification</cite>.
+ *
+ * @since 1.11
+ */
+public class MalformedEscapeException extends IllegalArgumentException {
+
+    private static final long serialVersionUID = 0L;
+
+    // package-private to prevent explicit instantiation
+    MalformedEscapeException() { }
+}
--- a/src/java.base/share/classes/java/lang/String.java	Thu Feb 15 10:47:38 2018 -0400
+++ b/src/java.base/share/classes/java/lang/String.java	Thu Feb 15 11:26:45 2018 -0400
@@ -2636,6 +2636,210 @@
     }
 
     /**
+     * Returns a string whose value is this string, with any leading
+     * whitespace removed.
+     * <p>
+     * If this {@code String} object represents an empty character
+     * sequence, or the first characters of character sequence
+     * represented by this {@code String} object have codes
+     * greater than {@code '\u005Cu0020'} (the space character), then a
+     * reference to this {@code String} object is returned.
+     * <p>
+     * Otherwise, if there is no character with a code greater than
+     * {@code '\u005Cu0020'} in the string, then a
+     * {@code String} object representing an empty string is
+     * returned.
+     * <p>
+     * Otherwise, let <i>k</i> be the index of the first character in the
+     * string whose code is greater than {@code '\u005Cu0020'}, and let
+     * <i>m</i> be the index of the last character in the string. A
+     * {@code String} object is returned, representing the substring of this
+     * string that begins with the character at index <i>k</i> and ends with the
+     * character at index <i>m</i>-that is, the result of
+     * {@code this.substring(k, m + 1)}.
+     * <p>
+     * This method may be used to trim whitespace (as defined above) from
+     * the beginning of a string.
+     *
+     * @return  A string whose value is this string, with any leading white
+     *          space removed, or this string if it has no leading white space.
+     * @since 10
+     */
+    public String trimLeft() {
+        String ret = isLatin1() ? StringLatin1.trimLeft(value)
+                                : StringUTF16.trimLeft(value);
+        return ret == null ? this : ret;
+    }
+
+    /**
+     * Returns a string whose value is this string, with any trailing
+     * whitespace removed.
+     * <p>
+     * If this {@code String} object represents an empty character
+     * sequence, or the last characters of character sequence
+     * represented by this {@code String} object have codes
+     * greater than {@code '\u005Cu0020'} (the space character), then a
+     * reference to this {@code String} object is returned.
+     * <p>
+     * Otherwise, if there is no character with a code greater than
+     * {@code '\u005Cu0020'} in the string, then a
+     * {@code String} object representing an empty string is
+     * returned.
+     * <p>
+     * Otherwise, let <i>k</i> be the index of the first character in the
+     * string, and let
+     * <i>m</i> be the index of the last character in the string whose code
+     * is greater than {@code '\u005Cu0020'}. A {@code String}
+     * object is returned, representing the substring of this string that
+     * begins with the character at index <i>k</i> and ends with the
+     * character at index <i>m</i>-that is, the result of
+     * {@code this.substring(k, m + 1)}.
+     * <p>
+     * This method may be used to trim whitespace (as defined above) from
+     * the end of a string.
+     *
+     * @return  A string whose value is this string, with any trailing white
+     *          space removed, or this string if it has no
+     *          trailing white space.
+     * @since 10
+     */
+    public String trimRight() {
+        String ret = isLatin1() ? StringLatin1.trimRight(value)
+                                : StringUTF16.trimRight(value);
+        return ret == null ? this : ret;
+    }
+
+    private int skipLeadingSpaces() {
+        if (isLatin1()) {
+            return StringLatin1.skipLeadingSpaces(value);
+        } else {
+            return StringUTF16.skipLeadingSpaces(value) >> 1;
+        }
+    }
+
+    private String trimJoin(String[] lines) {
+        int length = lines.length;
+        StringBuilder sb = new StringBuilder(length());
+        int first = lines[0].isEmpty() ? 1 : 0;
+        int last = lines[length - 1].isEmpty() ? length - 1 : length;
+        for (int i = first; i < last; i++) {
+            if (i != first) {
+                sb.append('\n');
+            }
+            sb.append(lines[i]);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * When applied to multi-line strings, removes trailing spaces
+     * from each line.
+     * <p>
+     * The new line terminator should {@code '\n'}.
+     * <p>
+     * If the first line is blank then it is removed.
+     * <p>
+     * If the last line is blank then it is removed.
+     * @return String with trailing spaces removed.
+     * @since 10
+     */
+    public java.lang.String trimLines() {
+        if (isEmpty()) {
+            return this;
+        }
+        java.lang.String[] lines = split("\n");
+        for (int i = 0; i < lines.length; i++) {
+            lines[i] = lines[i].trimRight();
+        }
+        return trimJoin(lines);
+    }
+
+    /**
+     * When applied to multi-line strings, determines the minimal number of
+     * leading spaces of each line and then removes that minimum from each
+     * line.
+     * <p>
+     * The new line terminator should {@code '\n'}.
+     * <p>
+     * If the first line is blank then it is removed.
+     * <p>
+     * If the last line is blank then it is removed.
+     * @return String with indent removed.
+     * @since 10
+     */
+    public String trimIndent() {
+        if (isEmpty()) {
+            return this;
+        }
+        String[] lines = split("\n");
+        int count = lines.length;
+        int least = Integer.MAX_VALUE;
+        for (int i = 0; i < count; i++) {
+            String line = lines[i].trimRight();
+            if (i == 0) {
+                // ignore
+            } else if (!line.isEmpty()) {
+                least = Integer.min(least, line.skipLeadingSpaces());
+            } else if (i == count - 1) {
+                least = Integer.min(least, lines[i].length());
+            }
+            lines[i] = line;
+        }
+        if (least != 0 && least != Integer.MAX_VALUE) {
+            for (int i = 1; i < count; i++) {
+                String line = lines[i];
+                if (!line.isEmpty()) {
+                    lines[i] = line.substring(least);
+                }
+             }
+        }
+        return trimJoin(lines);
+    }
+
+    /**
+     * When applied to multi-line strings, removes leading whitespace up to
+     * marker from each line.
+     * <p>
+     * The new line terminator should {@code '\n'}.
+     * <p>
+     * If the first line is blank then it is removed.
+     * <p>
+     * If the last line is blank then it is removed.
+     * @param marker String representing margin indicator.
+     * @return String with margin removed.
+     * @since 10
+     */
+    public String trimMargin(String marker) {
+        if (isEmpty()) {
+            return this;
+        }
+        String[] lines = split("\n");
+        int count = lines.length;
+        int trim = marker.length();
+        for (int i = 0; i < count; i++) {
+            String line = lines[i].trimLeft();
+            if (line.startsWith(marker)) {
+                line = line.substring(trim);
+            }
+            lines[i] = line.trimRight();
+        }
+        return trimJoin(lines);
+    }
+
+    /**
+     * When applied to multi-line strings, removes leading whitespace up to
+     * the first "|" from each line.
+     * <p>
+     * If the first line is blank then it is removed.
+     * <p>
+     * If the last line is blank then it is removed.
+     * @return String with margin removed.
+     */
+    public String trimMargin() {
+        return trimMargin("|");
+    }
+
+    /**
      * This object (which is already a string!) is itself returned.
      *
      * @return  the string itself.
@@ -2983,6 +3187,180 @@
         }
     }
 
+    private String unescape(boolean backslash, boolean unicode) throws MalformedEscapeException {
+        if (isLatin1()) {
+            return StringLatin1.unescape(value, backslash, unicode);
+        } else {
+            return StringUTF16.unescape(toCharArray(), backslash, unicode);
+        }
+    }
+
+    /**
+     * Translates all escapes in the string into representations specified
+     * in sections 3.3 and 3.10.6 of the <cite>The Java&trade; Language
+     * Specification</cite>.
+     * <p>
+     * Each  unicode escape in the form {@code \u005Cunnnn} is translated to
+     * the unicode character whose code point is {@code 0xnnnn}. Care should be
+     * taken when using UTF-16 surrogate pairs to ensure that the high
+     * surrogate ({@code \u005CuD800..\u005CuDBFF}) is immediately followed by
+     * a low surrogate ({@code \u005CuDC00..\u005CuDFFF}) otherwise a {@link
+     * java.nio.charset.CharacterCodingException} may occur during UTF-8
+     * decoding.
+     * <p>
+     * Backslash escape sequences are translated as follows;
+     * <table class="plain">
+     *   <caption style="display:none">Escape sequences</caption>
+     *   <thead>
+     *   <tr>
+     *     <th scope="col">Escape</th>
+     *     <th scope="col">Name</th>
+     *     <th scope="col">Unicode</th>
+     *   </tr>
+     *   </thead>
+     *   <tr>
+     *     <td>{@code \u005Cb}</td>
+     *     <td>backspace</td>
+     *     <td>{@code \u005Cu0008}</td>
+     *   </tr>
+     *   <tr>
+     *     <td>{@code \u005Ct}</td>
+     *     <td>horizontal tab</td>
+     *     <td>{@code \u005Cu0009}</td>
+     *   </tr>
+     *   <tr>
+     *     <td>{@code \u005Cn}</td>
+     *     <td>linefeed</td>
+     *     <td>{@code \u005Cu000A}</td>
+     *   </tr>
+     *   <tr>
+     *     <td>{@code \u005Cf}</td>
+     *     <td>form feed</td>
+     *     <td>{@code \u005Cu000C}</td>
+     *   </tr>
+     *   <tr>
+     *     <td>{@code \u005Cr}</td>
+     *     <td>carriage return</td>
+     *     <td>{@code \u005Cu000D}</td>
+     *   </tr>
+     *   <tr>
+     *     <td>{@code \u005C"}</td>
+     *     <td>double quote</td>
+     *     <td>{@code \u005Cu0022}</td>
+     *   </tr>
+     *   <tr>
+     *     <td>{@code \u005C'}</td>
+     *     <td>single quote</td>
+     *     <td>{@code \u005Cu0027}</td>
+     *   </tr>
+     *   <tr>
+     *     <td>{@code \u005C\u005C}</td>
+     *     <td>backslash</td>
+     *     <td>{@code \u005Cu005C}</td>
+     *   </tr>
+     * </table>
+     * <p>
+     * Octal escapes \u005C0 - \u005C377 are translated to their code
+     * point equivalents.
+     * @throws MalformedEscapeException when escape sequence is malformed.
+     * @return String with all escapes translated.
+     */
+    public String unescape() throws MalformedEscapeException {
+        return unescape(true, true);
+    }
+
+    /**
+     * Translates all non-unicode escapes in the string into representations
+     * specified in section 3.10.6 of the <cite>The Java&trade; Language
+     * Specification</cite>.
+     * <p>
+     * Backslash escape sequences are translated following the table at
+     * {@link #unescape()}.
+     * <p>
+     * Octal escapes {@code \u005C0..\u005C377} are translated to their
+     * code point equivalents.
+     * @throws MalformedEscapeException when escape sequence is malformed.
+     * @return String with all non-unicode escapes translated.
+     */
+    public String unescapeBackslash() throws MalformedEscapeException {
+        return unescape(true, false);
+    }
+
+    /**
+     * Translates all unicode escapes in the string into representations
+     * specified in section 3.3 of the <cite>The Java&trade; Language
+     * Specification</cite>.
+     * <p>
+     * Each  unicode escape in the form {@code \u005Cunnnn} is translated to
+     * the unicode character whose code point is {@code 0xnnnn}. Care should be
+     * taken when using UTF-16 surrogate pairs to ensure that the high
+     * surrogate ({@code \u005CuD800..\u005CuDBFF}) is immediately followed by
+     * a low surrogate ({@code \u005CuDC00..\u005CuDFFF}) otherwise a {@link
+     * java.nio.charset.CharacterCodingException} may occur during UTF-8
+     * decoding.
+     * @throws MalformedEscapeException when escape sequence is malformed.
+     * @return String with all escapes unicode translated.
+     */
+    public String unescapeUnicode() throws MalformedEscapeException {
+        return unescape(false, true);
+    }
+
+    private String escape(boolean backslash, boolean unicode) {
+        String result;
+        if (isLatin1()) {
+            result = StringLatin1.escape(value, backslash, unicode);
+        } else {
+            result = StringUTF16.escape(toCharArray(), backslash, unicode);
+        }
+        return result == null ? this : result;
+    }
+
+    /**
+     * Translates characters in the string that have a code point outside the
+     * range {@code \u005Cu0020..\u005Cu007F} into representations specified in
+     * sections 3.3 and 3.10.6 of the <cite>The Java&trade; Language
+     * Specification</cite>.
+     * <p>
+     * Characters in the code point range {@code \u005Cu0080..\u005CuFFFF} are
+     * translated into unicode escapes.
+     * <p>
+     * Characters in the code point range {@code \u005Cu0000..\u005Cu001F} are
+     * translated into unicode escapes unless they have a character escape
+     * sequence found in the table at {@link #unescape()}.
+     * @return String with translated escape sequences.
+     */
+    public String escape() {
+        return escape(true, true);
+    }
+
+    /**
+     * Translates characters in the string that have a code point in the range
+     * {@code \u005Cu0000..\u005Cu001F} into representations specified in
+     * section 3.10.6 of the <cite>The Java&trade; Language
+     * Specification</cite>.
+     * <p>
+     * Characters in the code point range {@code \u005Cu0000..\u005Cu001F} are
+     * translated into unicode escapes unless they have a character escape
+     * sequence found in the table at {@link #unescape()}.
+     * @return String with translated escape sequences.
+     */
+    public String escapeBackslash() {
+        return escape(true, false);
+    }
+
+    /**
+     * Translates characters in the string that have a code point in the range
+     * {@code \u005Cu0080..\u005CuFFFF} into representations specified in
+     * section 3.3 of the <cite>The Java&trade; Language Specification</cite>.
+     * <p>
+     * Characters in the code point range {@code \u005Cu0080..\u005CuFFFF} are
+     * translated into unicode escapes.
+     * @return String with translated escape sequences.
+     */
+     public String escapeUnicode() {
+        return escape(false, true);
+    }
+
     /*
      * Package private constructor. Trailing Void argument is there for
      * disambiguating it against other (public) constructors.
--- a/src/java.base/share/classes/java/lang/StringLatin1.java	Thu Feb 15 10:47:38 2018 -0400
+++ b/src/java.base/share/classes/java/lang/StringLatin1.java	Thu Feb 15 11:26:45 2018 -0400
@@ -26,6 +26,7 @@
 package java.lang;
 
 import java.util.Arrays;
+import java.util.Formatter;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.Spliterator;
@@ -507,17 +508,48 @@
         return StringUTF16.newString(result, 0, resultOffset);
     }
 
+    public static int skipLeadingSpaces(byte[] value) {
+        int left = 0;
+        while ((left < value.length) && ((value[left] & 0xff) <= ' ')) {
+            left++;
+        }
+        return left;
+    }
+
+    public static int skipTrailingSpaces(byte[] value) {
+        int right = value.length;
+        while ((0 < right) && ((value[right - 1] & 0xff) <= ' ')) {
+            right--;
+        }
+        return right;
+    }
+
     public static String trim(byte[] value) {
-        int len = value.length;
-        int st = 0;
-        while ((st < len) && ((value[st] & 0xff) <= ' ')) {
-            st++;
+        int left = skipLeadingSpaces(value);
+        if (left == value.length) {
+            return "";
         }
-        while ((st < len) && ((value[len - 1] & 0xff) <= ' ')) {
-            len--;
+        int right = skipTrailingSpaces(value);
+        return ((left > 0) || (right < value.length)) ?
+                newString(value, left, right - left) : null;
+    }
+
+    public static String trimLeft(byte[] value) {
+        int left = skipLeadingSpaces(value);
+        if (left == value.length) {
+            return "";
         }
-        return ((st > 0) || (len < value.length)) ?
-            newString(value, st, len - st) : null;
+        return (left != 0) ?
+                newString(value, left, value.length - left) : null;
+    }
+
+    public static String trimRight(byte[] value) {
+        int right = skipTrailingSpaces(value);
+        if (right == 0) {
+            return "";
+        }
+        return (right != value.length) ?
+                newString(value, 0, right) : null;
     }
 
     public static void putChar(byte[] val, int index, int c) {
@@ -568,6 +600,148 @@
         StringUTF16.inflate(src, srcOff, dst, dstOff, len);
     }
 
+    static String unescape(byte[] value, boolean backslash, boolean unicode)
+            throws MalformedEscapeException {
+        int length = value.length;
+        byte[] chars = new byte[length];
+
+        int from = 0;
+        int to = 0;
+
+        while (from < length) {
+            char ch = (char)value[from++];
+            if (ch == '\\' && from < length) {
+                ch = (char)value[from++];
+                if (unicode && ch == 'u') {
+                    while (from < length && (char)value[from] == 'u') {
+                        from++;
+                    }
+                    if (length <= from + 3) {
+                        throw new MalformedEscapeException();
+                    }
+                    int code = (Character.digit(value[from + 0], 16) << 12) |
+                               (Character.digit(value[from + 1], 16) <<  8) |
+                               (Character.digit(value[from + 2], 16) <<  4) |
+                                Character.digit(value[from + 3], 16);
+                    if (code < 0) {
+                        throw new MalformedEscapeException();
+                    }
+                    if (canEncode(code)) {
+                        ch = (char)code;
+                        from += 4;
+                    } else {
+                        return StringUTF16.unescape(toChars(value), backslash, unicode);
+                    }
+                } else if (backslash) {
+                    switch (ch) {
+                    case 'b':
+                        ch = '\b';
+                        break;
+                    case 'f':
+                        ch = '\f';
+                        break;
+                    case 'n':
+                        ch = '\n';
+                        break;
+                    case 'r':
+                        ch = '\r';
+                        break;
+                    case 't':
+                        ch = '\t';
+                        break;
+                    case 'u':
+                        chars[to++] = (byte)'\\';
+                        break;
+                    case '0': case '1': case '2': case '3':
+                    case '4': case '5': case '6': case '7':
+                        int code = ch - '0';
+                        for (int i = 0; i < 2 && from < length; i++) {
+                            int digit = Character.digit(value[from], 8);
+                            if (digit < 0) {
+                                break;
+                            }
+                            from++;
+                            code = code << 3 | digit;
+                        }
+                        if (0377 < code) {
+                            throw new MalformedEscapeException();
+                        }
+                        ch = (char)code;
+                        break;
+                    default:
+                        throw new MalformedEscapeException();
+                    }
+                } else {
+                    chars[to++] = (byte)'\\';
+                }
+            }
+
+            chars[to++] = (byte)ch;
+        }
+
+        return new String(Arrays.copyOfRange(chars, 0, to), LATIN1);
+    }
+
+    static String escape(byte[] value, boolean backslash, boolean unicode) {
+        if (!backslash) {
+            return null;
+        }
+        int length = value.length;
+        StringBuilder sb = new StringBuilder(value.length);
+        Formatter fmt = null;
+        for (int i = 0; i < length; i++) {
+            char ch = (char)value[i];
+            switch (ch) {
+            case '\b':
+                sb.append('\\');
+                sb.append('b');
+                break;
+            case '\f':
+                sb.append('\\');
+                sb.append('f');
+                break;
+            case '\n':
+                sb.append('\\');
+                sb.append('n');
+                break;
+            case '\r':
+                sb.append('\\');
+                sb.append('r');
+                break;
+            case '\t':
+                sb.append('\\');
+                sb.append('t');
+                break;
+            case '\\':
+                sb.append('\\');
+                sb.append('\\');
+                break;
+            case '\"':
+                sb.append('\\');
+                sb.append('\"');
+                break;
+            case '\'':
+                sb.append('\\');
+                sb.append('\'');
+                break;
+            default:
+                if (' ' <= ch && ch <= '~') {
+                    sb.append(ch);
+                } else {
+                    if (fmt == null) {
+                        fmt = new Formatter(sb);
+                    }
+                    sb.append('\\');
+                    sb.append('u');
+                    fmt.format("%04x", (int)ch);
+                }
+                break;
+            }
+        }
+
+        return sb.length() == length ? null : sb.toString();
+    }
+
     static class CharsSpliterator implements Spliterator.OfInt {
         private final byte[] array;
         private int index;        // current index, modified on advance/split
--- a/src/java.base/share/classes/java/lang/StringUTF16.java	Thu Feb 15 10:47:38 2018 -0400
+++ b/src/java.base/share/classes/java/lang/StringUTF16.java	Thu Feb 15 11:26:45 2018 -0400
@@ -26,6 +26,7 @@
 package java.lang;
 
 import java.util.Arrays;
+import java.util.Formatter;
 import java.util.Locale;
 import java.util.Spliterator;
 import java.util.function.IntConsumer;
@@ -816,19 +817,53 @@
         return newString(result, 0, resultOffset);
     }
 
+    public static int skipLeadingSpaces(byte[] value) {
+        int left = 0;
+        while ((left < value.length) &&
+                (value[left + 1] == 0) &&
+                ((value[left] & 0xff) <= ' ')) {
+            left += 2;
+        }
+        return left;
+    }
+
+    public static int skipTrailingSpaces(byte[] value) {
+        int right = value.length;
+        while ((0 < right) &&
+                (value[right - 1] == 0) &&
+                ((value[right - 2] & 0xff) <= ' ')) {
+            right -= 2;
+        }
+        return right;
+    }
+
     public static String trim(byte[] value) {
-        int length = value.length >> 1;
-        int len = length;
-        int st = 0;
-        while (st < len && getChar(value, st) <= ' ') {
-            st++;
+        int left = skipLeadingSpaces(value);
+        if (left == value.length) {
+            return "";
         }
-        while (st < len && getChar(value, len - 1) <= ' ') {
-            len--;
+        int right = skipTrailingSpaces(value);
+        return ((left > 0) || (right < value.length)) ?
+                new String(Arrays.copyOfRange(value, left, right), UTF16) : null;
+    }
+
+    public static String trimLeft(byte[] value) {
+        int left = skipLeadingSpaces(value);
+        if (left == value.length) {
+            return "";
         }
-        return ((st > 0) || (len < length )) ?
-            new String(Arrays.copyOfRange(value, st << 1, len << 1), UTF16) :
-            null;
+        return (left != 0) ?
+                new String(Arrays.copyOfRange(value, left, value.length), UTF16) :
+                null;
+    }
+
+    public static String trimRight(byte[] value) {
+        int right = skipTrailingSpaces(value);
+        if (right == 0) {
+            return "";
+        }
+        return (right != value.length) ?
+                new String(Arrays.copyOfRange(value, 0, right), UTF16) : null;
     }
 
     private static void putChars(byte[] val, int index, char[] str, int off, int end) {
@@ -1305,4 +1340,147 @@
         String.checkBoundsOffCount(offset, count, length(val));
     }
 
+    static String unescape(char[] chars, boolean backslash, boolean unicode)
+            throws MalformedEscapeException {
+        int length = chars.length;
+        int from = 0;
+        int to = 0;
+
+        while (from < length) {
+            char ch = chars[from++];
+            if (ch == '\\' && from < length) {
+                ch = chars[from++];
+                if (unicode && ch == 'u') {
+                    while (from < length && chars[from] == 'u') {
+                        from++;
+                    }
+                    if (length <= from + 3) {
+                        throw new MalformedEscapeException();
+                    }
+                    int code = (Character.digit(chars[from + 0], 16) << 12) |
+                               (Character.digit(chars[from + 1], 16) <<  8) |
+                               (Character.digit(chars[from + 2], 16) <<  4) |
+                                Character.digit(chars[from + 3], 16);
+                    if (code < 0) {
+                        throw new MalformedEscapeException();
+                    }
+                    ch = (char)code;
+                    from += 4;
+                } else if (backslash) {
+                    switch (ch) {
+                    case 'b':
+                        ch = '\b';
+                        break;
+                    case 'f':
+                        ch = '\f';
+                        break;
+                    case 'n':
+                        ch = '\n';
+                        break;
+                    case 'r':
+                        ch = '\r';
+                        break;
+                    case 't':
+                        ch = '\t';
+                        break;
+                    case 'u':
+                        chars[to++] = (byte)'\\';
+                        break;
+                    case '0': case '1': case '2': case '3':
+                    case '4': case '5': case '6': case '7':
+                        int code = ch - '0';
+                        for (int i = 0; i < 2 && from < length; i++) {
+                            int digit = Character.digit(chars[from], 8);
+                            if (digit < 0) {
+                                break;
+                            }
+                            from++;
+                            code = code << 3 | digit;
+                        }
+                        if (0377 < code) {
+                            throw new MalformedEscapeException();
+                        }
+                        ch = (char)code;
+                        break;
+                    default:
+                        throw new MalformedEscapeException();
+                    }
+                } else {
+                    chars[to++] = '\\';
+                }
+            }
+
+            chars[to++] = ch;
+        }
+
+        return new String(chars, 0, to);
+    }
+
+    static String escape(char[] value, boolean backslash, boolean unicode) {
+        int length = value.length;
+        StringBuilder sb = new StringBuilder(length);
+        Formatter fmt = null;
+        for (int i = 0; i < length; i++) {
+            char ch = value[i];
+            if (backslash && ch <= '~') {
+                switch (ch) {
+                case '\b':
+                    sb.append('\\');
+                    sb.append('b');
+                    break;
+                case '\f':
+                    sb.append('\\');
+                    sb.append('f');
+                    break;
+                case '\n':
+                    sb.append('\\');
+                    sb.append('n');
+                    break;
+                case '\r':
+                    sb.append('\\');
+                    sb.append('r');
+                    break;
+                case '\t':
+                    sb.append('\\');
+                    sb.append('t');
+                    break;
+                case '\\':
+                    sb.append('\\');
+                    sb.append('\\');
+                    break;
+                case '\"':
+                    sb.append('\\');
+                    sb.append('\"');
+                    break;
+                case '\'':
+                    sb.append('\\');
+                    sb.append('\'');
+                    break;
+                default:
+                    if (' ' <= ch) {
+                        sb.append(ch);
+                    } else {
+                        if (fmt == null) {
+                            fmt = new Formatter(sb);
+                        }
+                        sb.append('\\');
+                        sb.append('u');
+                        fmt.format("%04x", (int)ch);
+                    }
+                    break;
+                }
+            } else if (unicode && ch > '~') {
+                if (fmt == null) {
+                    fmt = new Formatter(sb);
+                }
+                sb.append("\\u");
+                fmt.format("%04x", (int)ch);
+            } else {
+                sb.append(ch);
+            }
+        }
+
+        return sb.length() == length ? null : sb.toString();
+    }
+
 }