Home | History | Annotate | Download | only in protobuf
      1 // Protocol Buffers - Google's data interchange format
      2 // Copyright 2008 Google Inc.  All rights reserved.
      3 // https://developers.google.com/protocol-buffers/
      4 //
      5 // Redistribution and use in source and binary forms, with or without
      6 // modification, are permitted provided that the following conditions are
      7 // met:
      8 //
      9 //     * Redistributions of source code must retain the above copyright
     10 // notice, this list of conditions and the following disclaimer.
     11 //     * Redistributions in binary form must reproduce the above
     12 // copyright notice, this list of conditions and the following disclaimer
     13 // in the documentation and/or other materials provided with the
     14 // distribution.
     15 //     * Neither the name of Google Inc. nor the names of its
     16 // contributors may be used to endorse or promote products derived from
     17 // this software without specific prior written permission.
     18 //
     19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30 
     31 package com.google.protobuf;
     32 
     33 import java.lang.reflect.Method;
     34 import java.util.HashMap;
     35 import java.util.Iterator;
     36 import java.util.List;
     37 import java.util.Map;
     38 import java.util.Set;
     39 import java.util.TreeSet;
     40 
     41 /**
     42  * Helps generate {@link String} representations of {@link MessageLite} protos.
     43  */
     44 // TODO(dweis): Fix map fields.
     45 final class MessageLiteToString {
     46 
     47   private static final String LIST_SUFFIX = "List";
     48   private static final String BUILDER_LIST_SUFFIX = "OrBuilderList";
     49   private static final String BYTES_SUFFIX = "Bytes";
     50 
     51   /**
     52    * Returns a {@link String} representation of the {@link MessageLite} object.  The first line of
     53    * the {@code String} representation representation includes a comment string to uniquely identify
     54    * the objcet instance. This acts as an indicator that this should not be relied on for
     55    * comparisons.
     56    *
     57    * <p>For use by generated code only.
     58    */
     59   static String toString(MessageLite messageLite, String commentString) {
     60     StringBuilder buffer = new StringBuilder();
     61     buffer.append("# ").append(commentString);
     62     reflectivePrintWithIndent(messageLite, buffer, 0);
     63     return buffer.toString();
     64   }
     65 
     66   /**
     67    * Reflectively prints the {@link MessageLite} to the buffer at given {@code indent} level.
     68    *
     69    * @param buffer the buffer to write to
     70    * @param indent the number of spaces to indent the proto by
     71    */
     72   private static void reflectivePrintWithIndent(
     73       MessageLite messageLite, StringBuilder buffer, int indent) {
     74     // Build a map of method name to method. We're looking for methods like getFoo(), hasFoo(), and
     75     // getFooList() which might be useful for building an object's string representation.
     76     Map<String, Method> nameToNoArgMethod = new HashMap<String, Method>();
     77     Map<String, Method> nameToMethod = new HashMap<String, Method>();
     78     Set<String> getters = new TreeSet<String>();
     79     for (Method method : messageLite.getClass().getDeclaredMethods()) {
     80       nameToMethod.put(method.getName(), method);
     81       if (method.getParameterTypes().length == 0) {
     82         nameToNoArgMethod.put(method.getName(), method);
     83 
     84         if (method.getName().startsWith("get")) {
     85           getters.add(method.getName());
     86         }
     87       }
     88     }
     89 
     90     for (String getter : getters) {
     91       String suffix = getter.replaceFirst("get", "");
     92       if (suffix.endsWith(LIST_SUFFIX) && !suffix.endsWith(BUILDER_LIST_SUFFIX)) {
     93         String camelCase = suffix.substring(0, 1).toLowerCase()
     94             + suffix.substring(1, suffix.length() - LIST_SUFFIX.length());
     95         // Try to reflectively get the value and toString() the field as if it were repeated. This
     96         // only works if the method names have not be proguarded out or renamed.
     97         Method listMethod = nameToNoArgMethod.get("get" + suffix);
     98         if (listMethod != null) {
     99           printField(
    100               buffer,
    101               indent,
    102               camelCaseToSnakeCase(camelCase),
    103               GeneratedMessageLite.invokeOrDie(listMethod, messageLite));
    104           continue;
    105         }
    106       }
    107 
    108       Method setter = nameToMethod.get("set" + suffix);
    109       if (setter == null) {
    110         continue;
    111       }
    112       if (suffix.endsWith(BYTES_SUFFIX)
    113           && nameToNoArgMethod.containsKey(
    114               "get" + suffix.substring(0, suffix.length() - "Bytes".length()))) {
    115         // Heuristic to skip bytes based accessors for string fields.
    116         continue;
    117       }
    118 
    119       String camelCase = suffix.substring(0, 1).toLowerCase() + suffix.substring(1);
    120 
    121       // Try to reflectively get the value and toString() the field as if it were optional. This
    122       // only works if the method names have not be proguarded out or renamed.
    123       Method getMethod = nameToNoArgMethod.get("get" + suffix);
    124       Method hasMethod = nameToNoArgMethod.get("has" + suffix);
    125       // TODO(dweis): Fix proto3 semantics.
    126       if (getMethod != null) {
    127         Object value = GeneratedMessageLite.invokeOrDie(getMethod, messageLite);
    128         final boolean hasValue = hasMethod == null
    129             ? !isDefaultValue(value)
    130             : (Boolean) GeneratedMessageLite.invokeOrDie(hasMethod, messageLite);
    131          // TODO(dweis): This doesn't stop printing oneof case twice: value and enum style.
    132         if (hasValue) {
    133           printField(
    134               buffer,
    135               indent,
    136               camelCaseToSnakeCase(camelCase),
    137               value);
    138         }
    139         continue;
    140       }
    141     }
    142 
    143     if (messageLite instanceof GeneratedMessageLite.ExtendableMessage) {
    144       Iterator<Map.Entry<GeneratedMessageLite.ExtensionDescriptor, Object>> iter =
    145           ((GeneratedMessageLite.ExtendableMessage<?, ?>) messageLite).extensions.iterator();
    146       while (iter.hasNext()) {
    147         Map.Entry<GeneratedMessageLite.ExtensionDescriptor, Object> entry = iter.next();
    148         printField(buffer, indent, "[" + entry.getKey().getNumber() + "]", entry.getValue());
    149       }
    150     }
    151 
    152     if (((GeneratedMessageLite<?, ?>) messageLite).unknownFields != null) {
    153       ((GeneratedMessageLite<?, ?>) messageLite).unknownFields.printWithIndent(buffer, indent);
    154     }
    155   }
    156 
    157   private static boolean isDefaultValue(Object o) {
    158     if (o instanceof Boolean) {
    159       return !((Boolean) o);
    160     }
    161     if (o instanceof Integer) {
    162       return ((Integer) o) == 0;
    163     }
    164     if (o instanceof Float) {
    165       return ((Float) o) == 0f;
    166     }
    167     if (o instanceof Double) {
    168       return ((Double) o) == 0d;
    169     }
    170     if (o instanceof String) {
    171       return o.equals("");
    172     }
    173     if (o instanceof ByteString) {
    174       return o.equals(ByteString.EMPTY);
    175     }
    176     if (o instanceof MessageLite) { // Can happen in oneofs.
    177       return o == ((MessageLite) o).getDefaultInstanceForType();
    178     }
    179     if (o instanceof java.lang.Enum<?>) { // Catches oneof enums.
    180       return ((java.lang.Enum<?>) o).ordinal() == 0;
    181     }
    182 
    183     return false;
    184   }
    185 
    186   /**
    187    * Formats a text proto field.
    188    *
    189    * <p>For use by generated code only.
    190    *
    191    * @param buffer the buffer to write to
    192    * @param indent the number of spaces the proto should be indented by
    193    * @param name the field name (in lower underscore case)
    194    * @param object the object value of the field
    195    */
    196   static final void printField(StringBuilder buffer, int indent, String name, Object object) {
    197     if (object instanceof List<?>) {
    198       List<?> list = (List<?>) object;
    199       for (Object entry : list) {
    200         printField(buffer, indent, name, entry);
    201       }
    202       return;
    203     }
    204 
    205     buffer.append('\n');
    206     for (int i = 0; i < indent; i++) {
    207       buffer.append(' ');
    208     }
    209     buffer.append(name);
    210 
    211     if (object instanceof String) {
    212       buffer.append(": \"").append(TextFormatEscaper.escapeText((String) object)).append('"');
    213     } else if (object instanceof ByteString) {
    214       buffer.append(": \"").append(TextFormatEscaper.escapeBytes((ByteString) object)).append('"');
    215     } else if (object instanceof GeneratedMessageLite) {
    216       buffer.append(" {");
    217       reflectivePrintWithIndent((GeneratedMessageLite<?, ?>) object, buffer, indent + 2);
    218       buffer.append("\n");
    219       for (int i = 0; i < indent; i++) {
    220         buffer.append(' ');
    221       }
    222       buffer.append("}");
    223     } else {
    224       buffer.append(": ").append(object.toString());
    225     }
    226   }
    227 
    228   private static final String camelCaseToSnakeCase(String camelCase) {
    229     StringBuilder builder = new StringBuilder();
    230     for (int i = 0; i < camelCase.length(); i++) {
    231       char ch = camelCase.charAt(i);
    232       if (Character.isUpperCase(ch)) {
    233         builder.append("_");
    234       }
    235       builder.append(Character.toLowerCase(ch));
    236     }
    237     return builder.toString();
    238   }
    239 }
    240