// 
// Decompiled by Procyon v0.6.0
// 

package com.google.protobuf;

import java.nio.Buffer;
import java.nio.CharBuffer;
import java.util.Map;
import java.util.Iterator;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.math.BigInteger;
import java.util.Locale;
import java.io.IOException;
import java.util.logging.Logger;

public final class TextFormat
{
    private static final Logger logger;
    private static final String DEBUG_STRING_SILENT_MARKER = " \t ";
    private static final String ENABLE_INSERT_SILENT_MARKER_ENV_NAME = "SILENT_MARKER_INSERTION_ENABLED";
    private static final boolean ENABLE_INSERT_SILENT_MARKER;
    private static final String REDACTED_MARKER = "[REDACTED]";
    private static final Parser PARSER;
    
    private TextFormat() {
    }
    
    @Deprecated
    public static String shortDebugString(final MessageOrBuilder message) {
        return printer().emittingSingleLine(true).printToString(message, Printer.FieldReporterLevel.SHORT_DEBUG_STRING);
    }
    
    public static void printUnknownFieldValue(final int tag, final Object value, final Appendable output) throws IOException {
        printUnknownFieldValue(tag, value, setSingleLineOutput(output, false), false);
    }
    
    private static void printUnknownFieldValue(final int tag, final Object value, final TextGenerator generator, final boolean redact) throws IOException {
        switch (WireFormat.getTagWireType(tag)) {
            case 0: {
                generator.print(unsignedToString((long)value));
                break;
            }
            case 5: {
                generator.print(String.format(null, "0x%08x", value));
                break;
            }
            case 1: {
                generator.print(String.format(null, "0x%016x", value));
                break;
            }
            case 2: {
                try {
                    final UnknownFieldSet message = UnknownFieldSet.parseFrom((ByteString)value);
                    generator.print("{");
                    generator.eol();
                    generator.indent();
                    printUnknownFields(message, generator, redact);
                    generator.outdent();
                    generator.print("}");
                }
                catch (final InvalidProtocolBufferException e) {
                    generator.print("\"");
                    generator.print(escapeBytes((ByteString)value));
                    generator.print("\"");
                }
                break;
            }
            case 3: {
                printUnknownFields((UnknownFieldSet)value, generator, redact);
                break;
            }
            default: {
                throw new IllegalArgumentException("Bad tag: " + tag);
            }
        }
    }
    
    public static Printer printer() {
        return Printer.DEFAULT_TEXT_FORMAT;
    }
    
    public static Printer debugFormatPrinter() {
        return Printer.DEFAULT_DEBUG_FORMAT;
    }
    
    public static Printer defaultFormatPrinter() {
        return Printer.DEFAULT_FORMAT;
    }
    
    @Deprecated
    @InlineMe(replacement = "TextFormat.printer().print(message, output)", imports = { "com.google.protobuf.TextFormat" })
    public static void print(final MessageOrBuilder message, final Appendable output) throws IOException {
        printer().print(message, output);
    }
    
    @Deprecated
    public static void printUnicode(final MessageOrBuilder message, final Appendable output) throws IOException {
        printer().escapingNonAscii(false).print(message, output, Printer.FieldReporterLevel.PRINT_UNICODE);
    }
    
    @Deprecated
    public static String printToString(final MessageOrBuilder message) {
        return printer().printToString(message, Printer.FieldReporterLevel.TEXTFORMAT_PRINT_TO_STRING);
    }
    
    @Deprecated
    public static String printToUnicodeString(final MessageOrBuilder message) {
        return printer().escapingNonAscii(false).printToString(message, Printer.FieldReporterLevel.PRINT_UNICODE);
    }
    
    @Deprecated
    @InlineMe(replacement = "TextFormat.printer().printFieldValue(field, value, output)", imports = { "com.google.protobuf.TextFormat" })
    public static void printFieldValue(final Descriptors.FieldDescriptor field, final Object value, final Appendable output) throws IOException {
        printer().printFieldValue(field, value, output);
    }
    
    public static String unsignedToString(final int value) {
        if (value >= 0) {
            return Integer.toString(value);
        }
        return Long.toString((long)value & 0xFFFFFFFFL);
    }
    
    public static String unsignedToString(final long value) {
        if (value >= 0L) {
            return Long.toString(value);
        }
        return BigInteger.valueOf(value & Long.MAX_VALUE).setBit(63).toString();
    }
    
    private static TextGenerator setSingleLineOutput(final Appendable output, final boolean singleLine) {
        return new TextGenerator(output, singleLine, (Descriptors.Descriptor)null, Printer.FieldReporterLevel.TEXT_GENERATOR, false);
    }
    
    private static TextGenerator setSingleLineOutput(final Appendable output, final boolean singleLine, final Descriptors.Descriptor rootMessageType, final Printer.FieldReporterLevel fieldReporterLevel, final boolean shouldEmitSilentMarker) {
        return new TextGenerator(output, singleLine, rootMessageType, fieldReporterLevel, shouldEmitSilentMarker);
    }
    
    public static Parser getParser() {
        return TextFormat.PARSER;
    }
    
    public static void merge(final Readable input, final Message.Builder builder) throws IOException {
        TextFormat.PARSER.merge(input, builder);
    }
    
    public static void merge(final CharSequence input, final Message.Builder builder) throws ParseException {
        TextFormat.PARSER.merge(input, builder);
    }
    
    public static <T extends Message> T parse(final CharSequence input, final Class<T> protoClass) throws ParseException {
        final Message.Builder builder = Internal.getDefaultInstance(protoClass).newBuilderForType();
        merge(input, builder);
        final T output = (T)builder.build();
        return output;
    }
    
    public static void merge(final Readable input, final ExtensionRegistry extensionRegistry, final Message.Builder builder) throws IOException {
        TextFormat.PARSER.merge(input, extensionRegistry, builder);
    }
    
    public static void merge(final CharSequence input, final ExtensionRegistry extensionRegistry, final Message.Builder builder) throws ParseException {
        TextFormat.PARSER.merge(input, extensionRegistry, builder);
    }
    
    public static <T extends Message> T parse(final CharSequence input, final ExtensionRegistry extensionRegistry, final Class<T> protoClass) throws ParseException {
        final Message.Builder builder = Internal.getDefaultInstance(protoClass).newBuilderForType();
        merge(input, extensionRegistry, builder);
        final T output = (T)builder.build();
        return output;
    }
    
    public static String escapeBytes(final ByteString input) {
        return TextFormatEscaper.escapeBytes(input);
    }
    
    public static String escapeBytes(final byte[] input) {
        return TextFormatEscaper.escapeBytes(input);
    }
    
    public static ByteString unescapeBytes(final CharSequence charString) throws InvalidEscapeSequenceException {
        final ByteString input = ByteString.copyFromUtf8(charString.toString());
        final byte[] result = new byte[input.size()];
        int pos = 0;
        for (int i = 0; i < input.size(); ++i) {
            byte c = input.byteAt(i);
            if (c == 92) {
                if (i + 1 >= input.size()) {
                    throw new InvalidEscapeSequenceException("Invalid escape sequence: '\\' at end of string.");
                }
                ++i;
                c = input.byteAt(i);
                if (isOctal(c)) {
                    int code = digitValue(c);
                    if (i + 1 < input.size() && isOctal(input.byteAt(i + 1))) {
                        ++i;
                        code = code * 8 + digitValue(input.byteAt(i));
                    }
                    if (i + 1 < input.size() && isOctal(input.byteAt(i + 1))) {
                        ++i;
                        code = code * 8 + digitValue(input.byteAt(i));
                    }
                    result[pos++] = (byte)code;
                }
                else {
                    switch (c) {
                        case 97: {
                            result[pos++] = 7;
                            break;
                        }
                        case 98: {
                            result[pos++] = 8;
                            break;
                        }
                        case 102: {
                            result[pos++] = 12;
                            break;
                        }
                        case 110: {
                            result[pos++] = 10;
                            break;
                        }
                        case 114: {
                            result[pos++] = 13;
                            break;
                        }
                        case 116: {
                            result[pos++] = 9;
                            break;
                        }
                        case 118: {
                            result[pos++] = 11;
                            break;
                        }
                        case 92: {
                            result[pos++] = 92;
                            break;
                        }
                        case 39: {
                            result[pos++] = 39;
                            break;
                        }
                        case 34: {
                            result[pos++] = 34;
                            break;
                        }
                        case 63: {
                            result[pos++] = 63;
                            break;
                        }
                        case 120: {
                            int code = 0;
                            if (i + 1 < input.size() && isHex(input.byteAt(i + 1))) {
                                ++i;
                                code = digitValue(input.byteAt(i));
                                if (i + 1 < input.size() && isHex(input.byteAt(i + 1))) {
                                    ++i;
                                    code = code * 16 + digitValue(input.byteAt(i));
                                }
                                result[pos++] = (byte)code;
                                break;
                            }
                            throw new InvalidEscapeSequenceException("Invalid escape sequence: '\\x' with no digits");
                        }
                        case 117: {
                            if (++i + 3 >= input.size() || !isHex(input.byteAt(i)) || !isHex(input.byteAt(i + 1)) || !isHex(input.byteAt(i + 2)) || !isHex(input.byteAt(i + 3))) {
                                throw new InvalidEscapeSequenceException("Invalid escape sequence: '\\u' with too few hex chars");
                            }
                            final char ch = (char)(digitValue(input.byteAt(i)) << 12 | digitValue(input.byteAt(i + 1)) << 8 | digitValue(input.byteAt(i + 2)) << 4 | digitValue(input.byteAt(i + 3)));
                            if (ch >= '\ud800' && ch <= '\udfff') {
                                throw new InvalidEscapeSequenceException("Invalid escape sequence: '\\u' refers to a surrogate");
                            }
                            final byte[] chUtf8 = Character.toString(ch).getBytes(Internal.UTF_8);
                            System.arraycopy(chUtf8, 0, result, pos, chUtf8.length);
                            pos += chUtf8.length;
                            i += 3;
                            break;
                        }
                        case 85: {
                            if (++i + 7 >= input.size()) {
                                throw new InvalidEscapeSequenceException("Invalid escape sequence: '\\U' with too few hex chars");
                            }
                            int codepoint = 0;
                            for (int offset = i; offset < i + 8; ++offset) {
                                final byte b = input.byteAt(offset);
                                if (!isHex(b)) {
                                    throw new InvalidEscapeSequenceException("Invalid escape sequence: '\\U' with too few hex chars");
                                }
                                codepoint = (codepoint << 4 | digitValue(b));
                            }
                            if (!Character.isValidCodePoint(codepoint)) {
                                throw new InvalidEscapeSequenceException("Invalid escape sequence: '\\U" + input.substring(i, i + 8).toStringUtf8() + "' is not a valid code point value");
                            }
                            final Character.UnicodeBlock unicodeBlock = Character.UnicodeBlock.of(codepoint);
                            if (unicodeBlock != null && (unicodeBlock.equals(Character.UnicodeBlock.LOW_SURROGATES) || unicodeBlock.equals(Character.UnicodeBlock.HIGH_SURROGATES) || unicodeBlock.equals(Character.UnicodeBlock.HIGH_PRIVATE_USE_SURROGATES))) {
                                throw new InvalidEscapeSequenceException("Invalid escape sequence: '\\U" + input.substring(i, i + 8).toStringUtf8() + "' refers to a surrogate code unit");
                            }
                            final int[] codepoints = { codepoint };
                            final byte[] chUtf9 = new String(codepoints, 0, 1).getBytes(Internal.UTF_8);
                            System.arraycopy(chUtf9, 0, result, pos, chUtf9.length);
                            pos += chUtf9.length;
                            i += 7;
                            break;
                        }
                        default: {
                            throw new InvalidEscapeSequenceException("Invalid escape sequence: '\\" + (char)c + '\'');
                        }
                    }
                }
            }
            else {
                result[pos++] = c;
            }
        }
        return (result.length == pos) ? ByteString.wrap(result) : ByteString.copyFrom(result, 0, pos);
    }
    
    static String escapeText(final String input) {
        return escapeBytes(ByteString.copyFromUtf8(input));
    }
    
    public static String escapeDoubleQuotesAndBackslashes(final String input) {
        return TextFormatEscaper.escapeDoubleQuotesAndBackslashes(input);
    }
    
    static String unescapeText(final String input) throws InvalidEscapeSequenceException {
        return unescapeBytes(input).toStringUtf8();
    }
    
    private static boolean isOctal(final byte c) {
        return 48 <= c && c <= 55;
    }
    
    private static boolean isHex(final byte c) {
        return (48 <= c && c <= 57) || (97 <= c && c <= 102) || (65 <= c && c <= 70);
    }
    
    private static int digitValue(final byte c) {
        if (48 <= c && c <= 57) {
            return c - 48;
        }
        if (97 <= c && c <= 122) {
            return c - 97 + 10;
        }
        return c - 65 + 10;
    }
    
    static int parseInt32(final String text) throws NumberFormatException {
        return (int)parseInteger(text, true, false);
    }
    
    static int parseUInt32(final String text) throws NumberFormatException {
        return (int)parseInteger(text, false, false);
    }
    
    static long parseInt64(final String text) throws NumberFormatException {
        return parseInteger(text, true, true);
    }
    
    static long parseUInt64(final String text) throws NumberFormatException {
        return parseInteger(text, false, true);
    }
    
    private static long parseInteger(final String text, final boolean isSigned, final boolean isLong) throws NumberFormatException {
        int pos = 0;
        boolean negative = false;
        if (text.startsWith("-", pos)) {
            if (!isSigned) {
                throw new NumberFormatException("Number must be positive: " + text);
            }
            ++pos;
            negative = true;
        }
        int radix = 10;
        if (text.startsWith("0x", pos)) {
            pos += 2;
            radix = 16;
        }
        else if (text.startsWith("0", pos)) {
            radix = 8;
        }
        final String numberText = text.substring(pos);
        long result = 0L;
        if (numberText.length() < 16) {
            result = Long.parseLong(numberText, radix);
            if (negative) {
                result = -result;
            }
            if (!isLong) {
                if (isSigned) {
                    if (result > 2147483647L || result < -2147483648L) {
                        throw new NumberFormatException("Number out of range for 32-bit signed integer: " + text);
                    }
                }
                else if (result >= 4294967296L || result < 0L) {
                    throw new NumberFormatException("Number out of range for 32-bit unsigned integer: " + text);
                }
            }
        }
        else {
            BigInteger bigValue = new BigInteger(numberText, radix);
            if (negative) {
                bigValue = bigValue.negate();
            }
            if (!isLong) {
                if (isSigned) {
                    if (bigValue.bitLength() > 31) {
                        throw new NumberFormatException("Number out of range for 32-bit signed integer: " + text);
                    }
                }
                else if (bigValue.bitLength() > 32) {
                    throw new NumberFormatException("Number out of range for 32-bit unsigned integer: " + text);
                }
            }
            else if (isSigned) {
                if (bigValue.bitLength() > 63) {
                    throw new NumberFormatException("Number out of range for 64-bit signed integer: " + text);
                }
            }
            else if (bigValue.bitLength() > 64) {
                throw new NumberFormatException("Number out of range for 64-bit unsigned integer: " + text);
            }
            result = bigValue.longValue();
        }
        return result;
    }
    
    static {
        logger = Logger.getLogger(TextFormat.class.getName());
        ENABLE_INSERT_SILENT_MARKER = System.getenv().getOrDefault("SILENT_MARKER_INSERTION_ENABLED", "false").equals("true");
        PARSER = Parser.newBuilder().build();
    }
    
    public static final class Printer
    {
        private static final Printer DEFAULT_TEXT_FORMAT;
        private static final Printer DEFAULT_DEBUG_FORMAT;
        private static final Printer DEFAULT_FORMAT;
        private final boolean escapeNonAscii;
        private final boolean useShortRepeatedPrimitives;
        private final TypeRegistry typeRegistry;
        private final ExtensionRegistryLite extensionRegistry;
        private final boolean enablingSafeDebugFormat;
        private final boolean singleLine;
        private boolean insertSilentMarker;
        private static final ThreadLocal<FieldReporterLevel> sensitiveFieldReportingLevel;
        
        static Printer getOutputModePrinter() {
            if (ProtobufToStringOutput.isDefaultFormat()) {
                return TextFormat.defaultFormatPrinter();
            }
            if (ProtobufToStringOutput.shouldOutputDebugFormat()) {
                return TextFormat.debugFormatPrinter();
            }
            return TextFormat.printer();
        }
        
        @CanIgnoreReturnValue
        private Printer setInsertSilentMarker(final boolean insertSilentMarker) {
            this.insertSilentMarker = insertSilentMarker;
            return this;
        }
        
        private Printer(final boolean escapeNonAscii, final boolean useShortRepeatedPrimitives, final TypeRegistry typeRegistry, final ExtensionRegistryLite extensionRegistry, final boolean enablingSafeDebugFormat, final boolean singleLine) {
            this.escapeNonAscii = escapeNonAscii;
            this.useShortRepeatedPrimitives = useShortRepeatedPrimitives;
            this.typeRegistry = typeRegistry;
            this.extensionRegistry = extensionRegistry;
            this.enablingSafeDebugFormat = enablingSafeDebugFormat;
            this.singleLine = singleLine;
            this.insertSilentMarker = false;
        }
        
        public Printer escapingNonAscii(final boolean escapeNonAscii) {
            return new Printer(escapeNonAscii, this.useShortRepeatedPrimitives, this.typeRegistry, this.extensionRegistry, this.enablingSafeDebugFormat, this.singleLine);
        }
        
        public Printer usingTypeRegistry(final TypeRegistry typeRegistry) {
            if (this.typeRegistry != TypeRegistry.getEmptyTypeRegistry()) {
                throw new IllegalArgumentException("Only one typeRegistry is allowed.");
            }
            return new Printer(this.escapeNonAscii, this.useShortRepeatedPrimitives, typeRegistry, this.extensionRegistry, this.enablingSafeDebugFormat, this.singleLine);
        }
        
        public Printer usingExtensionRegistry(final ExtensionRegistryLite extensionRegistry) {
            if (this.extensionRegistry != ExtensionRegistryLite.getEmptyRegistry()) {
                throw new IllegalArgumentException("Only one extensionRegistry is allowed.");
            }
            return new Printer(this.escapeNonAscii, this.useShortRepeatedPrimitives, this.typeRegistry, extensionRegistry, this.enablingSafeDebugFormat, this.singleLine);
        }
        
        Printer enablingSafeDebugFormat(final boolean enablingSafeDebugFormat) {
            return new Printer(this.escapeNonAscii, this.useShortRepeatedPrimitives, this.typeRegistry, this.extensionRegistry, enablingSafeDebugFormat, this.singleLine);
        }
        
        public Printer usingShortRepeatedPrimitives(final boolean useShortRepeatedPrimitives) {
            return new Printer(this.escapeNonAscii, useShortRepeatedPrimitives, this.typeRegistry, this.extensionRegistry, this.enablingSafeDebugFormat, this.singleLine);
        }
        
        public Printer emittingSingleLine(final boolean singleLine) {
            return new Printer(this.escapeNonAscii, this.useShortRepeatedPrimitives, this.typeRegistry, this.extensionRegistry, this.enablingSafeDebugFormat, singleLine);
        }
        
        void setSensitiveFieldReportingLevel(final FieldReporterLevel level) {
            Printer.sensitiveFieldReportingLevel.set(level);
        }
        
        public void print(final MessageOrBuilder message, final Appendable output) throws IOException {
            this.print(message, output, FieldReporterLevel.PRINT);
        }
        
        void print(final MessageOrBuilder message, final Appendable output, final FieldReporterLevel level) throws IOException {
            final TextGenerator generator = setSingleLineOutput(output, this.singleLine, message.getDescriptorForType(), level, this.insertSilentMarker);
            this.print(message, generator);
        }
        
        public void print(final UnknownFieldSet fields, final Appendable output) throws IOException {
            printUnknownFields(fields, setSingleLineOutput(output, this.singleLine), this.enablingSafeDebugFormat);
        }
        
        private void print(final MessageOrBuilder message, final TextGenerator generator) throws IOException {
            if (message.getDescriptorForType().getFullName().equals("google.protobuf.Any") && this.printAny(message, generator)) {
                return;
            }
            this.printMessage(message, generator);
        }
        
        private void applyUnstablePrefix(final Appendable output) {
            try {
                output.append("");
            }
            catch (final IOException e) {
                throw new IllegalStateException(e);
            }
        }
        
        private boolean printAny(final MessageOrBuilder message, final TextGenerator generator) throws IOException {
            final Descriptors.Descriptor messageType = message.getDescriptorForType();
            final Descriptors.FieldDescriptor typeUrlField = messageType.findFieldByNumber(1);
            final Descriptors.FieldDescriptor valueField = messageType.findFieldByNumber(2);
            if (typeUrlField == null || typeUrlField.getType() != Descriptors.FieldDescriptor.Type.STRING || valueField == null || valueField.getType() != Descriptors.FieldDescriptor.Type.BYTES) {
                return false;
            }
            final String typeUrl = (String)message.getField(typeUrlField);
            if (typeUrl.isEmpty()) {
                return false;
            }
            final Object value = message.getField(valueField);
            Message.Builder contentBuilder = null;
            try {
                final Descriptors.Descriptor contentType = this.typeRegistry.getDescriptorForTypeUrl(typeUrl);
                if (contentType == null) {
                    return false;
                }
                contentBuilder = DynamicMessage.getDefaultInstance(contentType).newBuilderForType();
                contentBuilder.mergeFrom((ByteString)value, this.extensionRegistry);
            }
            catch (final InvalidProtocolBufferException e) {
                return false;
            }
            generator.print("[");
            generator.print(typeUrl);
            generator.print("]");
            generator.maybePrintSilentMarker();
            generator.print("{");
            generator.eol();
            generator.indent();
            this.print(contentBuilder, generator);
            generator.outdent();
            generator.print("}");
            generator.eol();
            return true;
        }
        
        public String printFieldToString(final Descriptors.FieldDescriptor field, final Object value) {
            try {
                final StringBuilder text = new StringBuilder();
                if (this.enablingSafeDebugFormat) {
                    this.applyUnstablePrefix(text);
                }
                this.printField(field, value, text);
                return text.toString();
            }
            catch (final IOException e) {
                throw new IllegalStateException(e);
            }
        }
        
        public void printField(final Descriptors.FieldDescriptor field, final Object value, final Appendable output) throws IOException {
            this.printField(field, value, setSingleLineOutput(output, this.singleLine));
        }
        
        private void printField(final Descriptors.FieldDescriptor field, final Object value, final TextGenerator generator) throws IOException {
            if (field.isMapField()) {
                final List<MapEntryAdapter> adapters = new ArrayList<MapEntryAdapter>();
                for (final Object entry : (List)value) {
                    adapters.add(new MapEntryAdapter(entry, field));
                }
                Collections.sort(adapters);
                for (final MapEntryAdapter adapter : adapters) {
                    this.printSingleField(field, adapter.getEntry(), generator);
                }
            }
            else if (field.isRepeated()) {
                if (this.useShortRepeatedPrimitives && field.getJavaType() != Descriptors.FieldDescriptor.JavaType.MESSAGE) {
                    this.printShortRepeatedField(field, value, generator);
                }
                else {
                    for (final Object element : (List)value) {
                        this.printSingleField(field, element, generator);
                    }
                }
            }
            else {
                this.printSingleField(field, value, generator);
            }
        }
        
        public void printFieldValue(final Descriptors.FieldDescriptor field, final Object value, final Appendable output) throws IOException {
            this.printFieldValue(field, value, setSingleLineOutput(output, this.singleLine));
        }
        
        private void printFieldValue(final Descriptors.FieldDescriptor field, final Object value, final TextGenerator generator) throws IOException {
            if (this.shouldRedact(field, generator)) {
                generator.print("[REDACTED]");
                if (field.getJavaType() == Descriptors.FieldDescriptor.JavaType.MESSAGE) {
                    generator.eol();
                }
                return;
            }
            switch (field.getType()) {
                case INT32:
                case SINT32:
                case SFIXED32: {
                    generator.print(((Integer)value).toString());
                    break;
                }
                case INT64:
                case SINT64:
                case SFIXED64: {
                    generator.print(((Long)value).toString());
                    break;
                }
                case BOOL: {
                    generator.print(((Boolean)value).toString());
                    break;
                }
                case FLOAT: {
                    generator.print(((Float)value).toString());
                    break;
                }
                case DOUBLE: {
                    generator.print(((Double)value).toString());
                    break;
                }
                case UINT32:
                case FIXED32: {
                    generator.print(TextFormat.unsignedToString((int)value));
                    break;
                }
                case UINT64:
                case FIXED64: {
                    generator.print(TextFormat.unsignedToString((long)value));
                    break;
                }
                case STRING: {
                    generator.print("\"");
                    generator.print(this.escapeNonAscii ? TextFormatEscaper.escapeText((String)value) : TextFormat.escapeDoubleQuotesAndBackslashes((String)value).replace("\n", "\\n"));
                    generator.print("\"");
                    break;
                }
                case BYTES: {
                    generator.print("\"");
                    if (value instanceof ByteString) {
                        generator.print(TextFormat.escapeBytes((ByteString)value));
                    }
                    else {
                        generator.print(TextFormat.escapeBytes((byte[])value));
                    }
                    generator.print("\"");
                    break;
                }
                case ENUM: {
                    if (((Descriptors.EnumValueDescriptor)value).getIndex() == -1) {
                        generator.print(Integer.toString(((Descriptors.EnumValueDescriptor)value).getNumber()));
                        break;
                    }
                    generator.print(((Descriptors.EnumValueDescriptor)value).getName());
                    break;
                }
                case MESSAGE:
                case GROUP: {
                    this.print((MessageOrBuilder)value, generator);
                    break;
                }
            }
        }
        
        private boolean shouldRedact(final Descriptors.FieldDescriptor field, final TextGenerator generator) {
            final Descriptors.FieldDescriptor.RedactionState state = field.getRedactionState();
            return this.enablingSafeDebugFormat && state.redact;
        }
        
        public String printToString(final MessageOrBuilder message) {
            return this.printToString(message, FieldReporterLevel.PRINTER_PRINT_TO_STRING);
        }
        
        String printToString(final MessageOrBuilder message, final FieldReporterLevel level) {
            try {
                final StringBuilder text = new StringBuilder();
                if (this.enablingSafeDebugFormat) {
                    this.applyUnstablePrefix(text);
                }
                this.print(message, text, level);
                return text.toString();
            }
            catch (final IOException e) {
                throw new IllegalStateException(e);
            }
        }
        
        public String printToString(final UnknownFieldSet fields) {
            try {
                final StringBuilder text = new StringBuilder();
                if (this.enablingSafeDebugFormat) {
                    this.applyUnstablePrefix(text);
                }
                this.print(fields, text);
                return text.toString();
            }
            catch (final IOException e) {
                throw new IllegalStateException(e);
            }
        }
        
        @Deprecated
        public String shortDebugString(final MessageOrBuilder message) {
            return this.emittingSingleLine(true).printToString(message, FieldReporterLevel.SHORT_DEBUG_STRING);
        }
        
        @Deprecated
        @InlineMe(replacement = "this.emittingSingleLine(true).printFieldToString(field, value)")
        public String shortDebugString(final Descriptors.FieldDescriptor field, final Object value) {
            return this.emittingSingleLine(true).printFieldToString(field, value);
        }
        
        @Deprecated
        @InlineMe(replacement = "this.emittingSingleLine(true).printToString(fields)")
        public String shortDebugString(final UnknownFieldSet fields) {
            return this.emittingSingleLine(true).printToString(fields);
        }
        
        private static void printUnknownFieldValue(final int tag, final Object value, final TextGenerator generator, final boolean redact) throws IOException {
            switch (WireFormat.getTagWireType(tag)) {
                case 0: {
                    generator.print(redact ? String.format("UNKNOWN_VARINT %s", "[REDACTED]") : TextFormat.unsignedToString((long)value));
                    break;
                }
                case 5: {
                    generator.print(redact ? String.format("UNKNOWN_FIXED32 %s", "[REDACTED]") : String.format(null, "0x%08x", value));
                    break;
                }
                case 1: {
                    generator.print(redact ? String.format("UNKNOWN_FIXED64 %s", "[REDACTED]") : String.format(null, "0x%016x", value));
                    break;
                }
                case 2: {
                    try {
                        final UnknownFieldSet message = UnknownFieldSet.parseFrom((ByteString)value);
                        generator.print("{");
                        generator.eol();
                        generator.indent();
                        printUnknownFields(message, generator, redact);
                        generator.outdent();
                        generator.print("}");
                    }
                    catch (final InvalidProtocolBufferException e) {
                        if (redact) {
                            generator.print(String.format("UNKNOWN_STRING %s", "[REDACTED]"));
                        }
                        else {
                            generator.print("\"");
                            generator.print(TextFormat.escapeBytes((ByteString)value));
                            generator.print("\"");
                        }
                    }
                    break;
                }
                case 3: {
                    printUnknownFields((UnknownFieldSet)value, generator, redact);
                    break;
                }
                default: {
                    throw new IllegalArgumentException("Bad tag: " + tag);
                }
            }
        }
        
        private void printMessage(final MessageOrBuilder message, final TextGenerator generator) throws IOException {
            for (final Map.Entry<Descriptors.FieldDescriptor, Object> field : message.getAllFields().entrySet()) {
                this.printField(field.getKey(), field.getValue(), generator);
            }
            printUnknownFields(message.getUnknownFields(), generator, this.enablingSafeDebugFormat);
        }
        
        private void printShortRepeatedField(final Descriptors.FieldDescriptor field, final Object value, final TextGenerator generator) throws IOException {
            generator.print(field.getName());
            generator.print(": ");
            generator.print("[");
            String separator = "";
            for (final Object element : (List)value) {
                generator.print(separator);
                this.printFieldValue(field, element, generator);
                separator = ", ";
            }
            generator.print("]");
            generator.eol();
        }
        
        private void printSingleField(final Descriptors.FieldDescriptor field, final Object value, final TextGenerator generator) throws IOException {
            if (field.isExtension()) {
                generator.print("[");
                if (field.getContainingType().getOptions().getMessageSetWireFormat() && field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE && field.isOptional() && field.getExtensionScope() == field.getMessageType()) {
                    generator.print(field.getMessageType().getFullName());
                }
                else {
                    generator.print(field.getFullName());
                }
                generator.print("]");
            }
            else if (field.isGroupLike()) {
                generator.print(field.getMessageType().getName());
            }
            else {
                generator.print(field.getName());
            }
            if (field.getJavaType() == Descriptors.FieldDescriptor.JavaType.MESSAGE) {
                generator.maybePrintSilentMarker();
                generator.print("{");
                generator.eol();
                generator.indent();
            }
            else {
                generator.print(":");
                generator.maybePrintSilentMarker();
            }
            this.printFieldValue(field, value, generator);
            if (field.getJavaType() == Descriptors.FieldDescriptor.JavaType.MESSAGE) {
                generator.outdent();
                generator.print("}");
            }
            generator.eol();
        }
        
        private static void printUnknownFields(final UnknownFieldSet unknownFields, final TextGenerator generator, final boolean redact) throws IOException {
            if (unknownFields.isEmpty()) {
                return;
            }
            for (final Map.Entry<Integer, UnknownFieldSet.Field> entry : unknownFields.asMap().entrySet()) {
                final int number = entry.getKey();
                final UnknownFieldSet.Field field = entry.getValue();
                printUnknownField(number, 0, field.getVarintList(), generator, redact);
                printUnknownField(number, 5, field.getFixed32List(), generator, redact);
                printUnknownField(number, 1, field.getFixed64List(), generator, redact);
                printUnknownField(number, 2, field.getLengthDelimitedList(), generator, redact);
                for (final UnknownFieldSet value : field.getGroupList()) {
                    generator.print(entry.getKey().toString());
                    generator.maybePrintSilentMarker();
                    generator.print("{");
                    generator.eol();
                    generator.indent();
                    printUnknownFields(value, generator, redact);
                    generator.outdent();
                    generator.print("}");
                    generator.eol();
                }
            }
        }
        
        private static void printUnknownField(final int number, final int wireType, final List<?> values, final TextGenerator generator, final boolean redact) throws IOException {
            for (final Object value : values) {
                generator.print(String.valueOf(number));
                generator.print(":");
                generator.maybePrintSilentMarker();
                printUnknownFieldValue(wireType, value, generator, redact);
                generator.eol();
            }
        }
        
        static {
            DEFAULT_TEXT_FORMAT = new Printer(true, false, TypeRegistry.getEmptyTypeRegistry(), ExtensionRegistryLite.getEmptyRegistry(), false, false);
            DEFAULT_DEBUG_FORMAT = new Printer(true, false, TypeRegistry.getEmptyTypeRegistry(), ExtensionRegistryLite.getEmptyRegistry(), true, false);
            DEFAULT_FORMAT = new Printer(true, false, TypeRegistry.getEmptyTypeRegistry(), ExtensionRegistryLite.getEmptyRegistry(), false, false).setInsertSilentMarker(TextFormat.ENABLE_INSERT_SILENT_MARKER);
            sensitiveFieldReportingLevel = new ThreadLocal<FieldReporterLevel>() {
                @Override
                protected FieldReporterLevel initialValue() {
                    return FieldReporterLevel.ABSTRACT_TO_STRING;
                }
            };
        }
        
        enum FieldReporterLevel
        {
            REPORT_ALL(0), 
            TEXT_GENERATOR(1), 
            PRINT(2), 
            PRINTER_PRINT_TO_STRING(3), 
            TEXTFORMAT_PRINT_TO_STRING(4), 
            PRINT_UNICODE(5), 
            SHORT_DEBUG_STRING(6), 
            LEGACY_MULTILINE(7), 
            LEGACY_SINGLE_LINE(8), 
            DEBUG_MULTILINE(9), 
            DEBUG_SINGLE_LINE(10), 
            ABSTRACT_TO_STRING(11), 
            ABSTRACT_BUILDER_TO_STRING(12), 
            ABSTRACT_MUTABLE_TO_STRING(13), 
            REPORT_NONE(14);
            
            private final int index;
            
            private FieldReporterLevel(final int index) {
                this.index = index;
            }
        }
        
        static class MapEntryAdapter implements Comparable<MapEntryAdapter>
        {
            private Object entry;
            private Message messageEntry;
            private final Descriptors.FieldDescriptor keyField;
            
            MapEntryAdapter(final Object entry, final Descriptors.FieldDescriptor fieldDescriptor) {
                if (entry instanceof Message) {
                    this.messageEntry = (Message)entry;
                }
                else {
                    this.entry = entry;
                }
                this.keyField = fieldDescriptor.getMessageType().findFieldByName("key");
            }
            
            Object getKey() {
                if (this.messageEntry != null && this.keyField != null) {
                    return this.messageEntry.getField(this.keyField);
                }
                return null;
            }
            
            Object getEntry() {
                if (this.messageEntry != null) {
                    return this.messageEntry;
                }
                return this.entry;
            }
            
            @Override
            public int compareTo(final MapEntryAdapter b) {
                final Object aKey = this.getKey();
                final Object bKey = b.getKey();
                if (aKey == null && bKey == null) {
                    return 0;
                }
                if (aKey == null) {
                    return -1;
                }
                if (bKey == null) {
                    return 1;
                }
                switch (this.keyField.getJavaType()) {
                    case BOOLEAN: {
                        return ((Boolean)aKey).compareTo((Boolean)bKey);
                    }
                    case LONG: {
                        return ((Long)aKey).compareTo((Long)bKey);
                    }
                    case INT: {
                        return ((Integer)aKey).compareTo((Integer)bKey);
                    }
                    case STRING: {
                        return ((String)aKey).compareTo((String)bKey);
                    }
                    default: {
                        return 0;
                    }
                }
            }
        }
    }
    
    private static final class TextGenerator
    {
        private final Appendable output;
        private final StringBuilder indent;
        private final boolean singleLineMode;
        private boolean shouldEmitSilentMarker;
        private boolean atStartOfLine;
        private final Printer.FieldReporterLevel fieldReporterLevel;
        private final Descriptors.Descriptor rootMessageType;
        
        private TextGenerator(final Appendable output, final boolean singleLineMode, final Descriptors.Descriptor rootMessageType, final Printer.FieldReporterLevel fieldReporterLevel, final boolean shouldEmitSilentMarker) {
            this.indent = new StringBuilder();
            this.atStartOfLine = false;
            this.output = output;
            this.singleLineMode = singleLineMode;
            this.rootMessageType = rootMessageType;
            this.fieldReporterLevel = fieldReporterLevel;
            this.shouldEmitSilentMarker = shouldEmitSilentMarker;
        }
        
        public void indent() {
            this.indent.append("  ");
        }
        
        public void outdent() {
            final int length = this.indent.length();
            if (length == 0) {
                throw new IllegalArgumentException(" Outdent() without matching Indent().");
            }
            this.indent.setLength();
        }
        
        public void print(final CharSequence text) throws IOException {
            if (this.atStartOfLine) {
                this.atStartOfLine = false;
                this.output.append((CharSequence)(this.singleLineMode ? " " : this.indent));
            }
            this.output.append(text);
        }
        
        public void eol() throws IOException {
            if (!this.singleLineMode) {
                this.output.append("\n");
            }
            this.atStartOfLine = true;
        }
        
        void maybePrintSilentMarker() throws IOException {
            if (this.shouldEmitSilentMarker) {
                this.output.append(" \t ");
                this.shouldEmitSilentMarker = false;
            }
            else {
                this.output.append(" ");
            }
        }
    }
    
    private static final class Tokenizer
    {
        private final CharSequence text;
        private String currentToken;
        private int pos;
        private int line;
        private int column;
        private int lineInfoTrackingPos;
        private int previousLine;
        private int previousColumn;
        private boolean containsSilentMarkerAfterCurrentToken;
        private boolean containsSilentMarkerAfterPrevToken;
        
        private Tokenizer(final CharSequence text) {
            this.pos = 0;
            this.line = 0;
            this.column = 0;
            this.lineInfoTrackingPos = 0;
            this.previousLine = 0;
            this.previousColumn = 0;
            this.containsSilentMarkerAfterCurrentToken = false;
            this.containsSilentMarkerAfterPrevToken = false;
            this.text = text;
            this.skipWhitespace();
            this.nextToken();
        }
        
        int getPreviousLine() {
            return this.previousLine;
        }
        
        int getPreviousColumn() {
            return this.previousColumn;
        }
        
        int getLine() {
            return this.line;
        }
        
        int getColumn() {
            return this.column;
        }
        
        boolean getContainsSilentMarkerAfterCurrentToken() {
            return this.containsSilentMarkerAfterCurrentToken;
        }
        
        boolean getContainsSilentMarkerAfterPrevToken() {
            return this.containsSilentMarkerAfterPrevToken;
        }
        
        boolean atEnd() {
            return this.currentToken.length() == 0;
        }
        
        void nextToken() {
            this.previousLine = this.line;
            this.previousColumn = this.column;
            while (this.lineInfoTrackingPos < this.pos) {
                if (this.text.charAt(this.lineInfoTrackingPos) == '\n') {
                    ++this.line;
                    this.column = 0;
                }
                else {
                    ++this.column;
                }
                ++this.lineInfoTrackingPos;
            }
            if (this.pos == this.text.length()) {
                this.currentToken = "";
            }
            else {
                this.currentToken = this.nextTokenInternal();
                this.skipWhitespace();
            }
        }
        
        private String nextTokenInternal() {
            final int textLength = this.text.length();
            final int startPos = this.pos;
            final char startChar = this.text.charAt(startPos);
            int endPos = this.pos;
            if (isAlphaUnder(startChar)) {
                while (++endPos != textLength) {
                    final char c = this.text.charAt(endPos);
                    if (!isAlphaUnder(c) && !isDigitPlusMinus(c)) {
                        break;
                    }
                }
            }
            else if (isDigitPlusMinus(startChar) || startChar == '.') {
                if (startChar == '.') {
                    if (++endPos == textLength) {
                        return this.nextTokenSingleChar();
                    }
                    if (!isDigitPlusMinus(this.text.charAt(endPos))) {
                        return this.nextTokenSingleChar();
                    }
                }
                while (++endPos != textLength) {
                    final char c = this.text.charAt(endPos);
                    if (!isDigitPlusMinus(c) && !isAlphaUnder(c) && c != '.') {
                        break;
                    }
                }
            }
            else {
                if (startChar != '\"' && startChar != '\'') {
                    return this.nextTokenSingleChar();
                }
                while (++endPos != textLength) {
                    final char c = this.text.charAt(endPos);
                    if (c == startChar) {
                        ++endPos;
                        break;
                    }
                    if (c == '\n') {
                        break;
                    }
                    if (c != '\\') {
                        continue;
                    }
                    if (++endPos == textLength) {
                        break;
                    }
                    if (this.text.charAt(endPos) == '\n') {
                        break;
                    }
                }
            }
            this.pos = endPos;
            return this.text.subSequence(startPos, endPos).toString();
        }
        
        private static boolean isAlphaUnder(final char c) {
            return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_';
        }
        
        private static boolean isDigitPlusMinus(final char c) {
            return ('0' <= c && c <= '9') || c == '+' || c == '-';
        }
        
        private static boolean isWhitespace(final char c) {
            return c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t';
        }
        
        private String nextTokenSingleChar() {
            final char c = this.text.charAt(this.pos++);
            switch (c) {
                case ':': {
                    return ":";
                }
                case ',': {
                    return ",";
                }
                case '[': {
                    return "[";
                }
                case ']': {
                    return "]";
                }
                case '{': {
                    return "{";
                }
                case '}': {
                    return "}";
                }
                case '<': {
                    return "<";
                }
                case '>': {
                    return ">";
                }
                default: {
                    return String.valueOf(c);
                }
            }
        }
        
        private void skipWhitespace() {
            final int textLength = this.text.length();
            final int startPos = this.pos;
            int endPos = this.pos - 1;
            while (++endPos != textLength) {
                final char c = this.text.charAt(endPos);
                if (c == '#') {
                    while (++endPos != textLength && this.text.charAt(endPos) != '\n') {}
                    if (endPos == textLength) {
                        break;
                    }
                    continue;
                }
                else {
                    if (isWhitespace(c)) {
                        continue;
                    }
                    break;
                }
            }
            this.pos = endPos;
        }
        
        boolean tryConsume(final String token) {
            if (this.currentToken.equals(token)) {
                this.nextToken();
                return true;
            }
            return false;
        }
        
        void consume(final String token) throws ParseException {
            if (!this.tryConsume(token)) {
                throw this.parseException("Expected \"" + token + "\".");
            }
        }
        
        boolean lookingAtInteger() {
            return this.currentToken.length() != 0 && isDigitPlusMinus(this.currentToken.charAt(0));
        }
        
        boolean lookingAt(final String text) {
            return this.currentToken.equals(text);
        }
        
        String consumeIdentifier() throws ParseException {
            for (int i = 0; i < this.currentToken.length(); ++i) {
                final char c = this.currentToken.charAt(i);
                if (!isAlphaUnder(c) && ('0' > c || c > '9') && c != '.') {
                    throw this.parseException("Expected identifier. Found '" + this.currentToken + "'");
                }
            }
            final String result = this.currentToken;
            this.nextToken();
            return result;
        }
        
        boolean tryConsumeIdentifier() {
            try {
                this.consumeIdentifier();
                return true;
            }
            catch (final ParseException e) {
                return false;
            }
        }
        
        int consumeInt32() throws ParseException {
            try {
                final int result = TextFormat.parseInt32(this.currentToken);
                this.nextToken();
                return result;
            }
            catch (final NumberFormatException e) {
                throw this.integerParseException(e);
            }
        }
        
        int consumeUInt32() throws ParseException {
            try {
                final int result = TextFormat.parseUInt32(this.currentToken);
                this.nextToken();
                return result;
            }
            catch (final NumberFormatException e) {
                throw this.integerParseException(e);
            }
        }
        
        long consumeInt64() throws ParseException {
            try {
                final long result = TextFormat.parseInt64(this.currentToken);
                this.nextToken();
                return result;
            }
            catch (final NumberFormatException e) {
                throw this.integerParseException(e);
            }
        }
        
        boolean tryConsumeInt64() {
            try {
                this.consumeInt64();
                return true;
            }
            catch (final ParseException e) {
                return false;
            }
        }
        
        long consumeUInt64() throws ParseException {
            try {
                final long result = TextFormat.parseUInt64(this.currentToken);
                this.nextToken();
                return result;
            }
            catch (final NumberFormatException e) {
                throw this.integerParseException(e);
            }
        }
        
        public boolean tryConsumeUInt64() {
            try {
                this.consumeUInt64();
                return true;
            }
            catch (final ParseException e) {
                return false;
            }
        }
        
        public double consumeDouble() throws ParseException {
            final String lowerCase = this.currentToken.toLowerCase(Locale.ROOT);
            switch (lowerCase) {
                case "-inf":
                case "-infinity": {
                    this.nextToken();
                    return Double.NEGATIVE_INFINITY;
                }
                case "inf":
                case "infinity": {
                    this.nextToken();
                    return Double.POSITIVE_INFINITY;
                }
                case "nan": {
                    this.nextToken();
                    return Double.NaN;
                }
                default: {
                    try {
                        final double result = Double.parseDouble(this.currentToken);
                        this.nextToken();
                        return result;
                    }
                    catch (final NumberFormatException e) {
                        throw this.floatParseException(e);
                    }
                    break;
                }
            }
        }
        
        public boolean tryConsumeDouble() {
            try {
                this.consumeDouble();
                return true;
            }
            catch (final ParseException e) {
                return false;
            }
        }
        
        public float consumeFloat() throws ParseException {
            final String lowerCase = this.currentToken.toLowerCase(Locale.ROOT);
            switch (lowerCase) {
                case "-inf":
                case "-inff":
                case "-infinity":
                case "-infinityf": {
                    this.nextToken();
                    return Float.NEGATIVE_INFINITY;
                }
                case "inf":
                case "inff":
                case "infinity":
                case "infinityf": {
                    this.nextToken();
                    return Float.POSITIVE_INFINITY;
                }
                case "nan":
                case "nanf": {
                    this.nextToken();
                    return Float.NaN;
                }
                default: {
                    try {
                        final float result = Float.parseFloat(this.currentToken);
                        this.nextToken();
                        return result;
                    }
                    catch (final NumberFormatException e) {
                        throw this.floatParseException(e);
                    }
                    break;
                }
            }
        }
        
        public boolean tryConsumeFloat() {
            try {
                this.consumeFloat();
                return true;
            }
            catch (final ParseException e) {
                return false;
            }
        }
        
        public boolean consumeBoolean() throws ParseException {
            if (this.currentToken.equals("true") || this.currentToken.equals("True") || this.currentToken.equals("t") || this.currentToken.equals("1")) {
                this.nextToken();
                return true;
            }
            if (this.currentToken.equals("false") || this.currentToken.equals("False") || this.currentToken.equals("f") || this.currentToken.equals("0")) {
                this.nextToken();
                return false;
            }
            throw this.parseException("Expected \"true\" or \"false\". Found \"" + this.currentToken + "\".");
        }
        
        public String consumeString() throws ParseException {
            return this.consumeByteString().toStringUtf8();
        }
        
        @CanIgnoreReturnValue
        ByteString consumeByteString() throws ParseException {
            final List<ByteString> list = new ArrayList<ByteString>();
            this.consumeByteString(list);
            while (this.currentToken.startsWith("'") || this.currentToken.startsWith("\"")) {
                this.consumeByteString(list);
            }
            return ByteString.copyFrom(list);
        }
        
        boolean tryConsumeByteString() {
            try {
                this.consumeByteString();
                return true;
            }
            catch (final ParseException e) {
                return false;
            }
        }
        
        private void consumeByteString(final List<ByteString> list) throws ParseException {
            final char quote = (this.currentToken.length() > 0) ? this.currentToken.charAt(0) : '\0';
            if (quote != '\"' && quote != '\'') {
                throw this.parseException("Expected string.");
            }
            if (this.currentToken.length() < 2 || this.currentToken.charAt(this.currentToken.length() - 1) != quote) {
                throw this.parseException("String missing ending quote.");
            }
            try {
                final String escaped = this.currentToken.substring(1, this.currentToken.length() - 1);
                final ByteString result = TextFormat.unescapeBytes(escaped);
                this.nextToken();
                list.add(result);
            }
            catch (final InvalidEscapeSequenceException e) {
                throw this.parseException(e.getMessage());
            }
        }
        
        ParseException parseException(final String description) {
            return new ParseException(this.line + 1, this.column + 1, description);
        }
        
        ParseException parseExceptionPreviousToken(final String description) {
            return new ParseException(this.previousLine + 1, this.previousColumn + 1, description);
        }
        
        private ParseException integerParseException(final NumberFormatException e) {
            return this.parseException("Couldn't parse integer: " + e.getMessage());
        }
        
        private ParseException floatParseException(final NumberFormatException e) {
            return this.parseException("Couldn't parse number: " + e.getMessage());
        }
    }
    
    public static class ParseException extends IOException
    {
        private static final long serialVersionUID = 3196188060225107702L;
        private final int line;
        private final int column;
        
        public ParseException(final String message) {
            this(-1, -1, message);
        }
        
        public ParseException(final int line, final int column, final String message) {
            super(Integer.toString(line) + ":" + column + ": " + message);
            this.line = line;
            this.column = column;
        }
        
        public int getLine() {
            return this.line;
        }
        
        public int getColumn() {
            return this.column;
        }
    }
    
    @Deprecated
    public static class UnknownFieldParseException extends ParseException
    {
        private final String unknownField;
        
        public UnknownFieldParseException(final String message) {
            this(-1, -1, "", message);
        }
        
        public UnknownFieldParseException(final int line, final int column, final String unknownField, final String message) {
            super(line, column, message);
            this.unknownField = unknownField;
        }
        
        public String getUnknownField() {
            return this.unknownField;
        }
    }
    
    public static class Parser
    {
        private final TypeRegistry typeRegistry;
        private final boolean allowUnknownFields;
        private final boolean allowUnknownEnumValues;
        private final boolean allowUnknownExtensions;
        private final SingularOverwritePolicy singularOverwritePolicy;
        private TextFormatParseInfoTree.Builder parseInfoTreeBuilder;
        private final int recursionLimit;
        private static final int BUFFER_SIZE = 4096;
        
        private void detectSilentMarker(final Tokenizer tokenizer, final Descriptors.Descriptor immediateMessageType, final String fieldName) {
        }
        
        private Parser(final TypeRegistry typeRegistry, final boolean allowUnknownFields, final boolean allowUnknownEnumValues, final boolean allowUnknownExtensions, final SingularOverwritePolicy singularOverwritePolicy, final TextFormatParseInfoTree.Builder parseInfoTreeBuilder, final int recursionLimit) {
            this.typeRegistry = typeRegistry;
            this.allowUnknownFields = allowUnknownFields;
            this.allowUnknownEnumValues = allowUnknownEnumValues;
            this.allowUnknownExtensions = allowUnknownExtensions;
            this.singularOverwritePolicy = singularOverwritePolicy;
            this.parseInfoTreeBuilder = parseInfoTreeBuilder;
            this.recursionLimit = recursionLimit;
        }
        
        public static Builder newBuilder() {
            return new Builder();
        }
        
        public void merge(final Readable input, final Message.Builder builder) throws IOException {
            this.merge(input, ExtensionRegistry.getEmptyRegistry(), builder);
        }
        
        public void merge(final CharSequence input, final Message.Builder builder) throws ParseException {
            this.merge(input, ExtensionRegistry.getEmptyRegistry(), builder);
        }
        
        public void merge(final Readable input, final ExtensionRegistry extensionRegistry, final Message.Builder builder) throws IOException {
            this.merge(toStringBuilder(input), extensionRegistry, builder);
        }
        
        private static StringBuilder toStringBuilder(final Readable input) throws IOException {
            final StringBuilder text = new StringBuilder();
            final CharBuffer buffer = CharBuffer.allocate(4096);
            while (true) {
                final int n = input.read(buffer);
                if (n == -1) {
                    break;
                }
                Java8Compatibility.flip(buffer);
                text.append(buffer, 0, n);
            }
            return text;
        }
        
        private void checkUnknownFields(final List<UnknownField> unknownFields) throws ParseException {
            if (unknownFields.isEmpty()) {
                return;
            }
            final StringBuilder msg = new StringBuilder("Input contains unknown fields and/or extensions:");
            for (final UnknownField field : unknownFields) {
                msg.append('\n').append(field.message);
            }
            if (this.allowUnknownFields) {
                TextFormat.logger.warning(msg.toString());
                return;
            }
            int firstErrorIndex = 0;
            if (this.allowUnknownExtensions) {
                boolean allUnknownExtensions = true;
                for (final UnknownField field2 : unknownFields) {
                    if (field2.type == UnknownField.Type.FIELD) {
                        allUnknownExtensions = false;
                        break;
                    }
                    ++firstErrorIndex;
                }
                if (allUnknownExtensions) {
                    TextFormat.logger.warning(msg.toString());
                    return;
                }
            }
            final String[] lineColumn = unknownFields.get(firstErrorIndex).message.split(":");
            throw new ParseException(Integer.parseInt(lineColumn[0]), Integer.parseInt(lineColumn[1]), msg.toString());
        }
        
        public void merge(final CharSequence input, final ExtensionRegistry extensionRegistry, final Message.Builder builder) throws ParseException {
            final Tokenizer tokenizer = new Tokenizer(input);
            final MessageReflection.BuilderAdapter target = new MessageReflection.BuilderAdapter(builder);
            final List<UnknownField> unknownFields = new ArrayList<UnknownField>();
            while (!tokenizer.atEnd()) {
                this.mergeField(tokenizer, extensionRegistry, target, unknownFields, this.recursionLimit);
            }
            this.checkUnknownFields(unknownFields);
        }
        
        private void mergeField(final Tokenizer tokenizer, final ExtensionRegistry extensionRegistry, final MessageReflection.MergeTarget target, final List<UnknownField> unknownFields, final int recursionLimit) throws ParseException {
            this.mergeField(tokenizer, extensionRegistry, target, this.parseInfoTreeBuilder, unknownFields, recursionLimit);
        }
        
        private void mergeField(final Tokenizer tokenizer, final ExtensionRegistry extensionRegistry, final MessageReflection.MergeTarget target, final TextFormatParseInfoTree.Builder parseTreeBuilder, final List<UnknownField> unknownFields, final int recursionLimit) throws ParseException {
            Descriptors.FieldDescriptor field = null;
            final int startLine = tokenizer.getLine();
            final int startColumn = tokenizer.getColumn();
            final Descriptors.Descriptor type = target.getDescriptorForType();
            ExtensionRegistry.ExtensionInfo extension = null;
            if ("google.protobuf.Any".equals(type.getFullName()) && tokenizer.tryConsume("[")) {
                if (recursionLimit < 1) {
                    throw tokenizer.parseException("Message is nested too deep");
                }
                this.mergeAnyFieldValue(tokenizer, extensionRegistry, target, parseTreeBuilder, unknownFields, type, recursionLimit - 1);
            }
            else {
                String name;
                if (tokenizer.tryConsume("[")) {
                    final StringBuilder nameBuilder = new StringBuilder(tokenizer.consumeIdentifier());
                    while (tokenizer.tryConsume(".")) {
                        nameBuilder.append('.');
                        nameBuilder.append(tokenizer.consumeIdentifier());
                    }
                    name = nameBuilder.toString();
                    extension = target.findExtensionByName(extensionRegistry, name);
                    if (extension == null) {
                        final String message = tokenizer.getPreviousLine() + 1 + ":" + (tokenizer.getPreviousColumn() + 1) + ":\t" + type.getFullName() + ".[" + name + "]";
                        unknownFields.add(new UnknownField(message, UnknownField.Type.EXTENSION));
                    }
                    else {
                        if (extension.descriptor.getContainingType() != type) {
                            throw tokenizer.parseExceptionPreviousToken("Extension \"" + name + "\" does not extend message type \"" + type.getFullName() + "\".");
                        }
                        field = extension.descriptor;
                    }
                    tokenizer.consume("]");
                }
                else {
                    name = tokenizer.consumeIdentifier();
                    field = type.findFieldByName(name);
                    if (field == null) {
                        final String lowerName = name.toLowerCase(Locale.US);
                        field = type.findFieldByName(lowerName);
                        if (field != null && !field.isGroupLike()) {
                            field = null;
                        }
                        if (field != null && !field.getMessageType().getName().equals(name)) {
                            field = null;
                        }
                    }
                    if (field == null) {
                        final String message2 = tokenizer.getPreviousLine() + 1 + ":" + (tokenizer.getPreviousColumn() + 1) + ":\t" + type.getFullName() + "." + name;
                        unknownFields.add(new UnknownField(message2, UnknownField.Type.FIELD));
                    }
                }
                if (field == null) {
                    this.detectSilentMarker(tokenizer, type, name);
                    this.guessFieldTypeAndSkip(tokenizer, type, recursionLimit);
                    return;
                }
                if (field.getJavaType() == Descriptors.FieldDescriptor.JavaType.MESSAGE) {
                    this.detectSilentMarker(tokenizer, type, field.getFullName());
                    tokenizer.tryConsume(":");
                    if (parseTreeBuilder != null) {
                        final TextFormatParseInfoTree.Builder childParseTreeBuilder = parseTreeBuilder.getBuilderForSubMessageField(field);
                        this.consumeFieldValues(tokenizer, extensionRegistry, target, field, extension, childParseTreeBuilder, unknownFields, recursionLimit);
                    }
                    else {
                        this.consumeFieldValues(tokenizer, extensionRegistry, target, field, extension, parseTreeBuilder, unknownFields, recursionLimit);
                    }
                }
                else {
                    this.detectSilentMarker(tokenizer, type, field.getFullName());
                    tokenizer.consume(":");
                    this.consumeFieldValues(tokenizer, extensionRegistry, target, field, extension, parseTreeBuilder, unknownFields, recursionLimit);
                }
                if (parseTreeBuilder != null) {
                    parseTreeBuilder.setLocation(field, TextFormatParseLocation.create(startLine, startColumn));
                }
                if (!tokenizer.tryConsume(";")) {
                    tokenizer.tryConsume(",");
                }
            }
        }
        
        private String consumeFullTypeName(final Tokenizer tokenizer) throws ParseException {
            if (!tokenizer.tryConsume("[")) {
                return tokenizer.consumeIdentifier();
            }
            String name = tokenizer.consumeIdentifier();
            while (tokenizer.tryConsume(".")) {
                name = name + "." + tokenizer.consumeIdentifier();
            }
            if (tokenizer.tryConsume("/")) {
                name = name + "/" + tokenizer.consumeIdentifier();
                while (tokenizer.tryConsume(".")) {
                    name = name + "." + tokenizer.consumeIdentifier();
                }
            }
            tokenizer.consume("]");
            return name;
        }
        
        private void consumeFieldValues(final Tokenizer tokenizer, final ExtensionRegistry extensionRegistry, final MessageReflection.MergeTarget target, final Descriptors.FieldDescriptor field, final ExtensionRegistry.ExtensionInfo extension, final TextFormatParseInfoTree.Builder parseTreeBuilder, final List<UnknownField> unknownFields, final int recursionLimit) throws ParseException {
            if (field.isRepeated() && tokenizer.tryConsume("[")) {
                if (!tokenizer.tryConsume("]")) {
                    while (true) {
                        this.consumeFieldValue(tokenizer, extensionRegistry, target, field, extension, parseTreeBuilder, unknownFields, recursionLimit);
                        if (tokenizer.tryConsume("]")) {
                            break;
                        }
                        tokenizer.consume(",");
                    }
                }
            }
            else {
                this.consumeFieldValue(tokenizer, extensionRegistry, target, field, extension, parseTreeBuilder, unknownFields, recursionLimit);
            }
        }
        
        private void consumeFieldValue(final Tokenizer tokenizer, final ExtensionRegistry extensionRegistry, final MessageReflection.MergeTarget target, final Descriptors.FieldDescriptor field, final ExtensionRegistry.ExtensionInfo extension, final TextFormatParseInfoTree.Builder parseTreeBuilder, final List<UnknownField> unknownFields, final int recursionLimit) throws ParseException {
            if (this.singularOverwritePolicy == SingularOverwritePolicy.FORBID_SINGULAR_OVERWRITES && !field.isRepeated()) {
                if (target.hasField(field)) {
                    throw tokenizer.parseExceptionPreviousToken("Non-repeated field \"" + field.getFullName() + "\" cannot be overwritten.");
                }
                if (field.getContainingOneof() != null && target.hasOneof(field.getContainingOneof())) {
                    final Descriptors.OneofDescriptor oneof = field.getContainingOneof();
                    throw tokenizer.parseExceptionPreviousToken("Field \"" + field.getFullName() + "\" is specified along with field \"" + target.getOneofFieldDescriptor(oneof).getFullName() + "\", another member of oneof \"" + oneof.getName() + "\".");
                }
            }
            Object value = null;
            if (field.getJavaType() == Descriptors.FieldDescriptor.JavaType.MESSAGE) {
                if (recursionLimit < 1) {
                    throw tokenizer.parseException("Message is nested too deep");
                }
                String endToken;
                if (tokenizer.tryConsume("<")) {
                    endToken = ">";
                }
                else {
                    tokenizer.consume("{");
                    endToken = "}";
                }
                final Message defaultInstance = (extension == null) ? null : extension.defaultInstance;
                final MessageReflection.MergeTarget subField = target.newMergeTargetForField(field, defaultInstance);
                while (!tokenizer.tryConsume(endToken)) {
                    if (tokenizer.atEnd()) {
                        throw tokenizer.parseException("Expected \"" + endToken + "\".");
                    }
                    this.mergeField(tokenizer, extensionRegistry, subField, parseTreeBuilder, unknownFields, recursionLimit - 1);
                }
                value = subField.finish();
            }
            else {
                switch (field.getType()) {
                    case INT32:
                    case SINT32:
                    case SFIXED32: {
                        value = tokenizer.consumeInt32();
                        break;
                    }
                    case INT64:
                    case SINT64:
                    case SFIXED64: {
                        value = tokenizer.consumeInt64();
                        break;
                    }
                    case UINT32:
                    case FIXED32: {
                        value = tokenizer.consumeUInt32();
                        break;
                    }
                    case UINT64:
                    case FIXED64: {
                        value = tokenizer.consumeUInt64();
                        break;
                    }
                    case FLOAT: {
                        value = tokenizer.consumeFloat();
                        break;
                    }
                    case DOUBLE: {
                        value = tokenizer.consumeDouble();
                        break;
                    }
                    case BOOL: {
                        value = tokenizer.consumeBoolean();
                        break;
                    }
                    case STRING: {
                        value = tokenizer.consumeString();
                        break;
                    }
                    case BYTES: {
                        value = tokenizer.consumeByteString();
                        break;
                    }
                    case ENUM: {
                        final Descriptors.EnumDescriptor enumType = field.getEnumType();
                        if (tokenizer.lookingAtInteger()) {
                            final int number = tokenizer.consumeInt32();
                            value = (enumType.isClosed() ? enumType.findValueByNumber(number) : enumType.findValueByNumberCreatingIfUnknown(number));
                            if (value != null) {
                                break;
                            }
                            final String unknownValueMsg = "Enum type \"" + enumType.getFullName() + "\" has no value with number " + number + '.';
                            if (this.allowUnknownEnumValues) {
                                TextFormat.logger.warning(unknownValueMsg);
                                return;
                            }
                            throw tokenizer.parseExceptionPreviousToken("Enum type \"" + enumType.getFullName() + "\" has no value with number " + number + '.');
                        }
                        else {
                            final String id = tokenizer.consumeIdentifier();
                            value = enumType.findValueByName(id);
                            if (value != null) {
                                break;
                            }
                            final String unknownValueMsg = "Enum type \"" + enumType.getFullName() + "\" has no value named \"" + id + "\".";
                            if (this.allowUnknownEnumValues) {
                                TextFormat.logger.warning(unknownValueMsg);
                                return;
                            }
                            throw tokenizer.parseExceptionPreviousToken(unknownValueMsg);
                        }
                        break;
                    }
                    case MESSAGE:
                    case GROUP: {
                        throw new RuntimeException("Can't get here.");
                    }
                }
            }
            if (field.isRepeated()) {
                target.addRepeatedField(field, value);
            }
            else {
                target.setField(field, value);
            }
        }
        
        private void mergeAnyFieldValue(final Tokenizer tokenizer, final ExtensionRegistry extensionRegistry, final MessageReflection.MergeTarget target, final TextFormatParseInfoTree.Builder parseTreeBuilder, final List<UnknownField> unknownFields, final Descriptors.Descriptor anyDescriptor, final int recursionLimit) throws ParseException {
            final StringBuilder typeUrlBuilder = new StringBuilder();
            while (true) {
                typeUrlBuilder.append(tokenizer.consumeIdentifier());
                if (tokenizer.tryConsume("]")) {
                    this.detectSilentMarker(tokenizer, anyDescriptor, typeUrlBuilder.toString());
                    tokenizer.tryConsume(":");
                    String anyEndToken;
                    if (tokenizer.tryConsume("<")) {
                        anyEndToken = ">";
                    }
                    else {
                        tokenizer.consume("{");
                        anyEndToken = "}";
                    }
                    final String typeUrl = typeUrlBuilder.toString();
                    Descriptors.Descriptor contentType = null;
                    try {
                        contentType = this.typeRegistry.getDescriptorForTypeUrl(typeUrl);
                    }
                    catch (final InvalidProtocolBufferException e) {
                        throw tokenizer.parseException("Invalid valid type URL. Found: " + typeUrl);
                    }
                    if (contentType == null) {
                        throw tokenizer.parseException("Unable to parse Any of type: " + typeUrl + ". Please make sure that the TypeRegistry contains the descriptors for the given types.");
                    }
                    final Message.Builder contentBuilder = DynamicMessage.getDefaultInstance(contentType).newBuilderForType();
                    final MessageReflection.BuilderAdapter contentTarget = new MessageReflection.BuilderAdapter(contentBuilder);
                    while (!tokenizer.tryConsume(anyEndToken)) {
                        this.mergeField(tokenizer, extensionRegistry, contentTarget, parseTreeBuilder, unknownFields, recursionLimit);
                    }
                    target.setField(anyDescriptor.findFieldByName("type_url"), typeUrlBuilder.toString());
                    target.setField(anyDescriptor.findFieldByName("value"), contentBuilder.build().toByteString());
                }
                else if (tokenizer.tryConsume("/")) {
                    typeUrlBuilder.append("/");
                }
                else {
                    if (!tokenizer.tryConsume(".")) {
                        throw tokenizer.parseExceptionPreviousToken("Expected a valid type URL.");
                    }
                    typeUrlBuilder.append(".");
                }
            }
        }
        
        private void skipField(final Tokenizer tokenizer, final Descriptors.Descriptor type, final int recursionLimit) throws ParseException {
            final String name = this.consumeFullTypeName(tokenizer);
            this.detectSilentMarker(tokenizer, type, name);
            this.guessFieldTypeAndSkip(tokenizer, type, recursionLimit);
            if (!tokenizer.tryConsume(";")) {
                tokenizer.tryConsume(",");
            }
        }
        
        private void skipFieldMessage(final Tokenizer tokenizer, final Descriptors.Descriptor type, final int recursionLimit) throws ParseException {
            String delimiter;
            if (tokenizer.tryConsume("<")) {
                delimiter = ">";
            }
            else {
                tokenizer.consume("{");
                delimiter = "}";
            }
            while (!tokenizer.lookingAt(">") && !tokenizer.lookingAt("}")) {
                this.skipField(tokenizer, type, recursionLimit);
            }
            tokenizer.consume(delimiter);
        }
        
        private void skipFieldValue(final Tokenizer tokenizer) throws ParseException {
            if (!tokenizer.tryConsumeByteString() && !tokenizer.tryConsumeIdentifier() && !tokenizer.tryConsumeInt64() && !tokenizer.tryConsumeUInt64() && !tokenizer.tryConsumeDouble() && !tokenizer.tryConsumeFloat()) {
                throw tokenizer.parseException("Invalid field value: " + tokenizer.currentToken);
            }
        }
        
        private void guessFieldTypeAndSkip(final Tokenizer tokenizer, final Descriptors.Descriptor type, final int recursionLimit) throws ParseException {
            final boolean semicolonConsumed = tokenizer.tryConsume(":");
            if (tokenizer.lookingAt("[")) {
                this.skipFieldShortFormedRepeated(tokenizer, semicolonConsumed, type, recursionLimit);
            }
            else if (semicolonConsumed && !tokenizer.lookingAt("{") && !tokenizer.lookingAt("<")) {
                this.skipFieldValue(tokenizer);
            }
            else {
                if (recursionLimit < 1) {
                    throw tokenizer.parseException("Message is nested too deep");
                }
                this.skipFieldMessage(tokenizer, type, recursionLimit - 1);
            }
        }
        
        private void skipFieldShortFormedRepeated(final Tokenizer tokenizer, final boolean scalarAllowed, final Descriptors.Descriptor type, final int recursionLimit) throws ParseException {
            if (!tokenizer.tryConsume("[") || tokenizer.tryConsume("]")) {
                return;
            }
            while (true) {
                if (tokenizer.lookingAt("{") || tokenizer.lookingAt("<")) {
                    if (recursionLimit < 1) {
                        throw tokenizer.parseException("Message is nested too deep");
                    }
                    this.skipFieldMessage(tokenizer, type, recursionLimit - 1);
                }
                else {
                    if (!scalarAllowed) {
                        throw tokenizer.parseException("Invalid repeated scalar field: missing \":\" before \"[\".");
                    }
                    this.skipFieldValue(tokenizer);
                }
                if (tokenizer.tryConsume("]")) {
                    return;
                }
                tokenizer.consume(",");
            }
        }
        
        public enum SingularOverwritePolicy
        {
            ALLOW_SINGULAR_OVERWRITES, 
            FORBID_SINGULAR_OVERWRITES;
        }
        
        public static class Builder
        {
            private boolean allowUnknownFields;
            private boolean allowUnknownEnumValues;
            private boolean allowUnknownExtensions;
            private SingularOverwritePolicy singularOverwritePolicy;
            private TextFormatParseInfoTree.Builder parseInfoTreeBuilder;
            private TypeRegistry typeRegistry;
            private int recursionLimit;
            
            public Builder() {
                this.allowUnknownFields = false;
                this.allowUnknownEnumValues = false;
                this.allowUnknownExtensions = false;
                this.singularOverwritePolicy = SingularOverwritePolicy.ALLOW_SINGULAR_OVERWRITES;
                this.parseInfoTreeBuilder = null;
                this.typeRegistry = TypeRegistry.getEmptyTypeRegistry();
                this.recursionLimit = 100;
            }
            
            public Builder setTypeRegistry(final TypeRegistry typeRegistry) {
                this.typeRegistry = typeRegistry;
                return this;
            }
            
            public Builder setAllowUnknownFields(final boolean allowUnknownFields) {
                this.allowUnknownFields = allowUnknownFields;
                return this;
            }
            
            public Builder setAllowUnknownExtensions(final boolean allowUnknownExtensions) {
                this.allowUnknownExtensions = allowUnknownExtensions;
                return this;
            }
            
            public Builder setSingularOverwritePolicy(final SingularOverwritePolicy p) {
                this.singularOverwritePolicy = p;
                return this;
            }
            
            public Builder setParseInfoTreeBuilder(final TextFormatParseInfoTree.Builder parseInfoTreeBuilder) {
                this.parseInfoTreeBuilder = parseInfoTreeBuilder;
                return this;
            }
            
            public Builder setRecursionLimit(final int recursionLimit) {
                this.recursionLimit = recursionLimit;
                return this;
            }
            
            public Parser build() {
                return new Parser(this.typeRegistry, this.allowUnknownFields, this.allowUnknownEnumValues, this.allowUnknownExtensions, this.singularOverwritePolicy, this.parseInfoTreeBuilder, this.recursionLimit);
            }
        }
        
        static final class UnknownField
        {
            final String message;
            final Type type;
            
            UnknownField(final String message, final Type type) {
                this.message = message;
                this.type = type;
            }
            
            enum Type
            {
                FIELD, 
                EXTENSION;
            }
        }
    }
    
    public static class InvalidEscapeSequenceException extends IOException
    {
        private static final long serialVersionUID = -8164033650142593304L;
        
        InvalidEscapeSequenceException(final String description) {
            super(description);
        }
    }
}
