MessageNanoPrinter.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. // Protocol Buffers - Google's data interchange format
  2. // Copyright 2013 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. package com.google.protobuf.nano;
  31. import java.lang.reflect.Array;
  32. import java.lang.reflect.Field;
  33. import java.lang.reflect.InvocationTargetException;
  34. import java.lang.reflect.Method;
  35. import java.lang.reflect.Modifier;
  36. import java.util.Map;
  37. /**
  38. * Static helper methods for printing nano protos.
  39. *
  40. * @author flynn@google.com Andrew Flynn
  41. */
  42. public final class MessageNanoPrinter {
  43. // Do not allow instantiation
  44. private MessageNanoPrinter() {}
  45. private static final String INDENT = " ";
  46. private static final int MAX_STRING_LEN = 200;
  47. /**
  48. * Returns an text representation of a MessageNano suitable for debugging. The returned string
  49. * is mostly compatible with Protocol Buffer's TextFormat (as provided by non-nano protocol
  50. * buffers) -- groups (which are deprecated) are output with an underscore name (e.g. foo_bar
  51. * instead of FooBar) and will thus not parse.
  52. *
  53. * <p>Employs Java reflection on the given object and recursively prints primitive fields,
  54. * groups, and messages.</p>
  55. */
  56. public static <T extends MessageNano> String print(T message) {
  57. if (message == null) {
  58. return "";
  59. }
  60. StringBuffer buf = new StringBuffer();
  61. try {
  62. print(null, message, new StringBuffer(), buf);
  63. } catch (IllegalAccessException e) {
  64. return "Error printing proto: " + e.getMessage();
  65. } catch (InvocationTargetException e) {
  66. return "Error printing proto: " + e.getMessage();
  67. }
  68. return buf.toString();
  69. }
  70. /**
  71. * Function that will print the given message/field into the StringBuffer.
  72. * Meant to be called recursively.
  73. *
  74. * @param identifier the identifier to use, or {@code null} if this is the root message to
  75. * print.
  76. * @param object the value to print. May in fact be a primitive value or byte array and not a
  77. * message.
  78. * @param indentBuf the indentation each line should begin with.
  79. * @param buf the output buffer.
  80. */
  81. private static void print(String identifier, Object object,
  82. StringBuffer indentBuf, StringBuffer buf) throws IllegalAccessException,
  83. InvocationTargetException {
  84. if (object == null) {
  85. // This can happen if...
  86. // - we're about to print a message, String, or byte[], but it not present;
  87. // - we're about to print a primitive, but "reftype" optional style is enabled, and
  88. // the field is unset.
  89. // In both cases the appropriate behavior is to output nothing.
  90. } else if (object instanceof MessageNano) { // Nano proto message
  91. int origIndentBufLength = indentBuf.length();
  92. if (identifier != null) {
  93. buf.append(indentBuf).append(deCamelCaseify(identifier)).append(" <\n");
  94. indentBuf.append(INDENT);
  95. }
  96. Class<?> clazz = object.getClass();
  97. // Proto fields follow one of two formats:
  98. //
  99. // 1) Public, non-static variables that do not begin or end with '_'
  100. // Find and print these using declared public fields
  101. for (Field field : clazz.getFields()) {
  102. int modifiers = field.getModifiers();
  103. String fieldName = field.getName();
  104. if ("cachedSize".equals(fieldName)) {
  105. // TODO(bduff): perhaps cachedSize should have a more obscure name.
  106. continue;
  107. }
  108. if ((modifiers & Modifier.PUBLIC) == Modifier.PUBLIC
  109. && (modifiers & Modifier.STATIC) != Modifier.STATIC
  110. && !fieldName.startsWith("_")
  111. && !fieldName.endsWith("_")) {
  112. Class<?> fieldType = field.getType();
  113. Object value = field.get(object);
  114. if (fieldType.isArray()) {
  115. Class<?> arrayType = fieldType.getComponentType();
  116. // bytes is special since it's not repeated, but is represented by an array
  117. if (arrayType == byte.class) {
  118. print(fieldName, value, indentBuf, buf);
  119. } else {
  120. int len = value == null ? 0 : Array.getLength(value);
  121. for (int i = 0; i < len; i++) {
  122. Object elem = Array.get(value, i);
  123. print(fieldName, elem, indentBuf, buf);
  124. }
  125. }
  126. } else {
  127. print(fieldName, value, indentBuf, buf);
  128. }
  129. }
  130. }
  131. // 2) Fields that are accessed via getter methods (when accessors
  132. // mode is turned on)
  133. // Find and print these using getter methods.
  134. for (Method method : clazz.getMethods()) {
  135. String name = method.getName();
  136. // Check for the setter accessor method since getters and hazzers both have
  137. // non-proto-field name collisions (hashCode() and getSerializedSize())
  138. if (name.startsWith("set")) {
  139. String subfieldName = name.substring(3);
  140. Method hazzer = null;
  141. try {
  142. hazzer = clazz.getMethod("has" + subfieldName);
  143. } catch (NoSuchMethodException e) {
  144. continue;
  145. }
  146. // If hazzer doesn't exist or returns false, no need to continue
  147. if (!(Boolean) hazzer.invoke(object)) {
  148. continue;
  149. }
  150. Method getter = null;
  151. try {
  152. getter = clazz.getMethod("get" + subfieldName);
  153. } catch (NoSuchMethodException e) {
  154. continue;
  155. }
  156. print(subfieldName, getter.invoke(object), indentBuf, buf);
  157. }
  158. }
  159. if (identifier != null) {
  160. indentBuf.setLength(origIndentBufLength);
  161. buf.append(indentBuf).append(">\n");
  162. }
  163. } else if (object instanceof Map) {
  164. Map<?,?> map = (Map<?,?>) object;
  165. identifier = deCamelCaseify(identifier);
  166. for (Map.Entry<?,?> entry : map.entrySet()) {
  167. buf.append(indentBuf).append(identifier).append(" <\n");
  168. int origIndentBufLength = indentBuf.length();
  169. indentBuf.append(INDENT);
  170. print("key", entry.getKey(), indentBuf, buf);
  171. print("value", entry.getValue(), indentBuf, buf);
  172. indentBuf.setLength(origIndentBufLength);
  173. buf.append(indentBuf).append(">\n");
  174. }
  175. } else {
  176. // Non-null primitive value
  177. identifier = deCamelCaseify(identifier);
  178. buf.append(indentBuf).append(identifier).append(": ");
  179. if (object instanceof String) {
  180. String stringMessage = sanitizeString((String) object);
  181. buf.append("\"").append(stringMessage).append("\"");
  182. } else if (object instanceof byte[]) {
  183. appendQuotedBytes((byte[]) object, buf);
  184. } else {
  185. buf.append(object);
  186. }
  187. buf.append("\n");
  188. }
  189. }
  190. /**
  191. * Converts an identifier of the format "FieldName" into "field_name".
  192. */
  193. private static String deCamelCaseify(String identifier) {
  194. StringBuffer out = new StringBuffer();
  195. for (int i = 0; i < identifier.length(); i++) {
  196. char currentChar = identifier.charAt(i);
  197. if (i == 0) {
  198. out.append(Character.toLowerCase(currentChar));
  199. } else if (Character.isUpperCase(currentChar)) {
  200. out.append('_').append(Character.toLowerCase(currentChar));
  201. } else {
  202. out.append(currentChar);
  203. }
  204. }
  205. return out.toString();
  206. }
  207. /**
  208. * Shortens and escapes the given string.
  209. */
  210. private static String sanitizeString(String str) {
  211. if (!str.startsWith("http") && str.length() > MAX_STRING_LEN) {
  212. // Trim non-URL strings.
  213. str = str.substring(0, MAX_STRING_LEN) + "[...]";
  214. }
  215. return escapeString(str);
  216. }
  217. /**
  218. * Escape everything except for low ASCII code points.
  219. */
  220. private static String escapeString(String str) {
  221. int strLen = str.length();
  222. StringBuilder b = new StringBuilder(strLen);
  223. for (int i = 0; i < strLen; i++) {
  224. char original = str.charAt(i);
  225. if (original >= ' ' && original <= '~' && original != '"' && original != '\'') {
  226. b.append(original);
  227. } else {
  228. b.append(String.format("\\u%04x", (int) original));
  229. }
  230. }
  231. return b.toString();
  232. }
  233. /**
  234. * Appends a quoted byte array to the provided {@code StringBuffer}.
  235. */
  236. private static void appendQuotedBytes(byte[] bytes, StringBuffer builder) {
  237. if (bytes == null) {
  238. builder.append("\"\"");
  239. return;
  240. }
  241. builder.append('"');
  242. for (int i = 0; i < bytes.length; ++i) {
  243. int ch = bytes[i] & 0xff;
  244. if (ch == '\\' || ch == '"') {
  245. builder.append('\\').append((char) ch);
  246. } else if (ch >= 32 && ch < 127) {
  247. builder.append((char) ch);
  248. } else {
  249. builder.append(String.format("\\%03o", ch));
  250. }
  251. }
  252. builder.append('"');
  253. }
  254. }