View Javadoc

    1   /*
    2    *  Licensed to the Apache Software Foundation (ASF) under one
    3    *  or more contributor license agreements.  See the NOTICE file
    4    *  distributed with this work for additional information
    5    *  regarding copyright ownership.  The ASF licenses this file
    6    *  to you under the Apache License, Version 2.0 (the
    7    *  "License"); you may not use this file except in compliance
    8    *  with the License.  You may obtain a copy of the License at
    9    *
    10   *    http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   *  Unless required by applicable law or agreed to in writing,
    13   *  software distributed under the License is distributed on an
    14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   *  KIND, either express or implied.  See the License for the
    16   *  specific language governing permissions and limitations
    17   *  under the License.
    18   *
    19   */
    20  package org.apache.mina.codec.delimited.ints;
    21
    22  import java.nio.BufferUnderflowException;
    23  import java.nio.ByteBuffer;
    24
    25  import org.apache.mina.codec.IoBuffer;
    26  import org.apache.mina.codec.ProtocolDecoderException;
    27  import org.apache.mina.codec.delimited.ByteBufferEncoder;
    28  import org.apache.mina.codec.delimited.IoBufferDecoder;
    29
    30  /**
    31   * Class providing a variable length representation of integers.
    32   *
    33   * <style type="text/css"> pre-fw { color: rgb(0, 0, 0); display: block;
    34   * font-family:courier, "courier new", monospace; font-size: 13px; white-space:
    35   * pre; } </style>
    36   *
    37   * <h2>Base 128 Varints serializer</h2>
    38   * <p>
    39   * This serializer is efficient in terms of computing costs as well as
    40   * bandwith/memory usage.
    41   * </p>
    42   * <p>
    43   * The average memory usage overall the range 0 to
    44   * {@link java.lang.Integer#MAX_VALUE} is 4.87 bytes per number which is not far
    45   * from the canonical form ({@link RawInt32}), however varints are an
    46   * interesting solution since the small values (which are supposed to be more
    47   * frequent) are using less bytes.
    48   * </p>
    49   * <p>
    50   * All bytes forming a varint except the last one have the most significant bit
    51   * (MSB) set. The lower 7 bits of each byte contains the actual representation
    52   * of the two's complement representation of the number (least significant group
    53   * first).
    54   * </p>
    55   * <p>
    56   * n.b. This serializer is fully compatible with the 128 Varint mechanism
    57   * shipped with the <a
    58   * href="https://developers.google.com/protocol-buffers/docs/encoding#varints" >
    59   * Google Protocol Buffer stack</a> as default representation of messages sizes.
    60   * </p>
    61   * <h2>On-wire representation</h2>
    62   * <p>
    63   * Encoding of the value 812
    64   *
    65   * <pre-fw>
    66   *
    67   * 1001 1100  0000 0110
    68   * ↑          ↑
    69   * 1          0           // the most significant bit being unset designs the last byte
    70   *  ___↑____   ___↑____
    71   *  001 1100   000 0110   // the remaining bits defines the value itself
    72   * →      44          6   // 44 + 128 * 6 = 812
    73   * </pre-fw>
    74   *
    75   * </p>
    76   * <p>
    77   * n.b. This class doesn't have any dependency against Google Protocol Buffer or
    78   * any other library in order to provide this convenient integer serialization
    79   * module to any software using FramedMINA.
    80   * </p>
    81   *
    82   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
    83   */
    84  /*
    85   * About the suppression of warnings:
    86   * This class contains a lot of bit-shifting, logical and/or operations order to handle
    87   * VarInt conversions. The code contains a lot of hard-coded integer that tools like
    88   * Sonar classify as "magic numbers". Using final static variables for all of them
    89   * would have resulted in a code less readable.
    90   * The "all" scope is too generic, but Sonar doesn't not handle properly others scopes
    91   * like "MagicNumber" (Sonar 3.6 - 03July2013)
    92   */
    93  @SuppressWarnings("all")
    94  public final class VarInt implements IntTranscoder {
    95
    96      @Override
    97      public IoBufferDecoder<Integer> getDecoder() {
    98          return new Decoder();
    99      }
    100
    101     @Override
    102     public ByteBufferEncoder<Integer> getEncoder() {
    103         return new Encoder();
    104     }
    105
    106     /**
    107      * Documentation available in the {@link VarInt} enclosing class.
    108      *
    109      * @author <a href="http://mina.apache.org">Apache MINA Project</a>
    110      *
    111      */
    112     private class Decoder extends IoBufferDecoder<Integer> {
    113
    114         @Override
    115         public Integer decode(IoBuffer input) {
    116             int origpos = input.position();
    117
    118             try {
    119                 byte tmp = input.get();
    120                 if (tmp >= 0) {
    121                     return (int) tmp;
    122                 }
    123                 int result = tmp & 0x7f;
    124                 if ((tmp = input.get()) >= 0) {
    125                     result |= tmp << 7;
    126                 } else {
    127                     result |= (tmp & 0x7f) << 7;
    128                     if ((tmp = input.get()) >= 0) {
    129                         result |= tmp << 14;
    130                     } else {
    131                         result |= (tmp & 0x7f) << 14;
    132                         if ((tmp = input.get()) >= 0) {
    133                             result |= tmp << 21;
    134                         } else {
    135                             result |= (tmp & 0x7f) << 21;
    136
    137                             // check that there are at most 3 significant bits available
    138                             if (((tmp = input.get()) & ~0x7) == 0) {
    139                                 result |= tmp << 28;
    140                             } else {
    141                                 throw new ProtocolDecoderException("Not the varint representation of a signed int32");
    142                             }
    143                         }
    144                     }
    145                 }
    146                 return result;
    147
    148             } catch (BufferUnderflowException bue) {
    149                 input.position(origpos);
    150             }
    151             return null;
    152         }
    153     }
    154
    155     /**
    156      * Documentation available in the {@link VarInt} enclosing class.
    157      *
    158      * @author <a href="http://mina.apache.org">Apache MINA Project</a>
    159      *
    160      */
    161     private class Encoder extends ByteBufferEncoder<Integer> {
    162
    163         @Override
    164         public void writeTo(Integer message, ByteBuffer buffer) {
    165             // VarInts don't support negative values
    166             int value = Math.max(0,message);
    167
    168             while (true) {
    169                 if ((value & ~0x7F) == 0) {
    170                     buffer.put((byte) value);
    171                     return;
    172                 } else {
    173                     buffer.put((byte) ((value & 0x7F) | 0x80));
    174                     value >>>= 7;
    175                 }
    176             }
    177
    178         }
    179
    180         @Override
    181         public int getEncodedSize(Integer value) {
    182             if ((value & (0xffffffff << 7)) == 0) {
    183                 return 1;
    184             }
    185             if ((value & (0xffffffff << 14)) == 0) {
    186                 return 2;
    187             }
    188             if ((value & (0xffffffff << 21)) == 0) {
    189                 return 3;
    190             }
    191             if ((value & (0xffffffff << 28)) == 0) {
    192                 return 4;
    193             }
    194             return 5;
    195         }
    196
    197     }
    198 }