View Javadoc

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 }