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