MessageNanoPrinter.java revision e03e9f4b5774c0ffe04140d83bbdb532863b1720
1// Protocol Buffers - Google's data interchange format 2// Copyright 2013 Google Inc. All rights reserved. 3// http://code.google.com/p/protobuf/ 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 31package com.google.protobuf.nano; 32 33import java.lang.reflect.Array; 34import java.lang.reflect.Field; 35import java.lang.reflect.Modifier; 36 37/** 38 * Static helper methods for printing nano protos. 39 * 40 * @author flynn@google.com Andrew Flynn 41 */ 42public final class MessageNanoPrinter { 43 // Do not allow instantiation 44 private MessageNanoPrinter() {} 45 46 private static final String INDENT = " "; 47 private static final int MAX_STRING_LEN = 200; 48 49 /** 50 * Returns an text representation of a MessageNano suitable for debugging. 51 * 52 * <p>Employs Java reflection on the given object and recursively prints primitive fields, 53 * groups, and messages.</p> 54 */ 55 public static <T extends MessageNano> String print(T message) { 56 if (message == null) { 57 return "null"; 58 } 59 60 StringBuffer buf = new StringBuffer(); 61 try { 62 print(message.getClass().getSimpleName(), message.getClass(), message, 63 new StringBuffer(), buf); 64 } catch (IllegalAccessException e) { 65 return "Error printing proto: " + e.getMessage(); 66 } 67 return buf.toString(); 68 } 69 70 /** 71 * Function that will print the given message/class into the StringBuffer. 72 * Meant to be called recursively. 73 */ 74 private static void print(String identifier, Class<?> clazz, Object message, 75 StringBuffer indentBuf, StringBuffer buf) throws IllegalAccessException { 76 if (MessageNano.class.isAssignableFrom(clazz)) { 77 // Nano proto message 78 buf.append(indentBuf).append(identifier); 79 80 // If null, just print it and return 81 if (message == null) { 82 buf.append(": ").append(message).append("\n"); 83 return; 84 } 85 86 indentBuf.append(INDENT); 87 buf.append(" <\n"); 88 for (Field field : clazz.getFields()) { 89 // Proto fields are public, non-static variables that do not begin or end with '_' 90 int modifiers = field.getModifiers(); 91 String fieldName = field.getName(); 92 if ((modifiers & Modifier.PUBLIC) != Modifier.PUBLIC 93 || (modifiers & Modifier.STATIC) == Modifier.STATIC 94 || fieldName.startsWith("_") || fieldName.endsWith("_")) { 95 continue; 96 } 97 98 Class <?> fieldType = field.getType(); 99 Object value = field.get(message); 100 101 if (fieldType.isArray()) { 102 Class<?> arrayType = fieldType.getComponentType(); 103 104 // bytes is special since it's not repeated, but is represented by an array 105 if (arrayType == byte.class) { 106 print(fieldName, fieldType, value, indentBuf, buf); 107 } else { 108 int len = value == null ? 0 : Array.getLength(value); 109 for (int i = 0; i < len; i++) { 110 Object elem = Array.get(value, i); 111 print(fieldName, arrayType, elem, indentBuf, buf); 112 } 113 } 114 } else { 115 print(fieldName, fieldType, value, indentBuf, buf); 116 } 117 } 118 indentBuf.delete(indentBuf.length() - INDENT.length(), indentBuf.length()); 119 buf.append(indentBuf).append(">\n"); 120 } else { 121 // Primitive value 122 identifier = deCamelCaseify(identifier); 123 buf.append(indentBuf).append(identifier).append(": "); 124 if (message instanceof String) { 125 String stringMessage = sanitizeString((String) message); 126 buf.append("\"").append(stringMessage).append("\""); 127 } else { 128 buf.append(message); 129 } 130 buf.append("\n"); 131 } 132 } 133 134 /** 135 * Converts an identifier of the format "FieldName" into "field_name". 136 */ 137 private static String deCamelCaseify(String identifier) { 138 StringBuffer out = new StringBuffer(); 139 for (int i = 0; i < identifier.length(); i++) { 140 char currentChar = identifier.charAt(i); 141 if (i == 0) { 142 out.append(Character.toLowerCase(currentChar)); 143 } else if (Character.isUpperCase(currentChar)) { 144 out.append('_').append(Character.toLowerCase(currentChar)); 145 } else { 146 out.append(currentChar); 147 } 148 } 149 return out.toString(); 150 } 151 152 /** 153 * Shortens and escapes the given string. 154 */ 155 private static String sanitizeString(String str) { 156 if (!str.startsWith("http") && str.length() > MAX_STRING_LEN) { 157 // Trim non-URL strings. 158 str = str.substring(0, MAX_STRING_LEN) + "[...]"; 159 } 160 return escapeString(str); 161 } 162 163 /** 164 * Escape everything except for low ASCII code points. 165 */ 166 private static String escapeString(String str) { 167 int strLen = str.length(); 168 StringBuilder b = new StringBuilder(strLen); 169 for (int i = 0; i < strLen; i++) { 170 char original = str.charAt(i); 171 if (original >= ' ' && original <= '~' && original != '"' && original != '\'') { 172 b.append(original); 173 } else { 174 b.append(String.format("\\u%04x", (int) original)); 175 } 176 } 177 return b.toString(); 178 } 179} 180