| // Protocol Buffers - Google's data interchange format |
| // Copyright 2008 Google Inc. All rights reserved. |
| // https://developers.google.com/protocol-buffers/ |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| package com.google.protobuf; |
| |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| /** Helps generate {@link String} representations of {@link MessageLite} protos. */ |
| final class MessageLiteToString { |
| |
| private static final String LIST_SUFFIX = "List"; |
| private static final String BUILDER_LIST_SUFFIX = "OrBuilderList"; |
| private static final String MAP_SUFFIX = "Map"; |
| private static final String BYTES_SUFFIX = "Bytes"; |
| |
| /** |
| * Returns a {@link String} representation of the {@link MessageLite} object. The first line of |
| * the {@code String} representation representation includes a comment string to uniquely identify |
| * the object instance. This acts as an indicator that this should not be relied on for |
| * comparisons. |
| * |
| * <p>For use by generated code only. |
| */ |
| static String toString(MessageLite messageLite, String commentString) { |
| StringBuilder buffer = new StringBuilder(); |
| buffer.append("# ").append(commentString); |
| reflectivePrintWithIndent(messageLite, buffer, 0); |
| return buffer.toString(); |
| } |
| |
| /** |
| * Reflectively prints the {@link MessageLite} to the buffer at given {@code indent} level. |
| * |
| * @param buffer the buffer to write to |
| * @param indent the number of spaces to indent the proto by |
| */ |
| private static void reflectivePrintWithIndent( |
| MessageLite messageLite, StringBuilder buffer, int indent) { |
| // Build a map of method name to method. We're looking for methods like getFoo(), hasFoo(), |
| // getFooList() and getFooMap() which might be useful for building an object's string |
| // representation. |
| Map<String, Method> nameToNoArgMethod = new HashMap<String, Method>(); |
| Map<String, Method> nameToMethod = new HashMap<String, Method>(); |
| Set<String> getters = new TreeSet<String>(); |
| for (Method method : messageLite.getClass().getDeclaredMethods()) { |
| nameToMethod.put(method.getName(), method); |
| if (method.getParameterTypes().length == 0) { |
| nameToNoArgMethod.put(method.getName(), method); |
| |
| if (method.getName().startsWith("get")) { |
| getters.add(method.getName()); |
| } |
| } |
| } |
| |
| for (String getter : getters) { |
| String suffix = getter.replaceFirst("get", ""); |
| if (suffix.endsWith(LIST_SUFFIX) |
| && !suffix.endsWith(BUILDER_LIST_SUFFIX) |
| // Sometimes people have fields named 'list' that aren't repeated. |
| && !suffix.equals(LIST_SUFFIX)) { |
| String camelCase = |
| suffix.substring(0, 1).toLowerCase() |
| + suffix.substring(1, suffix.length() - LIST_SUFFIX.length()); |
| // Try to reflectively get the value and toString() the field as if it were repeated. This |
| // only works if the method names have not been proguarded out or renamed. |
| Method listMethod = nameToNoArgMethod.get(getter); |
| if (listMethod != null && listMethod.getReturnType().equals(List.class)) { |
| printField( |
| buffer, |
| indent, |
| camelCaseToSnakeCase(camelCase), |
| GeneratedMessageLite.invokeOrDie(listMethod, messageLite)); |
| continue; |
| } |
| } |
| if (suffix.endsWith(MAP_SUFFIX) |
| // Sometimes people have fields named 'map' that aren't maps. |
| && !suffix.equals(MAP_SUFFIX)) { |
| String camelCase = |
| suffix.substring(0, 1).toLowerCase() |
| + suffix.substring(1, suffix.length() - MAP_SUFFIX.length()); |
| // Try to reflectively get the value and toString() the field as if it were a map. This only |
| // works if the method names have not been proguarded out or renamed. |
| Method mapMethod = nameToNoArgMethod.get(getter); |
| if (mapMethod != null |
| && mapMethod.getReturnType().equals(Map.class) |
| // Skip the deprecated getter method with no prefix "Map" when the field name ends with |
| // "map". |
| && !mapMethod.isAnnotationPresent(Deprecated.class) |
| // Skip the internal mutable getter method. |
| && Modifier.isPublic(mapMethod.getModifiers())) { |
| printField( |
| buffer, |
| indent, |
| camelCaseToSnakeCase(camelCase), |
| GeneratedMessageLite.invokeOrDie(mapMethod, messageLite)); |
| continue; |
| } |
| } |
| |
| Method setter = nameToMethod.get("set" + suffix); |
| if (setter == null) { |
| continue; |
| } |
| if (suffix.endsWith(BYTES_SUFFIX) |
| && nameToNoArgMethod.containsKey( |
| "get" + suffix.substring(0, suffix.length() - "Bytes".length()))) { |
| // Heuristic to skip bytes based accessors for string fields. |
| continue; |
| } |
| |
| String camelCase = suffix.substring(0, 1).toLowerCase() + suffix.substring(1); |
| |
| // Try to reflectively get the value and toString() the field as if it were optional. This |
| // only works if the method names have not been proguarded out or renamed. |
| Method getMethod = nameToNoArgMethod.get("get" + suffix); |
| Method hasMethod = nameToNoArgMethod.get("has" + suffix); |
| // TODO(dweis): Fix proto3 semantics. |
| if (getMethod != null) { |
| Object value = GeneratedMessageLite.invokeOrDie(getMethod, messageLite); |
| final boolean hasValue = |
| hasMethod == null |
| ? !isDefaultValue(value) |
| : (Boolean) GeneratedMessageLite.invokeOrDie(hasMethod, messageLite); |
| // TODO(dweis): This doesn't stop printing oneof case twice: value and enum style. |
| if (hasValue) { |
| printField(buffer, indent, camelCaseToSnakeCase(camelCase), value); |
| } |
| continue; |
| } |
| } |
| |
| if (messageLite instanceof GeneratedMessageLite.ExtendableMessage) { |
| Iterator<Map.Entry<GeneratedMessageLite.ExtensionDescriptor, Object>> iter = |
| ((GeneratedMessageLite.ExtendableMessage<?, ?>) messageLite).extensions.iterator(); |
| while (iter.hasNext()) { |
| Map.Entry<GeneratedMessageLite.ExtensionDescriptor, Object> entry = iter.next(); |
| printField(buffer, indent, "[" + entry.getKey().getNumber() + "]", entry.getValue()); |
| } |
| } |
| |
| if (((GeneratedMessageLite<?, ?>) messageLite).unknownFields != null) { |
| ((GeneratedMessageLite<?, ?>) messageLite).unknownFields.printWithIndent(buffer, indent); |
| } |
| } |
| |
| private static boolean isDefaultValue(Object o) { |
| if (o instanceof Boolean) { |
| return !((Boolean) o); |
| } |
| if (o instanceof Integer) { |
| return ((Integer) o) == 0; |
| } |
| if (o instanceof Float) { |
| return ((Float) o) == 0f; |
| } |
| if (o instanceof Double) { |
| return ((Double) o) == 0d; |
| } |
| if (o instanceof String) { |
| return o.equals(""); |
| } |
| if (o instanceof ByteString) { |
| return o.equals(ByteString.EMPTY); |
| } |
| if (o instanceof MessageLite) { // Can happen in oneofs. |
| return o == ((MessageLite) o).getDefaultInstanceForType(); |
| } |
| if (o instanceof java.lang.Enum<?>) { // Catches oneof enums. |
| return ((java.lang.Enum<?>) o).ordinal() == 0; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Formats a text proto field. |
| * |
| * <p>For use by generated code only. |
| * |
| * @param buffer the buffer to write to |
| * @param indent the number of spaces the proto should be indented by |
| * @param name the field name (in lower underscore case) |
| * @param object the object value of the field |
| */ |
| static final void printField(StringBuilder buffer, int indent, String name, Object object) { |
| if (object instanceof List<?>) { |
| List<?> list = (List<?>) object; |
| for (Object entry : list) { |
| printField(buffer, indent, name, entry); |
| } |
| return; |
| } |
| if (object instanceof Map<?, ?>) { |
| Map<?, ?> map = (Map<?, ?>) object; |
| for (Map.Entry<?, ?> entry : map.entrySet()) { |
| printField(buffer, indent, name, entry); |
| } |
| return; |
| } |
| |
| buffer.append('\n'); |
| for (int i = 0; i < indent; i++) { |
| buffer.append(' '); |
| } |
| buffer.append(name); |
| |
| if (object instanceof String) { |
| buffer.append(": \"").append(TextFormatEscaper.escapeText((String) object)).append('"'); |
| } else if (object instanceof ByteString) { |
| buffer.append(": \"").append(TextFormatEscaper.escapeBytes((ByteString) object)).append('"'); |
| } else if (object instanceof GeneratedMessageLite) { |
| buffer.append(" {"); |
| reflectivePrintWithIndent((GeneratedMessageLite<?, ?>) object, buffer, indent + 2); |
| buffer.append("\n"); |
| for (int i = 0; i < indent; i++) { |
| buffer.append(' '); |
| } |
| buffer.append("}"); |
| } else if (object instanceof Map.Entry<?, ?>) { |
| buffer.append(" {"); |
| Map.Entry<?, ?> entry = (Map.Entry<?, ?>) object; |
| printField(buffer, indent + 2, "key", entry.getKey()); |
| printField(buffer, indent + 2, "value", entry.getValue()); |
| buffer.append("\n"); |
| for (int i = 0; i < indent; i++) { |
| buffer.append(' '); |
| } |
| buffer.append("}"); |
| } else { |
| buffer.append(": ").append(object.toString()); |
| } |
| } |
| |
| private static final String camelCaseToSnakeCase(String camelCase) { |
| StringBuilder builder = new StringBuilder(); |
| for (int i = 0; i < camelCase.length(); i++) { |
| char ch = camelCase.charAt(i); |
| if (Character.isUpperCase(ch)) { |
| builder.append("_"); |
| } |
| builder.append(Character.toLowerCase(ch)); |
| } |
| return builder.toString(); |
| } |
| } |