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 }