ICUBinary.java revision f716bda031dccdec5e47bb40e758c5901d209729
1/* 2 ******************************************************************************* 3 * Copyright (C) 1996-2015, International Business Machines Corporation and 4 * others. All Rights Reserved. 5 ******************************************************************************* 6 */ 7 8package com.ibm.icu.impl; 9 10import java.io.DataOutputStream; 11import java.io.File; 12import java.io.FileInputStream; 13import java.io.FileNotFoundException; 14import java.io.IOException; 15import java.io.InputStream; 16import java.nio.ByteBuffer; 17import java.nio.ByteOrder; 18import java.nio.channels.FileChannel; 19import java.util.ArrayList; 20import java.util.List; 21import java.util.MissingResourceException; 22import java.util.Set; 23 24import com.ibm.icu.util.ICUUncheckedIOException; 25import com.ibm.icu.util.VersionInfo; 26 27public final class ICUBinary { 28 /** 29 * Reads the ICU .dat package file format. 30 * Most methods do not modify the ByteBuffer in any way, 31 * not even its position or other state. 32 */ 33 private static final class DatPackageReader { 34 /** 35 * .dat package data format ID "CmnD". 36 */ 37 private static final int DATA_FORMAT = 0x436d6e44; 38 39 private static final class IsAcceptable implements Authenticate { 40 // @Override when we switch to Java 6 41 public boolean isDataVersionAcceptable(byte version[]) { 42 return version[0] == 1; 43 } 44 } 45 private static final IsAcceptable IS_ACCEPTABLE = new IsAcceptable(); 46 47 /** 48 * Checks that the ByteBuffer contains a valid, usable ICU .dat package. 49 * Moves the buffer position from 0 to after the data header. 50 */ 51 static boolean validate(ByteBuffer bytes) { 52 try { 53 readHeader(bytes, DATA_FORMAT, IS_ACCEPTABLE); 54 } catch (IOException ignored) { 55 return false; 56 } 57 int count = bytes.getInt(bytes.position()); // Do not move the position. 58 if (count <= 0) { 59 return false; 60 } 61 // For each item, there is one ToC entry (8 bytes) and a name string 62 // and a data item of at least 16 bytes. 63 // (We assume no data item duplicate elimination for now.) 64 if (bytes.position() + 4 + count * (8 + 16) > bytes.capacity()) { 65 return false; 66 } 67 if (!startsWithPackageName(bytes, getNameOffset(bytes, 0)) || 68 !startsWithPackageName(bytes, getNameOffset(bytes, count - 1))) { 69 return false; 70 } 71 return true; 72 } 73 74 private static boolean startsWithPackageName(ByteBuffer bytes, int start) { 75 // Compare all but the trailing 'b' or 'l' which depends on the platform. 76 int length = ICUData.PACKAGE_NAME.length() - 1; 77 for (int i = 0; i < length; ++i) { 78 if (bytes.get(start + i) != ICUData.PACKAGE_NAME.charAt(i)) { 79 return false; 80 } 81 } 82 // Check for 'b' or 'l' followed by '/'. 83 byte c = bytes.get(start + length++); 84 if ((c != 'b' && c != 'l') || bytes.get(start + length) != '/') { 85 return false; 86 } 87 return true; 88 } 89 90 static ByteBuffer getData(ByteBuffer bytes, CharSequence key) { 91 int index = binarySearch(bytes, key); 92 if (index >= 0) { 93 ByteBuffer data = bytes.duplicate(); 94 data.position(getDataOffset(bytes, index)); 95 data.limit(getDataOffset(bytes, index + 1)); 96 return ICUBinary.sliceWithOrder(data); 97 } else { 98 return null; 99 } 100 } 101 102 static void addBaseNamesInFolder(ByteBuffer bytes, String folder, String suffix, Set<String> names) { 103 // Find the first data item name that starts with the folder name. 104 int index = binarySearch(bytes, folder); 105 if (index < 0) { 106 index = ~index; // Normal: Otherwise the folder itself is the name of a data item. 107 } 108 109 int base = bytes.position(); 110 int count = bytes.getInt(base); 111 StringBuilder sb = new StringBuilder(); 112 while (index < count && addBaseName(bytes, index, folder, suffix, sb, names)) { 113 ++index; 114 } 115 } 116 117 private static int binarySearch(ByteBuffer bytes, CharSequence key) { 118 int base = bytes.position(); 119 int count = bytes.getInt(base); 120 121 // Do a binary search for the key. 122 int start = 0; 123 int limit = count; 124 while (start < limit) { 125 int mid = (start + limit) >>> 1; 126 int nameOffset = getNameOffset(bytes, mid); 127 // Skip "icudt54b/". 128 nameOffset += ICUData.PACKAGE_NAME.length() + 1; 129 int result = compareKeys(key, bytes, nameOffset); 130 if (result < 0) { 131 limit = mid; 132 } else if (result > 0) { 133 start = mid + 1; 134 } else { 135 // We found it! 136 return mid; 137 } 138 } 139 return ~start; // Not found or table is empty. 140 } 141 142 private static int getNameOffset(ByteBuffer bytes, int index) { 143 int base = bytes.position(); 144 assert 0 <= index && index < bytes.getInt(base); // count 145 // The count integer (4 bytes) 146 // is followed by count (nameOffset, dataOffset) integer pairs (8 bytes per pair). 147 return base + bytes.getInt(base + 4 + index * 8); 148 } 149 150 private static int getDataOffset(ByteBuffer bytes, int index) { 151 int base = bytes.position(); 152 int count = bytes.getInt(base); 153 if (index == count) { 154 // Return the limit of the last data item. 155 return bytes.capacity(); 156 } 157 assert 0 <= index && index < count; 158 // The count integer (4 bytes) 159 // is followed by count (nameOffset, dataOffset) integer pairs (8 bytes per pair). 160 // The dataOffset follows the nameOffset (skip another 4 bytes). 161 return base + bytes.getInt(base + 4 + 4 + index * 8); 162 } 163 164 static boolean addBaseName(ByteBuffer bytes, int index, 165 String folder, String suffix, StringBuilder sb, Set<String> names) { 166 int offset = getNameOffset(bytes, index); 167 // Skip "icudt54b/". 168 offset += ICUData.PACKAGE_NAME.length() + 1; 169 if (folder.length() != 0) { 170 // Test name.startsWith(folder + '/'). 171 for (int i = 0; i < folder.length(); ++i, ++offset) { 172 if (bytes.get(offset) != folder.charAt(i)) { 173 return false; 174 } 175 } 176 if (bytes.get(offset++) != '/') { 177 return false; 178 } 179 } 180 // Collect the NUL-terminated name and test for a subfolder, then test for the suffix. 181 sb.setLength(0); 182 byte b; 183 while ((b = bytes.get(offset++)) != 0) { 184 char c = (char) b; 185 if (c == '/') { 186 return true; // Skip subfolder contents. 187 } 188 sb.append(c); 189 } 190 int nameLimit = sb.length() - suffix.length(); 191 if (sb.lastIndexOf(suffix, nameLimit) >= 0) { 192 names.add(sb.substring(0, nameLimit)); 193 } 194 return true; 195 } 196 } 197 198 private static abstract class DataFile { 199 protected final String itemPath; 200 201 DataFile(String item) { 202 itemPath = item; 203 } 204 @Override 205 public String toString() { 206 return itemPath; 207 } 208 209 abstract ByteBuffer getData(String requestedPath); 210 211 /** 212 * @param folder The relative ICU data folder, like "" or "coll". 213 * @param suffix Usually ".res". 214 * @param names File base names relative to the folder are added without the suffix, 215 * for example "de_CH". 216 */ 217 abstract void addBaseNamesInFolder(String folder, String suffix, Set<String> names); 218 } 219 220 private static final class SingleDataFile extends DataFile { 221 private final File path; 222 223 SingleDataFile(String item, File path) { 224 super(item); 225 this.path = path; 226 } 227 @Override 228 public String toString() { 229 return path.toString(); 230 } 231 232 @Override 233 ByteBuffer getData(String requestedPath) { 234 if (requestedPath.equals(itemPath)) { 235 return mapFile(path); 236 } else { 237 return null; 238 } 239 } 240 241 @Override 242 void addBaseNamesInFolder(String folder, String suffix, Set<String> names) { 243 if (itemPath.length() > folder.length() + suffix.length() && 244 itemPath.startsWith(folder) && 245 itemPath.endsWith(suffix) && 246 itemPath.charAt(folder.length()) == '/' && 247 itemPath.indexOf('/', folder.length() + 1) < 0) { 248 names.add(itemPath.substring(folder.length() + 1, 249 itemPath.length() - suffix.length())); 250 } 251 } 252 } 253 254 private static final class PackageDataFile extends DataFile { 255 /** 256 * .dat package bytes, or null if not a .dat package. 257 * position() is after the header. 258 * Do not modify the position or other state, for thread safety. 259 */ 260 private final ByteBuffer pkgBytes; 261 262 PackageDataFile(String item, ByteBuffer bytes) { 263 super(item); 264 pkgBytes = bytes; 265 } 266 267 @Override 268 ByteBuffer getData(String requestedPath) { 269 return DatPackageReader.getData(pkgBytes, requestedPath); 270 } 271 272 @Override 273 void addBaseNamesInFolder(String folder, String suffix, Set<String> names) { 274 DatPackageReader.addBaseNamesInFolder(pkgBytes, folder, suffix, names); 275 } 276 } 277 278 private static final List<DataFile> icuDataFiles = new ArrayList<DataFile>(); 279 280 static { 281 // Normally com.ibm.icu.impl.ICUBinary.dataPath. 282 String dataPath = ICUConfig.get(ICUBinary.class.getName() + ".dataPath"); 283 if (dataPath != null) { 284 addDataFilesFromPath(dataPath, icuDataFiles); 285 } 286 } 287 288 private static void addDataFilesFromPath(String dataPath, List<DataFile> files) { 289 // Split the path and find files in each location. 290 // This splitting code avoids the regex pattern compilation in String.split() 291 // and its array allocation. 292 // (There is no simple by-character split() 293 // and the StringTokenizer "is discouraged in new code".) 294 int pathStart = 0; 295 while (pathStart < dataPath.length()) { 296 int sepIndex = dataPath.indexOf(File.pathSeparatorChar, pathStart); 297 int pathLimit; 298 if (sepIndex >= 0) { 299 pathLimit = sepIndex; 300 } else { 301 pathLimit = dataPath.length(); 302 } 303 String path = dataPath.substring(pathStart, pathLimit).trim(); 304 if (path.endsWith(File.separator)) { 305 path = path.substring(0, path.length() - 1); 306 } 307 if (path.length() != 0) { 308 addDataFilesFromFolder(new File(path), new StringBuilder(), icuDataFiles); 309 } 310 if (sepIndex < 0) { 311 break; 312 } 313 pathStart = sepIndex + 1; 314 } 315 } 316 317 private static void addDataFilesFromFolder(File folder, StringBuilder itemPath, 318 List<DataFile> dataFiles) { 319 File[] files = folder.listFiles(); 320 if (files == null || files.length == 0) { 321 return; 322 } 323 int folderPathLength = itemPath.length(); 324 if (folderPathLength > 0) { 325 // The item path must use the ICU file separator character, 326 // not the platform-dependent File.separatorChar, 327 // so that the enumerated item paths match the paths requested by ICU code. 328 itemPath.append('/'); 329 ++folderPathLength; 330 } 331 for (File file : files) { 332 String fileName = file.getName(); 333 if (fileName.endsWith(".txt")) { 334 continue; 335 } 336 itemPath.append(fileName); 337 if (file.isDirectory()) { 338 // TODO: Within a folder, put all single files before all .dat packages? 339 addDataFilesFromFolder(file, itemPath, dataFiles); 340 } else if (fileName.endsWith(".dat")) { 341 ByteBuffer pkgBytes = mapFile(file); 342 if (pkgBytes != null && DatPackageReader.validate(pkgBytes)) { 343 dataFiles.add(new PackageDataFile(itemPath.toString(), pkgBytes)); 344 } 345 } else { 346 dataFiles.add(new SingleDataFile(itemPath.toString(), file)); 347 } 348 itemPath.setLength(folderPathLength); 349 } 350 } 351 352 /** 353 * Compares the length-specified input key with the 354 * NUL-terminated table key. (ASCII) 355 */ 356 static int compareKeys(CharSequence key, ByteBuffer bytes, int offset) { 357 for (int i = 0;; ++i, ++offset) { 358 int c2 = bytes.get(offset); 359 if (c2 == 0) { 360 if (i == key.length()) { 361 return 0; 362 } else { 363 return 1; // key > table key because key is longer. 364 } 365 } else if (i == key.length()) { 366 return -1; // key < table key because key is shorter. 367 } 368 int diff = (int)key.charAt(i) - c2; 369 if (diff != 0) { 370 return diff; 371 } 372 } 373 } 374 375 // public inner interface ------------------------------------------------ 376 377 /** 378 * Special interface for data authentication 379 */ 380 public static interface Authenticate 381 { 382 /** 383 * Method used in ICUBinary.readHeader() to provide data format 384 * authentication. 385 * @param version version of the current data 386 * @return true if dataformat is an acceptable version, false otherwise 387 */ 388 public boolean isDataVersionAcceptable(byte version[]); 389 } 390 391 // public methods -------------------------------------------------------- 392 393 /** 394 * Loads an ICU binary data file and returns it as a ByteBuffer. 395 * The buffer contents is normally read-only, but its position etc. can be modified. 396 * 397 * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu". 398 * @return The data as a read-only ByteBuffer, 399 * or null if the resource could not be found. 400 */ 401 public static ByteBuffer getData(String itemPath) { 402 return getData(null, null, itemPath, false); 403 } 404 405 /** 406 * Loads an ICU binary data file and returns it as a ByteBuffer. 407 * The buffer contents is normally read-only, but its position etc. can be modified. 408 * 409 * @param loader Used for loader.getResourceAsStream() unless the data is found elsewhere. 410 * @param resourceName Resource name for use with the loader. 411 * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu". 412 * @return The data as a read-only ByteBuffer, 413 * or null if the resource could not be found. 414 */ 415 public static ByteBuffer getData(ClassLoader loader, String resourceName, String itemPath) { 416 return getData(loader, resourceName, itemPath, false); 417 } 418 419 /** 420 * Loads an ICU binary data file and returns it as a ByteBuffer. 421 * The buffer contents is normally read-only, but its position etc. can be modified. 422 * 423 * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu". 424 * @return The data as a read-only ByteBuffer. 425 * @throws MissingResourceException if required==true and the resource could not be found 426 */ 427 public static ByteBuffer getRequiredData(String itemPath) { 428 return getData(null, null, itemPath, true); 429 } 430 431 /** 432 * Loads an ICU binary data file and returns it as a ByteBuffer. 433 * The buffer contents is normally read-only, but its position etc. can be modified. 434 * 435 * @param loader Used for loader.getResourceAsStream() unless the data is found elsewhere. 436 * @param resourceName Resource name for use with the loader. 437 * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu". 438 * @return The data as a read-only ByteBuffer. 439 * @throws MissingResourceException if required==true and the resource could not be found 440 */ 441// public static ByteBuffer getRequiredData(ClassLoader loader, String resourceName, 442// String itemPath) { 443// return getData(loader, resourceName, itemPath, true); 444// } 445 446 /** 447 * Loads an ICU binary data file and returns it as a ByteBuffer. 448 * The buffer contents is normally read-only, but its position etc. can be modified. 449 * 450 * @param loader Used for loader.getResourceAsStream() unless the data is found elsewhere. 451 * @param resourceName Resource name for use with the loader. 452 * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu". 453 * @param required If the resource cannot be found, 454 * this method returns null (!required) or throws an exception (required). 455 * @return The data as a read-only ByteBuffer, 456 * or null if required==false and the resource could not be found. 457 * @throws MissingResourceException if required==true and the resource could not be found 458 */ 459 private static ByteBuffer getData(ClassLoader loader, String resourceName, 460 String itemPath, boolean required) { 461 ByteBuffer bytes = getDataFromFile(itemPath); 462 if (bytes != null) { 463 return bytes; 464 } 465 if (loader == null) { 466 loader = ClassLoaderUtil.getClassLoader(ICUData.class); 467 } 468 if (resourceName == null) { 469 resourceName = ICUData.ICU_BASE_NAME + '/' + itemPath; 470 } 471 ByteBuffer buffer = null; 472 try { 473 @SuppressWarnings("resource") // Closed by getByteBufferFromInputStreamAndCloseStream(). 474 InputStream is = ICUData.getStream(loader, resourceName, required); 475 if (is == null) { 476 return null; 477 } 478 buffer = getByteBufferFromInputStreamAndCloseStream(is); 479 } catch (IOException e) { 480 throw new ICUUncheckedIOException(e); 481 } 482 return buffer; 483 } 484 485 private static ByteBuffer getDataFromFile(String itemPath) { 486 for (DataFile dataFile : icuDataFiles) { 487 ByteBuffer data = dataFile.getData(itemPath); 488 if (data != null) { 489 return data; 490 } 491 } 492 return null; 493 } 494 495 @SuppressWarnings("resource") // Closing a file closes its channel. 496 private static ByteBuffer mapFile(File path) { 497 FileInputStream file; 498 try { 499 file = new FileInputStream(path); 500 FileChannel channel = file.getChannel(); 501 ByteBuffer bytes = null; 502 try { 503 bytes = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); 504 } finally { 505 file.close(); 506 } 507 return bytes; 508 } catch (FileNotFoundException ignored) { 509 System.err.println(ignored); 510 } catch (IOException ignored) { 511 System.err.println(ignored); 512 } 513 return null; 514 } 515 516 /** 517 * @param folder The relative ICU data folder, like "" or "coll". 518 * @param suffix Usually ".res". 519 * @param names File base names relative to the folder are added without the suffix, 520 * for example "de_CH". 521 */ 522 public static void addBaseNamesInFileFolder(String folder, String suffix, Set<String> names) { 523 for (DataFile dataFile : icuDataFiles) { 524 dataFile.addBaseNamesInFolder(folder, suffix, names); 525 } 526 } 527 528 /** 529 * Same as readHeader(), but returns a VersionInfo rather than a compact int. 530 */ 531 public static VersionInfo readHeaderAndDataVersion(ByteBuffer bytes, 532 int dataFormat, 533 Authenticate authenticate) 534 throws IOException { 535 return getVersionInfoFromCompactInt(readHeader(bytes, dataFormat, authenticate)); 536 } 537 538 /** 539 * Reads an ICU data header, checks the data format, and returns the data version. 540 * 541 * <p>Assumes that the ByteBuffer position is 0 on input. 542 * The buffer byte order is set according to the data. 543 * The buffer position is advanced past the header (including UDataInfo and comment). 544 * 545 * <p>See C++ ucmndata.h and unicode/udata.h. 546 * 547 * @return dataVersion 548 * @throws IOException if this is not a valid ICU data item of the expected dataFormat 549 */ 550 public static int readHeader(ByteBuffer bytes, int dataFormat, Authenticate authenticate) 551 throws IOException { 552 assert bytes.position() == 0; 553 byte magic1 = bytes.get(2); 554 byte magic2 = bytes.get(3); 555 if (magic1 != MAGIC1 || magic2 != MAGIC2) { 556 throw new IOException(MAGIC_NUMBER_AUTHENTICATION_FAILED_); 557 } 558 559 byte isBigEndian = bytes.get(8); 560 byte charsetFamily = bytes.get(9); 561 byte sizeofUChar = bytes.get(10); 562 if (isBigEndian < 0 || 1 < isBigEndian || 563 charsetFamily != CHAR_SET_ || sizeofUChar != CHAR_SIZE_) { 564 throw new IOException(HEADER_AUTHENTICATION_FAILED_); 565 } 566 bytes.order(isBigEndian != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN); 567 568 int headerSize = bytes.getChar(0); 569 int sizeofUDataInfo = bytes.getChar(4); 570 if (sizeofUDataInfo < 20 || headerSize < (sizeofUDataInfo + 4)) { 571 throw new IOException("Internal Error: Header size error"); 572 } 573 // TODO: Change Authenticate to take int major, int minor, int milli, int micro 574 // to avoid array allocation. 575 byte[] formatVersion = new byte[] { 576 bytes.get(16), bytes.get(17), bytes.get(18), bytes.get(19) 577 }; 578 if (bytes.get(12) != (byte)(dataFormat >> 24) || 579 bytes.get(13) != (byte)(dataFormat >> 16) || 580 bytes.get(14) != (byte)(dataFormat >> 8) || 581 bytes.get(15) != (byte)dataFormat || 582 (authenticate != null && !authenticate.isDataVersionAcceptable(formatVersion))) { 583 throw new IOException(HEADER_AUTHENTICATION_FAILED_ + 584 String.format("; data format %02x%02x%02x%02x, format version %d.%d.%d.%d", 585 bytes.get(12), bytes.get(13), bytes.get(14), bytes.get(15), 586 formatVersion[0] & 0xff, formatVersion[1] & 0xff, 587 formatVersion[2] & 0xff, formatVersion[3] & 0xff)); 588 } 589 590 bytes.position(headerSize); 591 return // dataVersion 592 ((int)bytes.get(20) << 24) | 593 ((bytes.get(21) & 0xff) << 16) | 594 ((bytes.get(22) & 0xff) << 8) | 595 (bytes.get(23) & 0xff); 596 } 597 598 /** 599 * Writes an ICU data header. 600 * Does not write a copyright string. 601 * 602 * @return The length of the header (number of bytes written). 603 * @throws IOException from the DataOutputStream 604 */ 605 public static int writeHeader(int dataFormat, int formatVersion, int dataVersion, 606 DataOutputStream dos) throws IOException { 607 // ucmndata.h MappedData 608 dos.writeChar(32); // headerSize 609 dos.writeByte(MAGIC1); 610 dos.writeByte(MAGIC2); 611 // unicode/udata.h UDataInfo 612 dos.writeChar(20); // sizeof(UDataInfo) 613 dos.writeChar(0); // reservedWord 614 dos.writeByte(1); // isBigEndian 615 dos.writeByte(CHAR_SET_); // charsetFamily 616 dos.writeByte(CHAR_SIZE_); // sizeofUChar 617 dos.writeByte(0); // reservedByte 618 dos.writeInt(dataFormat); 619 dos.writeInt(formatVersion); 620 dos.writeInt(dataVersion); 621 // 8 bytes padding for 32 bytes headerSize (multiple of 16). 622 dos.writeLong(0); 623 assert dos.size() == 32; 624 return 32; 625 } 626 627 public static void skipBytes(ByteBuffer bytes, int skipLength) { 628 if (skipLength > 0) { 629 bytes.position(bytes.position() + skipLength); 630 } 631 } 632 633 /** 634 * Same as ByteBuffer.slice() plus preserving the byte order. 635 */ 636 public static ByteBuffer sliceWithOrder(ByteBuffer bytes) { 637 ByteBuffer b = bytes.slice(); 638 return b.order(bytes.order()); 639 } 640 641 /** 642 * Reads the entire contents from the stream into a byte array 643 * and wraps it into a ByteBuffer. Closes the InputStream at the end. 644 */ 645 public static ByteBuffer getByteBufferFromInputStreamAndCloseStream(InputStream is) throws IOException { 646 try { 647 // is.available() may return 0, or 1, or the total number of bytes in the stream, 648 // or some other number. 649 // Do not try to use is.available() == 0 to find the end of the stream! 650 byte[] bytes; 651 int avail = is.available(); 652 if (avail > 32) { 653 // There are more bytes available than just the ICU data header length. 654 // With luck, it is the total number of bytes. 655 bytes = new byte[avail]; 656 } else { 657 bytes = new byte[128]; // empty .res files are even smaller 658 } 659 // Call is.read(...) until one returns a negative value. 660 int length = 0; 661 for(;;) { 662 if (length < bytes.length) { 663 int numRead = is.read(bytes, length, bytes.length - length); 664 if (numRead < 0) { 665 break; // end of stream 666 } 667 length += numRead; 668 } else { 669 // See if we are at the end of the stream before we grow the array. 670 int nextByte = is.read(); 671 if (nextByte < 0) { 672 break; 673 } 674 int capacity = 2 * bytes.length; 675 if (capacity < 128) { 676 capacity = 128; 677 } else if (capacity < 0x4000) { 678 capacity *= 2; // Grow faster until we reach 16kB. 679 } 680 // TODO Java 6 replace new byte[] and arraycopy(): bytes = Arrays.copyOf(bytes, capacity); 681 byte[] newBytes = new byte[capacity]; 682 System.arraycopy(bytes, 0, newBytes, 0, length); 683 bytes = newBytes; 684 bytes[length++] = (byte) nextByte; 685 } 686 } 687 return ByteBuffer.wrap(bytes, 0, length); 688 } finally { 689 is.close(); 690 } 691 } 692 693 /** 694 * Returns a VersionInfo for the bytes in the compact version integer. 695 */ 696 public static VersionInfo getVersionInfoFromCompactInt(int version) { 697 return VersionInfo.getInstance( 698 version >>> 24, (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff); 699 } 700 701 /** 702 * Returns an array of the bytes in the compact version integer. 703 */ 704 public static byte[] getVersionByteArrayFromCompactInt(int version) { 705 return new byte[] { 706 (byte)(version >> 24), 707 (byte)(version >> 16), 708 (byte)(version >> 8), 709 (byte)(version) 710 }; 711 } 712 713 // private variables ------------------------------------------------- 714 715 /** 716 * Magic numbers to authenticate the data file 717 */ 718 private static final byte MAGIC1 = (byte)0xda; 719 private static final byte MAGIC2 = (byte)0x27; 720 721 /** 722 * File format authentication values 723 */ 724 private static final byte CHAR_SET_ = 0; 725 private static final byte CHAR_SIZE_ = 2; 726 727 /** 728 * Error messages 729 */ 730 private static final String MAGIC_NUMBER_AUTHENTICATION_FAILED_ = 731 "ICU data file error: Not an ICU data file"; 732 private static final String HEADER_AUTHENTICATION_FAILED_ = 733 "ICU data file error: Header authentication failed, please check if you have a valid ICU data file"; 734} 735