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