147107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// Protocol Buffers - Google's data interchange format
247107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// Copyright 2013 Google Inc.  All rights reserved.
347107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// http://code.google.com/p/protobuf/
447107914acbad70ff4db1664d3664ccc994315afAndrew Flynn//
547107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// Redistribution and use in source and binary forms, with or without
647107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// modification, are permitted provided that the following conditions are
747107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// met:
847107914acbad70ff4db1664d3664ccc994315afAndrew Flynn//
947107914acbad70ff4db1664d3664ccc994315afAndrew Flynn//     * Redistributions of source code must retain the above copyright
1047107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// notice, this list of conditions and the following disclaimer.
1147107914acbad70ff4db1664d3664ccc994315afAndrew Flynn//     * Redistributions in binary form must reproduce the above
1247107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// copyright notice, this list of conditions and the following disclaimer
1347107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// in the documentation and/or other materials provided with the
1447107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// distribution.
1547107914acbad70ff4db1664d3664ccc994315afAndrew Flynn//     * Neither the name of Google Inc. nor the names of its
1647107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// contributors may be used to endorse or promote products derived from
1747107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// this software without specific prior written permission.
1847107914acbad70ff4db1664d3664ccc994315afAndrew Flynn//
1947107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
2047107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2147107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2247107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
2347107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2447107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2547107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2647107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2747107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2847107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2947107914acbad70ff4db1664d3664ccc994315afAndrew Flynn// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3047107914acbad70ff4db1664d3664ccc994315afAndrew Flynn
3147107914acbad70ff4db1664d3664ccc994315afAndrew Flynnpackage com.google.protobuf.nano;
3247107914acbad70ff4db1664d3664ccc994315afAndrew Flynn
3347107914acbad70ff4db1664d3664ccc994315afAndrew Flynnimport java.lang.reflect.Array;
3447107914acbad70ff4db1664d3664ccc994315afAndrew Flynnimport java.lang.reflect.Field;
356b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynnimport java.lang.reflect.InvocationTargetException;
366b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynnimport java.lang.reflect.Method;
3747107914acbad70ff4db1664d3664ccc994315afAndrew Flynnimport java.lang.reflect.Modifier;
3847107914acbad70ff4db1664d3664ccc994315afAndrew Flynn
3947107914acbad70ff4db1664d3664ccc994315afAndrew Flynn/**
4047107914acbad70ff4db1664d3664ccc994315afAndrew Flynn * Static helper methods for printing nano protos.
4147107914acbad70ff4db1664d3664ccc994315afAndrew Flynn *
4247107914acbad70ff4db1664d3664ccc994315afAndrew Flynn * @author flynn@google.com Andrew Flynn
4347107914acbad70ff4db1664d3664ccc994315afAndrew Flynn */
4447107914acbad70ff4db1664d3664ccc994315afAndrew Flynnpublic final class MessageNanoPrinter {
4547107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    // Do not allow instantiation
4647107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    private MessageNanoPrinter() {}
4747107914acbad70ff4db1664d3664ccc994315afAndrew Flynn
4847107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    private static final String INDENT = "  ";
4947107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    private static final int MAX_STRING_LEN = 200;
5047107914acbad70ff4db1664d3664ccc994315afAndrew Flynn
5147107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    /**
5262a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar     * Returns an text representation of a MessageNano suitable for debugging. The returned string
5362a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar     * is mostly compatible with Protocol Buffer's TextFormat (as provided by non-nano protocol
5462a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar     * buffers) -- groups (which are deprecated) are output with an underscore name (e.g. foo_bar
5562a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar     * instead of FooBar) and will thus not parse.
5647107914acbad70ff4db1664d3664ccc994315afAndrew Flynn     *
5747107914acbad70ff4db1664d3664ccc994315afAndrew Flynn     * <p>Employs Java reflection on the given object and recursively prints primitive fields,
5847107914acbad70ff4db1664d3664ccc994315afAndrew Flynn     * groups, and messages.</p>
5947107914acbad70ff4db1664d3664ccc994315afAndrew Flynn     */
6047107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    public static <T extends MessageNano> String print(T message) {
6147107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        if (message == null) {
6262a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            return "";
6347107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        }
6447107914acbad70ff4db1664d3664ccc994315afAndrew Flynn
6547107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        StringBuffer buf = new StringBuffer();
6647107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        try {
675cc242074f189837b38e7768b57ccfb0bca258dfMax Cai            print(null, message, new StringBuffer(), buf);
6847107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        } catch (IllegalAccessException e) {
6947107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            return "Error printing proto: " + e.getMessage();
706b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn        } catch (InvocationTargetException e) {
716b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn            return "Error printing proto: " + e.getMessage();
7247107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        }
7347107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        return buf.toString();
7447107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    }
7547107914acbad70ff4db1664d3664ccc994315afAndrew Flynn
7647107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    /**
775cc242074f189837b38e7768b57ccfb0bca258dfMax Cai     * Function that will print the given message/field into the StringBuffer.
7847107914acbad70ff4db1664d3664ccc994315afAndrew Flynn     * Meant to be called recursively.
7962a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar     *
8062a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar     * @param identifier the identifier to use, or {@code null} if this is the root message to
8162a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar     *        print.
825cc242074f189837b38e7768b57ccfb0bca258dfMax Cai     * @param object the value to print. May in fact be a primitive value or byte array and not a
8362a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar     *        message.
8462a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar     * @param indentBuf the indentation each line should begin with.
8562a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar     * @param buf the output buffer.
8647107914acbad70ff4db1664d3664ccc994315afAndrew Flynn     */
875cc242074f189837b38e7768b57ccfb0bca258dfMax Cai    private static void print(String identifier, Object object,
886b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn            StringBuffer indentBuf, StringBuffer buf) throws IllegalAccessException,
896b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn            InvocationTargetException {
905cc242074f189837b38e7768b57ccfb0bca258dfMax Cai        if (object == null) {
9162a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            // This can happen if...
9262a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            //   - we're about to print a message, String, or byte[], but it not present;
9362a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            //   - we're about to print a primitive, but "reftype" optional style is enabled, and
9462a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            //     the field is unset.
9562a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            // In both cases the appropriate behavior is to output nothing.
965cc242074f189837b38e7768b57ccfb0bca258dfMax Cai        } else if (object instanceof MessageNano) {  // Nano proto message
9762a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            int origIndentBufLength = indentBuf.length();
9862a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            if (identifier != null) {
9962a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar                buf.append(indentBuf).append(deCamelCaseify(identifier)).append(" <\n");
10062a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar                indentBuf.append(INDENT);
10147107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            }
1026b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn            Class<?> clazz = object.getClass();
10347107914acbad70ff4db1664d3664ccc994315afAndrew Flynn
1046b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn            // Proto fields follow one of two formats:
1056b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn            //
1066b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn            // 1) Public, non-static variables that do not begin or end with '_'
1076b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn            // Find and print these using declared public fields
1086b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn            for (Field field : clazz.getFields()) {
10947107914acbad70ff4db1664d3664ccc994315afAndrew Flynn                int modifiers = field.getModifiers();
11047107914acbad70ff4db1664d3664ccc994315afAndrew Flynn                String fieldName = field.getName();
111d270ce1431d40af4caebdcac447cf762036ade1cBrian Duff                if ("cachedSize".equals(fieldName)) {
112d270ce1431d40af4caebdcac447cf762036ade1cBrian Duff                    // TODO(bduff): perhaps cachedSize should have a more obscure name.
113d270ce1431d40af4caebdcac447cf762036ade1cBrian Duff                    continue;
114d270ce1431d40af4caebdcac447cf762036ade1cBrian Duff                }
11547107914acbad70ff4db1664d3664ccc994315afAndrew Flynn
1166b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                if ((modifiers & Modifier.PUBLIC) == Modifier.PUBLIC
1176b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                        && (modifiers & Modifier.STATIC) != Modifier.STATIC
1186b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                        && !fieldName.startsWith("_")
1196b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                        && !fieldName.endsWith("_")) {
1206b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    Class<?> fieldType = field.getType();
1216b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    Object value = field.get(object);
12247107914acbad70ff4db1664d3664ccc994315afAndrew Flynn
1236b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    if (fieldType.isArray()) {
1246b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                        Class<?> arrayType = fieldType.getComponentType();
12547107914acbad70ff4db1664d3664ccc994315afAndrew Flynn
1266b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                        // bytes is special since it's not repeated, but is represented by an array
1276b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                        if (arrayType == byte.class) {
1286b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                            print(fieldName, value, indentBuf, buf);
1296b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                        } else {
1306b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                            int len = value == null ? 0 : Array.getLength(value);
1316b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                            for (int i = 0; i < len; i++) {
1326b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                                Object elem = Array.get(value, i);
1336b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                                print(fieldName, elem, indentBuf, buf);
1346b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                            }
13547107914acbad70ff4db1664d3664ccc994315afAndrew Flynn                        }
1366b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    } else {
1376b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                        print(fieldName, value, indentBuf, buf);
13847107914acbad70ff4db1664d3664ccc994315afAndrew Flynn                    }
1396b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                }
1406b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn            }
1416b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn
1426b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn            // 2) Fields that are accessed via getter methods (when accessors
1436b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn            //    mode is turned on)
1446b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn            // Find and print these using getter methods.
1456b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn            for (Method method : clazz.getMethods()) {
1466b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                String name = method.getName();
1476b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                // Check for the setter accessor method since getters and hazzers both have
1486b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                // non-proto-field name collisions (hashCode() and getSerializedSize())
1496b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                if (name.startsWith("set")) {
1506b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    String subfieldName = name.substring(3);
1516b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn
1526b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    Method hazzer = null;
1536b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    try {
1546b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                        hazzer = clazz.getMethod("has" + subfieldName);
1556b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    } catch (NoSuchMethodException e) {
1566b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                        continue;
1576b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    }
1586b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    // If hazzer does't exist or returns false, no need to continue
1596b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    if (!(Boolean) hazzer.invoke(object)) {
1606b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                        continue;
1616b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    }
1626b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn
1636b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    Method getter = null;
1646b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    try {
1656b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                        getter = clazz.getMethod("get" + subfieldName);
1666b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    } catch (NoSuchMethodException e) {
1676b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                        continue;
1686b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    }
1696b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn
1706b594c228e7954eb98353ad8e242b83fb255a277Andrew Flynn                    print(subfieldName, getter.invoke(object), indentBuf, buf);
17147107914acbad70ff4db1664d3664ccc994315afAndrew Flynn                }
17247107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            }
17362a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            if (identifier != null) {
17462a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar                indentBuf.setLength(origIndentBufLength);
17562a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar                buf.append(indentBuf).append(">\n");
17662a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            }
17747107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        } else {
17862a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            // Non-null primitive value
17947107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            identifier = deCamelCaseify(identifier);
18047107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            buf.append(indentBuf).append(identifier).append(": ");
1815cc242074f189837b38e7768b57ccfb0bca258dfMax Cai            if (object instanceof String) {
1825cc242074f189837b38e7768b57ccfb0bca258dfMax Cai                String stringMessage = sanitizeString((String) object);
18347107914acbad70ff4db1664d3664ccc994315afAndrew Flynn                buf.append("\"").append(stringMessage).append("\"");
1845cc242074f189837b38e7768b57ccfb0bca258dfMax Cai            } else if (object instanceof byte[]) {
1855cc242074f189837b38e7768b57ccfb0bca258dfMax Cai                appendQuotedBytes((byte[]) object, buf);
18647107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            } else {
1875cc242074f189837b38e7768b57ccfb0bca258dfMax Cai                buf.append(object);
18847107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            }
18947107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            buf.append("\n");
19047107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        }
19147107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    }
19247107914acbad70ff4db1664d3664ccc994315afAndrew Flynn
19347107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    /**
19447107914acbad70ff4db1664d3664ccc994315afAndrew Flynn     * Converts an identifier of the format "FieldName" into "field_name".
19547107914acbad70ff4db1664d3664ccc994315afAndrew Flynn     */
19647107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    private static String deCamelCaseify(String identifier) {
19747107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        StringBuffer out = new StringBuffer();
19847107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        for (int i = 0; i < identifier.length(); i++) {
19947107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            char currentChar = identifier.charAt(i);
20047107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            if (i == 0) {
20147107914acbad70ff4db1664d3664ccc994315afAndrew Flynn                out.append(Character.toLowerCase(currentChar));
20247107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            } else if (Character.isUpperCase(currentChar)) {
20347107914acbad70ff4db1664d3664ccc994315afAndrew Flynn                out.append('_').append(Character.toLowerCase(currentChar));
20447107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            } else {
20547107914acbad70ff4db1664d3664ccc994315afAndrew Flynn                out.append(currentChar);
20647107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            }
20747107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        }
20847107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        return out.toString();
20947107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    }
21047107914acbad70ff4db1664d3664ccc994315afAndrew Flynn
21147107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    /**
21247107914acbad70ff4db1664d3664ccc994315afAndrew Flynn     * Shortens and escapes the given string.
21347107914acbad70ff4db1664d3664ccc994315afAndrew Flynn     */
21447107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    private static String sanitizeString(String str) {
21547107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        if (!str.startsWith("http") && str.length() > MAX_STRING_LEN) {
21647107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            // Trim non-URL strings.
21747107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            str = str.substring(0, MAX_STRING_LEN) + "[...]";
21847107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        }
21947107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        return escapeString(str);
22047107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    }
22147107914acbad70ff4db1664d3664ccc994315afAndrew Flynn
22247107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    /**
22347107914acbad70ff4db1664d3664ccc994315afAndrew Flynn     * Escape everything except for low ASCII code points.
22447107914acbad70ff4db1664d3664ccc994315afAndrew Flynn     */
22547107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    private static String escapeString(String str) {
22647107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        int strLen = str.length();
22747107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        StringBuilder b = new StringBuilder(strLen);
22847107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        for (int i = 0; i < strLen; i++) {
22947107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            char original = str.charAt(i);
23047107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            if (original >= ' ' && original <= '~' && original != '"' && original != '\'') {
23147107914acbad70ff4db1664d3664ccc994315afAndrew Flynn                b.append(original);
23247107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            } else {
23347107914acbad70ff4db1664d3664ccc994315afAndrew Flynn                b.append(String.format("\\u%04x", (int) original));
23447107914acbad70ff4db1664d3664ccc994315afAndrew Flynn            }
23547107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        }
23647107914acbad70ff4db1664d3664ccc994315afAndrew Flynn        return b.toString();
23747107914acbad70ff4db1664d3664ccc994315afAndrew Flynn    }
23862a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar
23962a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar    /**
24062a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar     * Appends a quoted byte array to the provided {@code StringBuffer}.
24162a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar     */
24262a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar    private static void appendQuotedBytes(byte[] bytes, StringBuffer builder) {
24362a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar        if (bytes == null) {
24462a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            builder.append("\"\"");
24562a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            return;
24662a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar        }
24762a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar
24862a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar        builder.append('"');
24962a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar        for (int i = 0; i < bytes.length; ++i) {
250f56bd09ac980fcde8b6af4e3fbd5bf9c97fc9199Linus Tufvesson            int ch = bytes[i] & 0xff;
25162a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            if (ch == '\\' || ch == '"') {
25262a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar                builder.append('\\').append((char) ch);
25362a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            } else if (ch >= 32 && ch < 127) {
25462a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar                builder.append((char) ch);
25562a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            } else {
256c9c2ffc3c005fbcaac2d0f91094bdb1928cb309eMax Cai                builder.append(String.format("\\%03o", ch));
25762a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar            }
25862a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar        }
25962a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar        builder.append('"');
26062a22a732fb134e5f34dd3e01920933ca5b16346Nicholas Seckar    }
26147107914acbad70ff4db1664d3664ccc994315afAndrew Flynn}
262