• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.lang.reflect.Modifier;
35 import java.util.Arrays;
36 import java.util.HashMap;
37 import java.util.HashSet;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Map.Entry;
42 import java.util.Set;
43 import java.util.TreeMap;
44 
45 /** Helps generate {@link String} representations of {@link MessageLite} protos. */
46 final class MessageLiteToString {
47 
48   private static final String LIST_SUFFIX = "List";
49   private static final String BUILDER_LIST_SUFFIX = "OrBuilderList";
50   private static final String MAP_SUFFIX = "Map";
51   private static final String BYTES_SUFFIX = "Bytes";
52   private static final char[] INDENT_BUFFER = new char[80];
53 
54   static {
Arrays.fill(INDENT_BUFFER, ' ')55     Arrays.fill(INDENT_BUFFER, ' ');
56   }
57 
58   /**
59    * Returns a {@link String} representation of the {@link MessageLite} object. The first line of
60    * the {@code String} representation representation includes a comment string to uniquely identify
61    * the object instance. This acts as an indicator that this should not be relied on for
62    * comparisons.
63    *
64    * <p>For use by generated code only.
65    */
toString(MessageLite messageLite, String commentString)66   static String toString(MessageLite messageLite, String commentString) {
67     StringBuilder buffer = new StringBuilder();
68     buffer.append("# ").append(commentString);
69     reflectivePrintWithIndent(messageLite, buffer, 0);
70     return buffer.toString();
71   }
72 
73   /**
74    * Reflectively prints the {@link MessageLite} to the buffer at given {@code indent} level.
75    *
76    * @param buffer the buffer to write to
77    * @param indent the number of spaces to indent the proto by
78    */
reflectivePrintWithIndent( MessageLite messageLite, StringBuilder buffer, int indent)79   private static void reflectivePrintWithIndent(
80       MessageLite messageLite, StringBuilder buffer, int indent) {
81     // Build a map of method name to method. We're looking for methods like getFoo(), hasFoo(),
82     // getFooList() and getFooMap() which might be useful for building an object's string
83     // representation.
84     Set<String> setters = new HashSet<>();
85     Map<String, Method> hazzers = new HashMap<>();
86     Map<String, Method> getters = new TreeMap<>();
87     for (Method method : messageLite.getClass().getDeclaredMethods()) {
88       if (Modifier.isStatic(method.getModifiers())) {
89         continue;
90       }
91       if (method.getName().length() < 3) {
92         continue;
93       }
94 
95       if (method.getName().startsWith("set")) {
96         setters.add(method.getName());
97         continue;
98       }
99 
100       if (!Modifier.isPublic(method.getModifiers())) {
101         continue;
102       }
103 
104       if (method.getParameterTypes().length != 0) {
105         continue;
106       }
107 
108       if (method.getName().startsWith("has")) {
109         hazzers.put(method.getName(), method);
110       } else if (method.getName().startsWith("get")) {
111         getters.put(method.getName(), method);
112       }
113     }
114 
115     for (Entry<String, Method> getter : getters.entrySet()) {
116       String suffix = getter.getKey().substring(3);
117       if (suffix.endsWith(LIST_SUFFIX)
118           && !suffix.endsWith(BUILDER_LIST_SUFFIX)
119           // Sometimes people have fields named 'list' that aren't repeated.
120           && !suffix.equals(LIST_SUFFIX)) {
121         // Try to reflectively get the value and toString() the field as if it were repeated. This
122         // only works if the method names have not been proguarded out or renamed.
123         Method listMethod = getter.getValue();
124         if (listMethod != null && listMethod.getReturnType().equals(List.class)) {
125           printField(
126               buffer,
127               indent,
128               suffix.substring(0, suffix.length() - LIST_SUFFIX.length()),
129               GeneratedMessageLite.invokeOrDie(listMethod, messageLite));
130           continue;
131         }
132       }
133       if (suffix.endsWith(MAP_SUFFIX)
134           // Sometimes people have fields named 'map' that aren't maps.
135           && !suffix.equals(MAP_SUFFIX)) {
136         // Try to reflectively get the value and toString() the field as if it were a map. This only
137         // works if the method names have not been proguarded out or renamed.
138         Method mapMethod = getter.getValue();
139         if (mapMethod != null
140             && mapMethod.getReturnType().equals(Map.class)
141             // Skip the deprecated getter method with no prefix "Map" when the field name ends with
142             // "map".
143             && !mapMethod.isAnnotationPresent(Deprecated.class)
144             // Skip the internal mutable getter method.
145             && Modifier.isPublic(mapMethod.getModifiers())) {
146           printField(
147               buffer,
148               indent,
149               suffix.substring(0, suffix.length() - MAP_SUFFIX.length()),
150               GeneratedMessageLite.invokeOrDie(mapMethod, messageLite));
151           continue;
152         }
153       }
154 
155       if (!setters.contains("set" + suffix)) {
156         continue;
157       }
158       if (suffix.endsWith(BYTES_SUFFIX)
159           && getters.containsKey("get" + suffix.substring(0, suffix.length() - "Bytes".length()))) {
160         // Heuristic to skip bytes based accessors for string fields.
161         continue;
162       }
163 
164       // Try to reflectively get the value and toString() the field as if it were optional. This
165       // only works if the method names have not been proguarded out or renamed.
166       Method getMethod = getter.getValue();
167       Method hasMethod = hazzers.get("has" + suffix);
168       // TODO(dweis): Fix proto3 semantics.
169       if (getMethod != null) {
170         Object value = GeneratedMessageLite.invokeOrDie(getMethod, messageLite);
171         final boolean hasValue =
172             hasMethod == null
173                 ? !isDefaultValue(value)
174                 : (Boolean) GeneratedMessageLite.invokeOrDie(hasMethod, messageLite);
175         // TODO(dweis): This doesn't stop printing oneof case twice: value and enum style.
176         if (hasValue) {
177           printField(buffer, indent, suffix, value);
178         }
179         continue;
180       }
181     }
182 
183     if (messageLite instanceof GeneratedMessageLite.ExtendableMessage) {
184       Iterator<Map.Entry<GeneratedMessageLite.ExtensionDescriptor, Object>> iter =
185           ((GeneratedMessageLite.ExtendableMessage<?, ?>) messageLite).extensions.iterator();
186       while (iter.hasNext()) {
187         Map.Entry<GeneratedMessageLite.ExtensionDescriptor, Object> entry = iter.next();
188         printField(buffer, indent, "[" + entry.getKey().getNumber() + "]", entry.getValue());
189       }
190     }
191 
192     if (((GeneratedMessageLite<?, ?>) messageLite).unknownFields != null) {
193       ((GeneratedMessageLite<?, ?>) messageLite).unknownFields.printWithIndent(buffer, indent);
194     }
195   }
196 
isDefaultValue(Object o)197   private static boolean isDefaultValue(Object o) {
198     if (o instanceof Boolean) {
199       return !((Boolean) o);
200     }
201     if (o instanceof Integer) {
202       return ((Integer) o) == 0;
203     }
204     if (o instanceof Float) {
205       return ((Float) o) == 0f;
206     }
207     if (o instanceof Double) {
208       return ((Double) o) == 0d;
209     }
210     if (o instanceof String) {
211       return o.equals("");
212     }
213     if (o instanceof ByteString) {
214       return o.equals(ByteString.EMPTY);
215     }
216     if (o instanceof MessageLite) { // Can happen in oneofs.
217       return o == ((MessageLite) o).getDefaultInstanceForType();
218     }
219     if (o instanceof java.lang.Enum<?>) { // Catches oneof enums.
220       return ((java.lang.Enum<?>) o).ordinal() == 0;
221     }
222 
223     return false;
224   }
225 
226   /**
227    * Formats a text proto field.
228    *
229    * <p>For use by generated code only.
230    *
231    * @param buffer the buffer to write to
232    * @param indent the number of spaces the proto should be indented by
233    * @param name the field name (in PascalCase)
234    * @param object the object value of the field
235    */
printField(StringBuilder buffer, int indent, String name, Object object)236   static void printField(StringBuilder buffer, int indent, String name, Object object) {
237     if (object instanceof List<?>) {
238       List<?> list = (List<?>) object;
239       for (Object entry : list) {
240         printField(buffer, indent, name, entry);
241       }
242       return;
243     }
244     if (object instanceof Map<?, ?>) {
245       Map<?, ?> map = (Map<?, ?>) object;
246       for (Map.Entry<?, ?> entry : map.entrySet()) {
247         printField(buffer, indent, name, entry);
248       }
249       return;
250     }
251 
252     buffer.append('\n');
253     indent(indent, buffer);
254     buffer.append(pascalCaseToSnakeCase(name));
255 
256     if (object instanceof String) {
257       buffer.append(": \"").append(TextFormatEscaper.escapeText((String) object)).append('"');
258     } else if (object instanceof ByteString) {
259       buffer.append(": \"").append(TextFormatEscaper.escapeBytes((ByteString) object)).append('"');
260     } else if (object instanceof GeneratedMessageLite) {
261       buffer.append(" {");
262       reflectivePrintWithIndent((GeneratedMessageLite<?, ?>) object, buffer, indent + 2);
263       buffer.append("\n");
264       indent(indent, buffer);
265       buffer.append("}");
266     } else if (object instanceof Map.Entry<?, ?>) {
267       buffer.append(" {");
268       Map.Entry<?, ?> entry = (Map.Entry<?, ?>) object;
269       printField(buffer, indent + 2, "key", entry.getKey());
270       printField(buffer, indent + 2, "value", entry.getValue());
271       buffer.append("\n");
272       indent(indent, buffer);
273       buffer.append("}");
274     } else {
275       buffer.append(": ").append(object.toString());
276     }
277   }
278 
indent(int indent, StringBuilder buffer)279   private static void indent(int indent, StringBuilder buffer) {
280     while (indent > 0) {
281       int partialIndent = indent;
282       if (partialIndent > INDENT_BUFFER.length) {
283         partialIndent = INDENT_BUFFER.length;
284       }
285       buffer.append(INDENT_BUFFER, 0, partialIndent);
286       indent -= partialIndent;
287     }
288   }
289 
pascalCaseToSnakeCase(String pascalCase)290   private static String pascalCaseToSnakeCase(String pascalCase) {
291     if (pascalCase.isEmpty()) {
292       return pascalCase;
293     }
294 
295     StringBuilder builder = new StringBuilder();
296     builder.append(Character.toLowerCase(pascalCase.charAt(0)));
297     for (int i = 1; i < pascalCase.length(); i++) {
298       char ch = pascalCase.charAt(i);
299       if (Character.isUpperCase(ch)) {
300         builder.append("_");
301       }
302       builder.append(Character.toLowerCase(ch));
303     }
304     return builder.toString();
305   }
306 }
307