1 /*
2 Copyright 2007 Ramon Servadei
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16 package fulmine.protocol.specification;
17
18 import static fulmine.util.Utils.logException;
19
20 import java.io.UnsupportedEncodingException;
21
22 import fulmine.util.array.ArrayUtils;
23
24 /**
25 * Utility methods for writing primitives to a byte[] using the fulmine delta
26 * (FD) protocol. As per the protocol, integrals can be compacted; the
27 * appropriate integral write methods perform this compacting.
28 * <p>
29 * Please refer to the "Fulmine Delta Transmission Protocol" specification for a
30 * complete description of the fulmine serialisation technique.
31 *
32 * @see ByteReader
33 * @author Ramon Servadei
34 *
35 */
36 public final class ByteWriter
37 {
38
39 /**
40 * Write the data byte[] into buffer[0][start]
41 *
42 * @param data
43 * the data to transform into a byte[] representation
44 * @param buffer
45 * buffer[0] will hold the data
46 * @param start
47 * the position in bytes[0] where the data should be written
48 * @return the number of bytes written
49 */
50 public static int writeBytes(final byte[] data, final byte[][] buffer,
51 final int start)
52 {
53 ArrayUtils.resizeIfNeeded(buffer, start + data.length);
54 System.arraycopy(data, 0, buffer[0], start, data.length);
55 return data.length;
56 }
57
58 /**
59 * Transform the data into a byte[] representation. The byte data is written
60 * to bytes[0][start].
61 *
62 * @param data
63 * the data to transform into a byte[] representation
64 * @param bytes
65 * bytes[0] will hold the data
66 * @param start
67 * the position in bytes[0] where the data should be written
68 * @return the number of bytes written. The number of bytes written is 2x
69 * the number of characters in the data (UTF16 character is 2
70 * bytes).
71 */
72 public static int writeString(final String data, final byte[][] bytes,
73 final int start)
74 {
75 return writeBytes(getBytes(data), bytes, start);
76 }
77
78 /**
79 * Transform the data into a byte[] representation. The byte data is written
80 * to bytes[0][start].
81 *
82 * @param data
83 * the data to transform into a byte[] representation
84 * @param bytes
85 * bytes[0] will hold the data
86 * @param start
87 * the position in bytes[0] where the data should be written
88 * @return the number of bytes written. For a boolean, 1 byte is written
89 * with a value 0 for false, 1 for true.
90 */
91 public static int writeBoolean(final boolean data, final byte[][] bytes,
92 final int start)
93 {
94 writeIntegerAsBytes((data ? 1 : 0), bytes, start, 1);
95 return 1;
96 }
97
98 /**
99 * Transform the data into a byte[] representation. The byte data is written
100 * to bytes[0][start].
101 *
102 * @param data
103 * the data to transform into a byte[] representation
104 * @param bytes
105 * bytes[0] will hold the data
106 * @param start
107 * the position in bytes[0] where the data should be written
108 * @return the number of bytes written. For a float, the raw bits are
109 * written into the byte[], this will always take up 4 bytes.
110 */
111 public static int writeFloat(final float data, final byte[][] bytes,
112 final int start)
113 {
114 writeIntegerAsBytes(Float.floatToRawIntBits(data), bytes, start, 4);
115 return 4;
116 }
117
118 /**
119 * Transform the data into a byte[] representation. The byte data is written
120 * to bytes[0][start].
121 *
122 * @param data
123 * the data to transform into a byte[] representation
124 * @param bytes
125 * bytes[0] will hold the data
126 * @param start
127 * the position in bytes[0] where the data should be written
128 * @return the number of bytes written. For a double the raw bits are
129 * written into the byte[], this will always take up 8 bytes.
130 */
131 public static int writeDouble(final double data, final byte[][] bytes,
132 final int start)
133 {
134 writeLongAsBytes(Double.doubleToRawLongBits(data), bytes, start, 8);
135 return 8;
136 }
137
138 /**
139 * Transform the data into a byte[] representation. The byte data is written
140 * to bytes[0][start].
141 *
142 * @param data
143 * the data to transform into a byte[] representation
144 * @param bytes
145 * bytes[0] will hold the data
146 * @param start
147 * the position in bytes[0] where the data should be written
148 * @return the number of bytes written. The number of bytes written depends
149 * on the integral value.
150 */
151 public static int writeInteger(final int data, final byte[][] bytes,
152 final int start)
153 {
154 int numberOfBytes = 0;
155 if (data == 0)
156 {
157 numberOfBytes = 1;
158 }
159 else
160 {
161 if (data > 0)
162 {
163 numberOfBytes = getByteCountForInteger(data);
164 }
165 else
166 {
167 numberOfBytes = getByteCountForInteger(~data);
168 }
169 }
170 writeIntegerAsBytes(data, bytes, start, numberOfBytes);
171 return numberOfBytes;
172 }
173
174 /**
175 * Transform the data into a byte[] representation. The byte data is written
176 * to bytes[0][start].
177 *
178 * @param data
179 * the data to transform into a byte[] representation
180 * @param bytes
181 * bytes[0] will hold the data
182 * @param start
183 * the position in bytes[0] where the data should be written
184 * @return the number of bytes written. The number of bytes written depends
185 * on the integral value.
186 */
187 public static int writeLong(final long data, final byte[][] bytes,
188 final int start)
189 {
190 int numberOfBytes = 0;
191 if (data == 0)
192 {
193 numberOfBytes = 1;
194 }
195 else
196 {
197 if (data > 0)
198 {
199 numberOfBytes = getByteCountForLong(data);
200 }
201 else
202 {
203 numberOfBytes = getByteCountForLong(~data);
204 }
205 }
206 writeLongAsBytes(data, bytes, start, numberOfBytes);
207 return numberOfBytes;
208 }
209
210 /**
211 * Get the byte[] for that data string encoded to
212 * {@link ByteConstants.ENCODING}.
213 *
214 * @param data
215 * the string to get the byte[] for
216 * @return the byte[] for the string in the encoding format or an empty
217 * array if the string is null
218 */
219 public static byte[] getBytes(String data)
220 {
221 try
222 {
223 return data == null ? new byte[0]
224 : data.getBytes(ByteConstants.ENCODING);
225 }
226 catch (UnsupportedEncodingException e)
227 {
228 final String error =
229 "Could not write '" + data + "' in " + ByteConstants.ENCODING
230 + " format";
231 logException(null, error, e);
232 throw new RuntimeException(error, e);
233 }
234 }
235
236 /**
237 * Write the integer data into buffer[0], starting at buffer[0][startFrom].
238 * Only the bytes of the integer up to numberOfBytes are written.
239 *
240 * @param data
241 * the data to write into buffer[0][startFrom]
242 * @param buffer
243 * the buffer to hold the data
244 * @param startFrom
245 * the index in buffer[0] to start writing the bytes
246 * @param numberOfBytes
247 * the number of bytes of data to write, starting from the least
248 * significant byte
249 */
250 public static void writeIntegerAsBytes(final int data,
251 final byte[][] buffer, int startFrom, int numberOfBytes)
252 {
253 ArrayUtils.resizeIfNeeded(buffer, startFrom + numberOfBytes);
254 for (int i = numberOfBytes; i > 0; i--)
255 {
256 buffer[0][startFrom++] =
257 (byte) (data >>> ByteConstants.bitShiftForByteOrdinal[i]);
258 }
259 }
260
261 /**
262 * Write the long data into buffer[0], starting at buffer[0][startFrom].
263 * Only the bytes of the integer up to numberOfBytes are written.
264 *
265 * @param data
266 * the data to write into buffer[0][startFrom]
267 * @param buffer
268 * the buffer to hold the data
269 * @param startFrom
270 * the index in buffer[0] to start writing the bytes
271 * @param numberOfBytes
272 * the number of bytes of data to write, starting from the least
273 * significant byte
274 */
275 static void writeLongAsBytes(final long fieldData, final byte[][] buffer,
276 int startFrom, int numberOfBytes)
277 {
278 ArrayUtils.resizeIfNeeded(buffer, startFrom + numberOfBytes);
279 for (int i = numberOfBytes; i > 0; i--)
280 {
281 buffer[0][startFrom++] =
282 (byte) (fieldData >>> ByteConstants.bitShiftForByteOrdinal[i]);
283 }
284 }
285
286 static int getByteCountForLong(final long data)
287 {
288 final int bytesToWrite;
289 if ((data & 0x000000007fffffffL) == data)
290 {
291 // 4 bytes or less
292 bytesToWrite = getByteCountForInteger((int) data);
293 }
294 else
295 {
296 // more than 4 bytes
297 if ((data & 0x00007fffffffffffL) == data)
298 {
299 // 6 bytes or less
300 if ((data & 0x0000007fffffffffL) == data)
301 {
302 // 5 bytes
303 bytesToWrite = 5;
304 }
305 else
306 {
307 // 6 bytes
308 bytesToWrite = 6;
309 }
310 }
311 else
312 {
313 // more than 6 bytes
314 if ((data & 0x007fffffffffffffL) == data)
315 {
316 // 7 bytes
317 bytesToWrite = 7;
318 }
319 else
320 {
321 // 8 bytes
322 bytesToWrite = 8;
323 }
324 }
325 }
326 return bytesToWrite;
327 }
328
329 static int getByteCountForInteger(final int data)
330 {
331 final int bytesToWrite;
332 // 4 bytes or less
333 if ((data & 0x00007fff) == data)
334 {
335 // 2 bytes or less
336 if ((data & 0x000007f) == data)
337 {
338 // 1 byte
339 bytesToWrite = 1;
340 }
341 else
342 {
343 // 2 bytes
344 bytesToWrite = 2;
345 }
346 }
347 else
348 {
349 // more than 2 bytes
350 if ((data & 0x007fffff) == data)
351 {
352 // 3 bytes
353 bytesToWrite = 3;
354 }
355 else
356 {
357 // 4 bytes
358 bytesToWrite = 4;
359 }
360 }
361 return bytesToWrite;
362 }
363 }