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