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 }