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 */ toString(MessageLite messageLite, String commentString)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 */ reflectivePrintWithIndent( MessageLite messageLite, StringBuilder buffer, int indent)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 isDefaultValue(Object o)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 */ printField(StringBuilder buffer, int indent, String name, Object object)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 camelCaseToSnakeCase(String camelCase)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