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 }