// Protocol Buffers - Google's data interchange format
// Copyright 2013 Google Inc.  All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package com.google.protobuf.nano;

import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

/**
 * Represents an extension.
 *
 * @author bduff@google.com (Brian Duff)
 * @author maxtroy@google.com (Max Cai)
 * @param <M> the type of the extendable message this extension is for.
 * @param <T> the Java type of the extension; see {@link #clazz}.
 */
public class Extension<M extends ExtendableMessageNano<M>, T> {

    /*
     * Because we typically only define message-typed extensions, the Extension class hierarchy is
     * designed as follows, to allow a big amount of code in this file to be removed by ProGuard:
     *
     *            Extension          // ready to use for message/group typed extensions
     *                Δ
     *                |
     *       PrimitiveExtension      // for primitive/enum typed extensions
     */

    public static final int TYPE_DOUBLE   = InternalNano.TYPE_DOUBLE;
    public static final int TYPE_FLOAT    = InternalNano.TYPE_FLOAT;
    public static final int TYPE_INT64    = InternalNano.TYPE_INT64;
    public static final int TYPE_UINT64   = InternalNano.TYPE_UINT64;
    public static final int TYPE_INT32    = InternalNano.TYPE_INT32;
    public static final int TYPE_FIXED64  = InternalNano.TYPE_FIXED64;
    public static final int TYPE_FIXED32  = InternalNano.TYPE_FIXED32;
    public static final int TYPE_BOOL     = InternalNano.TYPE_BOOL;
    public static final int TYPE_STRING   = InternalNano.TYPE_STRING;
    public static final int TYPE_GROUP    = InternalNano.TYPE_GROUP;
    public static final int TYPE_MESSAGE  = InternalNano.TYPE_MESSAGE;
    public static final int TYPE_BYTES    = InternalNano.TYPE_BYTES;
    public static final int TYPE_UINT32   = InternalNano.TYPE_UINT32;
    public static final int TYPE_ENUM     = InternalNano.TYPE_ENUM;
    public static final int TYPE_SFIXED32 = InternalNano.TYPE_SFIXED32;
    public static final int TYPE_SFIXED64 = InternalNano.TYPE_SFIXED64;
    public static final int TYPE_SINT32   = InternalNano.TYPE_SINT32;
    public static final int TYPE_SINT64   = InternalNano.TYPE_SINT64;

    /**
     * Creates an {@code Extension} of the given message type and tag number.
     * Should be used by the generated code only.
     *
     * @param type {@link #TYPE_MESSAGE} or {@link #TYPE_GROUP}
     */
    public static <M extends ExtendableMessageNano<M>, T extends MessageNano>
            Extension<M, T> createMessageTyped(int type, Class<T> clazz, int tag) {
        return new Extension<M, T>(type, clazz, tag, false);
    }

    /**
     * Creates a repeated {@code Extension} of the given message type and tag number.
     * Should be used by the generated code only.
     *
     * @param type {@link #TYPE_MESSAGE} or {@link #TYPE_GROUP}
     */
    public static <M extends ExtendableMessageNano<M>, T extends MessageNano>
            Extension<M, T[]> createRepeatedMessageTyped(int type, Class<T[]> clazz, int tag) {
        return new Extension<M, T[]>(type, clazz, tag, true);
    }

    /**
     * Creates an {@code Extension} of the given primitive type and tag number.
     * Should be used by the generated code only.
     *
     * @param type one of {@code TYPE_*}, except {@link #TYPE_MESSAGE} and {@link #TYPE_GROUP}
     * @param clazz the boxed Java type of this extension
     */
    public static <M extends ExtendableMessageNano<M>, T>
            Extension<M, T> createPrimitiveTyped(int type, Class<T> clazz, int tag) {
        return new PrimitiveExtension<M, T>(type, clazz, tag, false, 0, 0);
    }

    /**
     * Creates a repeated {@code Extension} of the given primitive type and tag number.
     * Should be used by the generated code only.
     *
     * @param type one of {@code TYPE_*}, except {@link #TYPE_MESSAGE} and {@link #TYPE_GROUP}
     * @param clazz the Java array type of this extension, with an unboxed component type
     */
    public static <M extends ExtendableMessageNano<M>, T>
            Extension<M, T> createRepeatedPrimitiveTyped(
                    int type, Class<T> clazz, int tag, int nonPackedTag, int packedTag) {
        return new PrimitiveExtension<M, T>(type, clazz, tag, true, nonPackedTag, packedTag);
    }

    /**
     * Protocol Buffer type of this extension; one of the {@code TYPE_} constants.
     */
    protected final int type;

    /**
     * Java type of this extension. For a singular extension, this is the boxed Java type for the
     * Protocol Buffer {@link #type}; for a repeated extension, this is an array type whose
     * component type is the unboxed Java type for {@link #type}. For example, for a singular
     * {@code int32}/{@link #TYPE_INT32} extension, this equals {@code Integer.class}; for a
     * repeated {@code int32} extension, this equals {@code int[].class}.
     */
    protected final Class<T> clazz;

    /**
     * Tag number of this extension.
     */
    public final int tag;

    /**
     * Whether this extension is repeated.
     */
    protected final boolean repeated;

    private Extension(int type, Class<T> clazz, int tag, boolean repeated) {
        this.type = type;
        this.clazz = clazz;
        this.tag = tag;
        this.repeated = repeated;
    }

    /**
     * Returns the value of this extension stored in the given list of unknown fields, or
     * {@code null} if no unknown fields matches this extension.
     *
     * @param unknownFields a list of {@link UnknownFieldData}. All of the elements must have a tag
     *                      that matches this Extension's tag.
     *
     */
    final T getValueFrom(List<UnknownFieldData> unknownFields) {
        if (unknownFields == null) {
            return null;
        }
        return repeated ? getRepeatedValueFrom(unknownFields) : getSingularValueFrom(unknownFields);
    }

    private T getRepeatedValueFrom(List<UnknownFieldData> unknownFields) {
        // For repeated extensions, read all matching unknown fields in their original order.
        List<Object> resultList = new ArrayList<Object>();
        for (int i = 0; i < unknownFields.size(); i++) {
            UnknownFieldData data = unknownFields.get(i);
            if (data.bytes.length != 0) {
                readDataInto(data, resultList);
            }
        }

        int resultSize = resultList.size();
        if (resultSize == 0) {
            return null;
        } else {
            T result = clazz.cast(Array.newInstance(clazz.getComponentType(), resultSize));
            for (int i = 0; i < resultSize; i++) {
                Array.set(result, i, resultList.get(i));
            }
            return result;
        }
    }

    private T getSingularValueFrom(List<UnknownFieldData> unknownFields) {
        // For singular extensions, get the last piece of data stored under this extension.
        if (unknownFields.isEmpty()) {
            return null;
        }
        UnknownFieldData lastData = unknownFields.get(unknownFields.size() - 1);
        return clazz.cast(readData(CodedInputByteBufferNano.newInstance(lastData.bytes)));
    }

    protected Object readData(CodedInputByteBufferNano input) {
        // This implementation is for message/group extensions.
        Class<?> messageType = repeated ? clazz.getComponentType() : clazz;
        try {
            switch (type) {
                case TYPE_GROUP:
                    MessageNano group = (MessageNano) messageType.newInstance();
                    input.readGroup(group, WireFormatNano.getTagFieldNumber(tag));
                    return group;
                case TYPE_MESSAGE:
                    MessageNano message = (MessageNano) messageType.newInstance();
                    input.readMessage(message);
                    return message;
                default:
                    throw new IllegalArgumentException("Unknown type " + type);
            }
        } catch (InstantiationException e) {
            throw new IllegalArgumentException(
                    "Error creating instance of class " + messageType, e);
        } catch (IllegalAccessException e) {
            throw new IllegalArgumentException(
                    "Error creating instance of class " + messageType, e);
        } catch (IOException e) {
            throw new IllegalArgumentException("Error reading extension field", e);
        }
    }

    protected void readDataInto(UnknownFieldData data, List<Object> resultList) {
        // This implementation is for message/group extensions.
        resultList.add(readData(CodedInputByteBufferNano.newInstance(data.bytes)));
    }

    void writeTo(Object value, CodedOutputByteBufferNano output) throws IOException {
        if (repeated) {
            writeRepeatedData(value, output);
        } else {
            writeSingularData(value, output);
        }
    }

    protected void writeSingularData(Object value, CodedOutputByteBufferNano out) {
        // This implementation is for message/group extensions.
        try {
            out.writeRawVarint32(tag);
            switch (type) {
                case TYPE_GROUP:
                    MessageNano groupValue = (MessageNano) value;
                    int fieldNumber = WireFormatNano.getTagFieldNumber(tag);
                    out.writeGroupNoTag(groupValue);
                    // The endgroup tag must be included in the data payload.
                    out.writeTag(fieldNumber, WireFormatNano.WIRETYPE_END_GROUP);
                    break;
                case TYPE_MESSAGE:
                    MessageNano messageValue = (MessageNano) value;
                    out.writeMessageNoTag(messageValue);
                    break;
                default:
                    throw new IllegalArgumentException("Unknown type " + type);
            }
        } catch (IOException e) {
            // Should not happen
            throw new IllegalStateException(e);
        }
    }

    protected void writeRepeatedData(Object array, CodedOutputByteBufferNano output) {
        // This implementation is for non-packed extensions.
        int arrayLength = Array.getLength(array);
        for (int i = 0; i < arrayLength; i++) {
            Object element = Array.get(array, i);
            if (element != null) {
                writeSingularData(element, output);
            }
        }
    }

    int computeSerializedSize(Object value) {
        if (repeated) {
            return computeRepeatedSerializedSize(value);
        } else {
            return computeSingularSerializedSize(value);
        }
    }

    protected int computeRepeatedSerializedSize(Object array) {
        // This implementation is for non-packed extensions.
        int size = 0;
        int arrayLength = Array.getLength(array);
        for (int i = 0; i < arrayLength; i++) {
            Object element = Array.get(array, i);
            if (element != null) {
                size += computeSingularSerializedSize(Array.get(array, i));
            }
        }
        return size;
    }

    protected int computeSingularSerializedSize(Object value) {
        // This implementation is for message/group extensions.
        int fieldNumber = WireFormatNano.getTagFieldNumber(tag);
        switch (type) {
            case TYPE_GROUP:
                MessageNano groupValue = (MessageNano) value;
                return CodedOutputByteBufferNano.computeGroupSize(fieldNumber, groupValue);
            case TYPE_MESSAGE:
                MessageNano messageValue = (MessageNano) value;
                return CodedOutputByteBufferNano.computeMessageSize(fieldNumber, messageValue);
            default:
                throw new IllegalArgumentException("Unknown type " + type);
        }
    }

    /**
     * Represents an extension of a primitive (including enum) type. If there is no primitive
     * extensions, this subclass will be removable by ProGuard.
     */
    private static class PrimitiveExtension<M extends ExtendableMessageNano<M>, T>
            extends Extension<M, T> {

        /**
         * Tag of a piece of non-packed data from the wire compatible with this extension.
         */
        private final int nonPackedTag;

        /**
         * Tag of a piece of packed data from the wire compatible with this extension.
         * 0 if the type of this extension is not packable.
         */
        private final int packedTag;

        public PrimitiveExtension(int type, Class<T> clazz, int tag, boolean repeated,
                int nonPackedTag, int packedTag) {
            super(type, clazz, tag, repeated);
            this.nonPackedTag = nonPackedTag;
            this.packedTag = packedTag;
        }

        @Override
        protected Object readData(CodedInputByteBufferNano input) {
            try {
              return input.readPrimitiveField(type);
            } catch (IOException e) {
                throw new IllegalArgumentException("Error reading extension field", e);
            }
        }

        @Override
        protected void readDataInto(UnknownFieldData data, List<Object> resultList) {
            // This implementation is for primitive typed extensions,
            // which can read both packed and non-packed data.
            if (data.tag == nonPackedTag) {
                resultList.add(readData(CodedInputByteBufferNano.newInstance(data.bytes)));
            } else {
                CodedInputByteBufferNano buffer =
                        CodedInputByteBufferNano.newInstance(data.bytes);
                try {
                    buffer.pushLimit(buffer.readRawVarint32()); // length limit
                } catch (IOException e) {
                    throw new IllegalArgumentException("Error reading extension field", e);
                }
                while (!buffer.isAtEnd()) {
                    resultList.add(readData(buffer));
                }
            }
        }

        @Override
        protected final void writeSingularData(Object value, CodedOutputByteBufferNano output) {
            try {
                output.writeRawVarint32(tag);
                switch (type) {
                    case TYPE_DOUBLE:
                        Double doubleValue = (Double) value;
                        output.writeDoubleNoTag(doubleValue);
                        break;
                    case TYPE_FLOAT:
                        Float floatValue = (Float) value;
                        output.writeFloatNoTag(floatValue);
                        break;
                    case TYPE_INT64:
                        Long int64Value = (Long) value;
                        output.writeInt64NoTag(int64Value);
                        break;
                    case TYPE_UINT64:
                        Long uint64Value = (Long) value;
                        output.writeUInt64NoTag(uint64Value);
                        break;
                    case TYPE_INT32:
                        Integer int32Value = (Integer) value;
                        output.writeInt32NoTag(int32Value);
                        break;
                    case TYPE_FIXED64:
                        Long fixed64Value = (Long) value;
                        output.writeFixed64NoTag(fixed64Value);
                        break;
                    case TYPE_FIXED32:
                        Integer fixed32Value = (Integer) value;
                        output.writeFixed32NoTag(fixed32Value);
                        break;
                    case TYPE_BOOL:
                        Boolean boolValue = (Boolean) value;
                        output.writeBoolNoTag(boolValue);
                        break;
                    case TYPE_STRING:
                        String stringValue = (String) value;
                        output.writeStringNoTag(stringValue);
                        break;
                    case TYPE_BYTES:
                        byte[] bytesValue = (byte[]) value;
                        output.writeBytesNoTag(bytesValue);
                        break;
                    case TYPE_UINT32:
                        Integer uint32Value = (Integer) value;
                        output.writeUInt32NoTag(uint32Value);
                        break;
                    case TYPE_ENUM:
                        Integer enumValue = (Integer) value;
                        output.writeEnumNoTag(enumValue);
                        break;
                    case TYPE_SFIXED32:
                        Integer sfixed32Value = (Integer) value;
                        output.writeSFixed32NoTag(sfixed32Value);
                        break;
                    case TYPE_SFIXED64:
                        Long sfixed64Value = (Long) value;
                        output.writeSFixed64NoTag(sfixed64Value);
                        break;
                    case TYPE_SINT32:
                        Integer sint32Value = (Integer) value;
                        output.writeSInt32NoTag(sint32Value);
                        break;
                    case TYPE_SINT64:
                        Long sint64Value = (Long) value;
                        output.writeSInt64NoTag(sint64Value);
                        break;
                    default:
                        throw new IllegalArgumentException("Unknown type " + type);
                }
            } catch (IOException e) {
                // Should not happen
                throw new IllegalStateException(e);
            }
        }

        @Override
        protected void writeRepeatedData(Object array, CodedOutputByteBufferNano output) {
            if (tag == nonPackedTag) {
                // Use base implementation for non-packed data
                super.writeRepeatedData(array, output);
            } else if (tag == packedTag) {
                // Packed. Note that the array element type is guaranteed to be primitive, so there
                // won't be any null elements, so no null check in this block.
                int arrayLength = Array.getLength(array);
                int dataSize = computePackedDataSize(array);

                try {
                    output.writeRawVarint32(tag);
                    output.writeRawVarint32(dataSize);
                    switch (type) {
                        case TYPE_BOOL:
                            for (int i = 0; i < arrayLength; i++) {
                                output.writeBoolNoTag(Array.getBoolean(array, i));
                            }
                            break;
                        case TYPE_FIXED32:
                            for (int i = 0; i < arrayLength; i++) {
                                output.writeFixed32NoTag(Array.getInt(array, i));
                            }
                            break;
                        case TYPE_SFIXED32:
                            for (int i = 0; i < arrayLength; i++) {
                                output.writeSFixed32NoTag(Array.getInt(array, i));
                            }
                            break;
                        case TYPE_FLOAT:
                            for (int i = 0; i < arrayLength; i++) {
                                output.writeFloatNoTag(Array.getFloat(array, i));
                            }
                            break;
                        case TYPE_FIXED64:
                            for (int i = 0; i < arrayLength; i++) {
                                output.writeFixed64NoTag(Array.getLong(array, i));
                            }
                            break;
                        case TYPE_SFIXED64:
                            for (int i = 0; i < arrayLength; i++) {
                                output.writeSFixed64NoTag(Array.getLong(array, i));
                            }
                            break;
                        case TYPE_DOUBLE:
                            for (int i = 0; i < arrayLength; i++) {
                                output.writeDoubleNoTag(Array.getDouble(array, i));
                            }
                            break;
                        case TYPE_INT32:
                            for (int i = 0; i < arrayLength; i++) {
                                output.writeInt32NoTag(Array.getInt(array, i));
                            }
                            break;
                        case TYPE_SINT32:
                            for (int i = 0; i < arrayLength; i++) {
                                output.writeSInt32NoTag(Array.getInt(array, i));
                            }
                            break;
                        case TYPE_UINT32:
                            for (int i = 0; i < arrayLength; i++) {
                                output.writeUInt32NoTag(Array.getInt(array, i));
                            }
                            break;
                        case TYPE_INT64:
                            for (int i = 0; i < arrayLength; i++) {
                                output.writeInt64NoTag(Array.getLong(array, i));
                            }
                            break;
                        case TYPE_SINT64:
                            for (int i = 0; i < arrayLength; i++) {
                                output.writeSInt64NoTag(Array.getLong(array, i));
                            }
                            break;
                        case TYPE_UINT64:
                            for (int i = 0; i < arrayLength; i++) {
                                output.writeUInt64NoTag(Array.getLong(array, i));
                            }
                            break;
                        case TYPE_ENUM:
                            for (int i = 0; i < arrayLength; i++) {
                                output.writeEnumNoTag(Array.getInt(array, i));
                            }
                            break;
                        default:
                            throw new IllegalArgumentException("Unpackable type " + type);
                    }
                } catch (IOException e) {
                    // Should not happen.
                    throw new IllegalStateException(e);
                }
            } else {
                throw new IllegalArgumentException("Unexpected repeated extension tag " + tag
                        + ", unequal to both non-packed variant " + nonPackedTag
                        + " and packed variant " + packedTag);
            }
        }

        private int computePackedDataSize(Object array) {
            int dataSize = 0;
            int arrayLength = Array.getLength(array);
            switch (type) {
                case TYPE_BOOL:
                    // Bools are stored as int32 but just as 0 or 1, so 1 byte each.
                    dataSize = arrayLength;
                    break;
                case TYPE_FIXED32:
                case TYPE_SFIXED32:
                case TYPE_FLOAT:
                    dataSize = arrayLength * CodedOutputByteBufferNano.LITTLE_ENDIAN_32_SIZE;
                    break;
                case TYPE_FIXED64:
                case TYPE_SFIXED64:
                case TYPE_DOUBLE:
                    dataSize = arrayLength * CodedOutputByteBufferNano.LITTLE_ENDIAN_64_SIZE;
                    break;
                case TYPE_INT32:
                    for (int i = 0; i < arrayLength; i++) {
                        dataSize += CodedOutputByteBufferNano.computeInt32SizeNoTag(
                                Array.getInt(array, i));
                    }
                    break;
                case TYPE_SINT32:
                    for (int i = 0; i < arrayLength; i++) {
                        dataSize += CodedOutputByteBufferNano.computeSInt32SizeNoTag(
                                Array.getInt(array, i));
                    }
                    break;
                case TYPE_UINT32:
                    for (int i = 0; i < arrayLength; i++) {
                        dataSize += CodedOutputByteBufferNano.computeUInt32SizeNoTag(
                                Array.getInt(array, i));
                    }
                    break;
                case TYPE_INT64:
                    for (int i = 0; i < arrayLength; i++) {
                        dataSize += CodedOutputByteBufferNano.computeInt64SizeNoTag(
                                Array.getLong(array, i));
                    }
                    break;
                case TYPE_SINT64:
                    for (int i = 0; i < arrayLength; i++) {
                        dataSize += CodedOutputByteBufferNano.computeSInt64SizeNoTag(
                                Array.getLong(array, i));
                    }
                    break;
                case TYPE_UINT64:
                    for (int i = 0; i < arrayLength; i++) {
                        dataSize += CodedOutputByteBufferNano.computeUInt64SizeNoTag(
                                Array.getLong(array, i));
                    }
                    break;
                case TYPE_ENUM:
                    for (int i = 0; i < arrayLength; i++) {
                        dataSize += CodedOutputByteBufferNano.computeEnumSizeNoTag(
                                Array.getInt(array, i));
                    }
                    break;
                default:
                    throw new IllegalArgumentException("Unexpected non-packable type " + type);
            }
            return dataSize;
        }

        @Override
        protected int computeRepeatedSerializedSize(Object array) {
            if (tag == nonPackedTag) {
                // Use base implementation for non-packed data
                return super.computeRepeatedSerializedSize(array);
            } else if (tag == packedTag) {
                // Packed.
                int dataSize = computePackedDataSize(array);
                int payloadSize =
                        dataSize + CodedOutputByteBufferNano.computeRawVarint32Size(dataSize);
                return payloadSize + CodedOutputByteBufferNano.computeRawVarint32Size(tag);
            } else {
                throw new IllegalArgumentException("Unexpected repeated extension tag " + tag
                        + ", unequal to both non-packed variant " + nonPackedTag
                        + " and packed variant " + packedTag);
            }
        }

        @Override
        protected final int computeSingularSerializedSize(Object value) {
            int fieldNumber = WireFormatNano.getTagFieldNumber(tag);
            switch (type) {
                case TYPE_DOUBLE:
                    Double doubleValue = (Double) value;
                    return CodedOutputByteBufferNano.computeDoubleSize(fieldNumber, doubleValue);
                case TYPE_FLOAT:
                    Float floatValue = (Float) value;
                    return CodedOutputByteBufferNano.computeFloatSize(fieldNumber, floatValue);
                case TYPE_INT64:
                    Long int64Value = (Long) value;
                    return CodedOutputByteBufferNano.computeInt64Size(fieldNumber, int64Value);
                case TYPE_UINT64:
                    Long uint64Value = (Long) value;
                    return CodedOutputByteBufferNano.computeUInt64Size(fieldNumber, uint64Value);
                case TYPE_INT32:
                    Integer int32Value = (Integer) value;
                    return CodedOutputByteBufferNano.computeInt32Size(fieldNumber, int32Value);
                case TYPE_FIXED64:
                    Long fixed64Value = (Long) value;
                    return CodedOutputByteBufferNano.computeFixed64Size(fieldNumber, fixed64Value);
                case TYPE_FIXED32:
                    Integer fixed32Value = (Integer) value;
                    return CodedOutputByteBufferNano.computeFixed32Size(fieldNumber, fixed32Value);
                case TYPE_BOOL:
                    Boolean boolValue = (Boolean) value;
                    return CodedOutputByteBufferNano.computeBoolSize(fieldNumber, boolValue);
                case TYPE_STRING:
                    String stringValue = (String) value;
                    return CodedOutputByteBufferNano.computeStringSize(fieldNumber, stringValue);
                case TYPE_BYTES:
                    byte[] bytesValue = (byte[]) value;
                    return CodedOutputByteBufferNano.computeBytesSize(fieldNumber, bytesValue);
                case TYPE_UINT32:
                    Integer uint32Value = (Integer) value;
                    return CodedOutputByteBufferNano.computeUInt32Size(fieldNumber, uint32Value);
                case TYPE_ENUM:
                    Integer enumValue = (Integer) value;
                    return CodedOutputByteBufferNano.computeEnumSize(fieldNumber, enumValue);
                case TYPE_SFIXED32:
                    Integer sfixed32Value = (Integer) value;
                    return CodedOutputByteBufferNano.computeSFixed32Size(fieldNumber,
                            sfixed32Value);
                case TYPE_SFIXED64:
                    Long sfixed64Value = (Long) value;
                    return CodedOutputByteBufferNano.computeSFixed64Size(fieldNumber,
                            sfixed64Value);
                case TYPE_SINT32:
                    Integer sint32Value = (Integer) value;
                    return CodedOutputByteBufferNano.computeSInt32Size(fieldNumber, sint32Value);
                case TYPE_SINT64:
                    Long sint64Value = (Long) value;
                    return CodedOutputByteBufferNano.computeSInt64Size(fieldNumber, sint64Value);
                default:
                    throw new IllegalArgumentException("Unknown type " + type);
            }
        }
    }
}
