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