1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 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.platform.AndroidDataFiles; 28 import com.ibm.icu.util.ICUUncheckedIOException; 29 import com.ibm.icu.util.VersionInfo; 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 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<>(); 283 284 static { 285 // BEGIN Android-changed: Initialize ICU data file paths. 286 /* 287 // Normally com.ibm.icu.impl.ICUBinary.dataPath. 288 String dataPath = ICUConfig.get(ICUBinary.class.getName() + ".dataPath"); 289 */ 290 String dataPath = null; 291 // Only when runs after repackaging ICU4J. Otherwise the jar should have the ICU resources. 292 if (ICUBinary.class.getName().startsWith("android.icu")) { 293 dataPath = AndroidDataFiles.generateIcuDataPath(); 294 } 295 // END Android-changed: Initialize ICU data file paths. 296 if (dataPath != null) { 297 addDataFilesFromPath(dataPath, icuDataFiles); 298 } 299 } 300 301 private static void addDataFilesFromPath(String dataPath, List<DataFile> files) { 302 // Split the path and find files in each location. 303 // This splitting code avoids the regex pattern compilation in String.split() 304 // and its array allocation. 305 // (There is no simple by-character split() 306 // and the StringTokenizer "is discouraged in new code".) 307 int pathStart = 0; 308 while (pathStart < dataPath.length()) { 309 int sepIndex = dataPath.indexOf(File.pathSeparatorChar, pathStart); 310 int pathLimit; 311 if (sepIndex >= 0) { 312 pathLimit = sepIndex; 313 } else { 314 pathLimit = dataPath.length(); 315 } 316 String path = dataPath.substring(pathStart, pathLimit).trim(); 317 if (path.endsWith(File.separator)) { 318 path = path.substring(0, path.length() - 1); 319 } 320 if (path.length() != 0) { 321 addDataFilesFromFolder(new File(path), new StringBuilder(), icuDataFiles); 322 } 323 if (sepIndex < 0) { 324 break; 325 } 326 pathStart = sepIndex + 1; 327 } 328 } 329 330 private static void addDataFilesFromFolder(File folder, StringBuilder itemPath, 331 List<DataFile> dataFiles) { 332 File[] files = folder.listFiles(); 333 if (files == null || files.length == 0) { 334 return; 335 } 336 int folderPathLength = itemPath.length(); 337 if (folderPathLength > 0) { 338 // The item path must use the ICU file separator character, 339 // not the platform-dependent File.separatorChar, 340 // so that the enumerated item paths match the paths requested by ICU code. 341 itemPath.append('/'); 342 ++folderPathLength; 343 } 344 for (File file : files) { 345 String fileName = file.getName(); 346 if (fileName.endsWith(".txt")) { 347 continue; 348 } 349 itemPath.append(fileName); 350 if (file.isDirectory()) { 351 // TODO: Within a folder, put all single files before all .dat packages? 352 addDataFilesFromFolder(file, itemPath, dataFiles); 353 } else if (fileName.endsWith(".dat")) { 354 ByteBuffer pkgBytes = mapFile(file); 355 if (pkgBytes != null && DatPackageReader.validate(pkgBytes)) { 356 dataFiles.add(new PackageDataFile(itemPath.toString(), pkgBytes)); 357 } 358 } else { 359 dataFiles.add(new SingleDataFile(itemPath.toString(), file)); 360 } 361 itemPath.setLength(folderPathLength); 362 } 363 } 364 365 /** 366 * Compares the length-specified input key with the 367 * NUL-terminated table key. (ASCII) 368 */ 369 static int compareKeys(CharSequence key, ByteBuffer bytes, int offset) { 370 for (int i = 0;; ++i, ++offset) { 371 int c2 = bytes.get(offset); 372 if (c2 == 0) { 373 if (i == key.length()) { 374 return 0; 375 } else { 376 return 1; // key > table key because key is longer. 377 } 378 } else if (i == key.length()) { 379 return -1; // key < table key because key is shorter. 380 } 381 int diff = key.charAt(i) - c2; 382 if (diff != 0) { 383 return diff; 384 } 385 } 386 } 387 388 static int compareKeys(CharSequence key, byte[] bytes, int offset) { 389 for (int i = 0;; ++i, ++offset) { 390 int c2 = bytes[offset]; 391 if (c2 == 0) { 392 if (i == key.length()) { 393 return 0; 394 } else { 395 return 1; // key > table key because key is longer. 396 } 397 } else if (i == key.length()) { 398 return -1; // key < table key because key is shorter. 399 } 400 int diff = key.charAt(i) - c2; 401 if (diff != 0) { 402 return diff; 403 } 404 } 405 } 406 407 // public inner interface ------------------------------------------------ 408 409 /** 410 * Special interface for data authentication 411 */ 412 public static interface Authenticate 413 { 414 /** 415 * Method used in ICUBinary.readHeader() to provide data format 416 * authentication. 417 * @param version version of the current data 418 * @return true if dataformat is an acceptable version, false otherwise 419 */ 420 public boolean isDataVersionAcceptable(byte version[]); 421 } 422 423 // public methods -------------------------------------------------------- 424 425 /** 426 * Loads an ICU binary data file and returns it as a ByteBuffer. 427 * The buffer contents is normally read-only, but its position etc. can be modified. 428 * 429 * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu". 430 * @return The data as a read-only ByteBuffer, 431 * or null if the resource could not be found. 432 */ 433 public static ByteBuffer getData(String itemPath) { 434 return getData(null, null, itemPath, false); 435 } 436 437 /** 438 * Loads an ICU binary data file and returns it as a ByteBuffer. 439 * The buffer contents is normally read-only, but its position etc. can be modified. 440 * 441 * @param loader Used for loader.getResourceAsStream() unless the data is found elsewhere. 442 * @param resourceName Resource name for use with the loader. 443 * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu". 444 * @return The data as a read-only ByteBuffer, 445 * or null if the resource could not be found. 446 */ 447 public static ByteBuffer getData(ClassLoader loader, String resourceName, String itemPath) { 448 return getData(loader, resourceName, itemPath, false); 449 } 450 451 /** 452 * Loads an ICU binary data file and returns it as a ByteBuffer. 453 * The buffer contents is normally read-only, but its position etc. can be modified. 454 * 455 * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu". 456 * @return The data as a read-only ByteBuffer. 457 * @throws MissingResourceException if required==true and the resource could not be found 458 */ 459 public static ByteBuffer getRequiredData(String itemPath) { 460 return getData(null, null, itemPath, true); 461 } 462 463 /** 464 * Loads an ICU binary data file and returns it as a ByteBuffer. 465 * The buffer contents is normally read-only, but its position etc. can be modified. 466 * 467 * @param loader Used for loader.getResourceAsStream() unless the data is found elsewhere. 468 * @param resourceName Resource name for use with the loader. 469 * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu". 470 * @return The data as a read-only ByteBuffer. 471 * @throws MissingResourceException if required==true and the resource could not be found 472 */ 473 // public static ByteBuffer getRequiredData(ClassLoader loader, String resourceName, 474 // String itemPath) { 475 // return getData(loader, resourceName, itemPath, true); 476 // } 477 478 /** 479 * Loads an ICU binary data file and returns it as a ByteBuffer. 480 * The buffer contents is normally read-only, but its position etc. can be modified. 481 * 482 * @param loader Used for loader.getResourceAsStream() unless the data is found elsewhere. 483 * @param resourceName Resource name for use with the loader. 484 * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu". 485 * @param required If the resource cannot be found, 486 * this method returns null (!required) or throws an exception (required). 487 * @return The data as a read-only ByteBuffer, 488 * or null if required==false and the resource could not be found. 489 * @throws MissingResourceException if required==true and the resource could not be found 490 */ 491 private static ByteBuffer getData(ClassLoader loader, String resourceName, 492 String itemPath, boolean required) { 493 ByteBuffer bytes = getDataFromFile(itemPath); 494 if (bytes != null) { 495 return bytes; 496 } 497 if (loader == null) { 498 loader = ClassLoaderUtil.getClassLoader(ICUData.class); 499 } 500 if (resourceName == null) { 501 resourceName = ICUData.ICU_BASE_NAME + '/' + itemPath; 502 } 503 ByteBuffer buffer = null; 504 try { 505 @SuppressWarnings("resource") // Closed by getByteBufferFromInputStreamAndCloseStream(). 506 InputStream is = ICUData.getStream(loader, resourceName, required); 507 if (is == null) { 508 return null; 509 } 510 buffer = getByteBufferFromInputStreamAndCloseStream(is); 511 } catch (IOException e) { 512 throw new ICUUncheckedIOException(e); 513 } 514 return buffer; 515 } 516 517 private static ByteBuffer getDataFromFile(String itemPath) { 518 for (DataFile dataFile : icuDataFiles) { 519 ByteBuffer data = dataFile.getData(itemPath); 520 if (data != null) { 521 return data; 522 } 523 } 524 return null; 525 } 526 527 @SuppressWarnings("resource") // Closing a file closes its channel. 528 private static ByteBuffer mapFile(File path) { 529 FileInputStream file; 530 try { 531 file = new FileInputStream(path); 532 FileChannel channel = file.getChannel(); 533 ByteBuffer bytes = null; 534 try { 535 bytes = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); 536 } finally { 537 file.close(); 538 } 539 return bytes; 540 } catch (FileNotFoundException ignored) { 541 System.err.println(ignored); 542 } catch (IOException ignored) { 543 System.err.println(ignored); 544 } 545 return null; 546 } 547 548 /** 549 * @param folder The relative ICU data folder, like "" or "coll". 550 * @param suffix Usually ".res". 551 * @param names File base names relative to the folder are added without the suffix, 552 * for example "de_CH". 553 */ 554 public static void addBaseNamesInFileFolder(String folder, String suffix, Set<String> names) { 555 for (DataFile dataFile : icuDataFiles) { 556 dataFile.addBaseNamesInFolder(folder, suffix, names); 557 } 558 } 559 560 /** 561 * Same as readHeader(), but returns a VersionInfo rather than a compact int. 562 */ 563 public static VersionInfo readHeaderAndDataVersion(ByteBuffer bytes, 564 int dataFormat, 565 Authenticate authenticate) 566 throws IOException { 567 return getVersionInfoFromCompactInt(readHeader(bytes, dataFormat, authenticate)); 568 } 569 570 /** 571 * Reads an ICU data header, checks the data format, and returns the data version. 572 * 573 * <p>Assumes that the ByteBuffer position is 0 on input. 574 * The buffer byte order is set according to the data. 575 * The buffer position is advanced past the header (including UDataInfo and comment). 576 * 577 * <p>See C++ ucmndata.h and unicode/udata.h. 578 * 579 * @return dataVersion 580 * @throws IOException if this is not a valid ICU data item of the expected dataFormat 581 */ 582 public static int readHeader(ByteBuffer bytes, int dataFormat, Authenticate authenticate) 583 throws IOException { 584 assert bytes != null && bytes.position() == 0; 585 byte magic1 = bytes.get(2); 586 byte magic2 = bytes.get(3); 587 if (magic1 != MAGIC1 || magic2 != MAGIC2) { 588 throw new IOException(MAGIC_NUMBER_AUTHENTICATION_FAILED_); 589 } 590 591 byte isBigEndian = bytes.get(8); 592 byte charsetFamily = bytes.get(9); 593 byte sizeofUChar = bytes.get(10); 594 if (isBigEndian < 0 || 1 < isBigEndian || 595 charsetFamily != CHAR_SET_ || sizeofUChar != CHAR_SIZE_) { 596 throw new IOException(HEADER_AUTHENTICATION_FAILED_); 597 } 598 bytes.order(isBigEndian != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN); 599 600 int headerSize = bytes.getChar(0); 601 int sizeofUDataInfo = bytes.getChar(4); 602 if (sizeofUDataInfo < 20 || headerSize < (sizeofUDataInfo + 4)) { 603 throw new IOException("Internal Error: Header size error"); 604 } 605 // TODO: Change Authenticate to take int major, int minor, int milli, int micro 606 // to avoid array allocation. 607 byte[] formatVersion = new byte[] { 608 bytes.get(16), bytes.get(17), bytes.get(18), bytes.get(19) 609 }; 610 if (bytes.get(12) != (byte)(dataFormat >> 24) || 611 bytes.get(13) != (byte)(dataFormat >> 16) || 612 bytes.get(14) != (byte)(dataFormat >> 8) || 613 bytes.get(15) != (byte)dataFormat || 614 (authenticate != null && !authenticate.isDataVersionAcceptable(formatVersion))) { 615 throw new IOException(HEADER_AUTHENTICATION_FAILED_ + 616 String.format("; data format %02x%02x%02x%02x, format version %d.%d.%d.%d", 617 bytes.get(12), bytes.get(13), bytes.get(14), bytes.get(15), 618 formatVersion[0] & 0xff, formatVersion[1] & 0xff, 619 formatVersion[2] & 0xff, formatVersion[3] & 0xff)); 620 } 621 622 bytes.position(headerSize); 623 return // dataVersion 624 (bytes.get(20) << 24) | 625 ((bytes.get(21) & 0xff) << 16) | 626 ((bytes.get(22) & 0xff) << 8) | 627 (bytes.get(23) & 0xff); 628 } 629 630 /** 631 * Writes an ICU data header. 632 * Does not write a copyright string. 633 * 634 * @return The length of the header (number of bytes written). 635 * @throws IOException from the DataOutputStream 636 */ 637 public static int writeHeader(int dataFormat, int formatVersion, int dataVersion, 638 DataOutputStream dos) throws IOException { 639 // ucmndata.h MappedData 640 dos.writeChar(32); // headerSize 641 dos.writeByte(MAGIC1); 642 dos.writeByte(MAGIC2); 643 // unicode/udata.h UDataInfo 644 dos.writeChar(20); // sizeof(UDataInfo) 645 dos.writeChar(0); // reservedWord 646 dos.writeByte(1); // isBigEndian 647 dos.writeByte(CHAR_SET_); // charsetFamily 648 dos.writeByte(CHAR_SIZE_); // sizeofUChar 649 dos.writeByte(0); // reservedByte 650 dos.writeInt(dataFormat); 651 dos.writeInt(formatVersion); 652 dos.writeInt(dataVersion); 653 // 8 bytes padding for 32 bytes headerSize (multiple of 16). 654 dos.writeLong(0); 655 assert dos.size() == 32; 656 return 32; 657 } 658 659 public static void skipBytes(ByteBuffer bytes, int skipLength) { 660 if (skipLength > 0) { 661 bytes.position(bytes.position() + skipLength); 662 } 663 } 664 665 public static byte[] getBytes(ByteBuffer bytes, int length, int additionalSkipLength) { 666 byte[] dest = new byte[length]; 667 bytes.get(dest); 668 if (additionalSkipLength > 0) { 669 skipBytes(bytes, additionalSkipLength); 670 } 671 return dest; 672 } 673 674 public static String getString(ByteBuffer bytes, int length, int additionalSkipLength) { 675 CharSequence cs = bytes.asCharBuffer(); 676 String s = cs.subSequence(0, length).toString(); 677 skipBytes(bytes, length * 2 + additionalSkipLength); 678 return s; 679 } 680 681 public static char[] getChars(ByteBuffer bytes, int length, int additionalSkipLength) { 682 char[] dest = new char[length]; 683 bytes.asCharBuffer().get(dest); 684 skipBytes(bytes, length * 2 + additionalSkipLength); 685 return dest; 686 } 687 688 public static short[] getShorts(ByteBuffer bytes, int length, int additionalSkipLength) { 689 short[] dest = new short[length]; 690 bytes.asShortBuffer().get(dest); 691 skipBytes(bytes, length * 2 + additionalSkipLength); 692 return dest; 693 } 694 695 public static int[] getInts(ByteBuffer bytes, int length, int additionalSkipLength) { 696 int[] dest = new int[length]; 697 bytes.asIntBuffer().get(dest); 698 skipBytes(bytes, length * 4 + additionalSkipLength); 699 return dest; 700 } 701 702 public static long[] getLongs(ByteBuffer bytes, int length, int additionalSkipLength) { 703 long[] dest = new long[length]; 704 bytes.asLongBuffer().get(dest); 705 skipBytes(bytes, length * 8 + additionalSkipLength); 706 return dest; 707 } 708 709 /** 710 * Same as ByteBuffer.slice() plus preserving the byte order. 711 */ 712 public static ByteBuffer sliceWithOrder(ByteBuffer bytes) { 713 ByteBuffer b = bytes.slice(); 714 return b.order(bytes.order()); 715 } 716 717 /** 718 * Reads the entire contents from the stream into a byte array 719 * and wraps it into a ByteBuffer. Closes the InputStream at the end. 720 */ 721 public static ByteBuffer getByteBufferFromInputStreamAndCloseStream(InputStream is) throws IOException { 722 try { 723 // is.available() may return 0, or 1, or the total number of bytes in the stream, 724 // or some other number. 725 // Do not try to use is.available() == 0 to find the end of the stream! 726 byte[] bytes; 727 int avail = is.available(); 728 if (avail > 32) { 729 // There are more bytes available than just the ICU data header length. 730 // With luck, it is the total number of bytes. 731 bytes = new byte[avail]; 732 } else { 733 bytes = new byte[128]; // empty .res files are even smaller 734 } 735 // Call is.read(...) until one returns a negative value. 736 int length = 0; 737 for(;;) { 738 if (length < bytes.length) { 739 int numRead = is.read(bytes, length, bytes.length - length); 740 if (numRead < 0) { 741 break; // end of stream 742 } 743 length += numRead; 744 } else { 745 // See if we are at the end of the stream before we grow the array. 746 int nextByte = is.read(); 747 if (nextByte < 0) { 748 break; 749 } 750 int capacity = 2 * bytes.length; 751 if (capacity < 128) { 752 capacity = 128; 753 } else if (capacity < 0x4000) { 754 capacity *= 2; // Grow faster until we reach 16kB. 755 } 756 bytes = Arrays.copyOf(bytes, capacity); 757 bytes[length++] = (byte) nextByte; 758 } 759 } 760 return ByteBuffer.wrap(bytes, 0, length); 761 } finally { 762 is.close(); 763 } 764 } 765 766 /** 767 * Returns a VersionInfo for the bytes in the compact version integer. 768 */ 769 public static VersionInfo getVersionInfoFromCompactInt(int version) { 770 return VersionInfo.getInstance( 771 version >>> 24, (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff); 772 } 773 774 /** 775 * Returns an array of the bytes in the compact version integer. 776 */ 777 public static byte[] getVersionByteArrayFromCompactInt(int version) { 778 return new byte[] { 779 (byte)(version >> 24), 780 (byte)(version >> 16), 781 (byte)(version >> 8), 782 (byte)(version) 783 }; 784 } 785 786 // private variables ------------------------------------------------- 787 788 /** 789 * Magic numbers to authenticate the data file 790 */ 791 private static final byte MAGIC1 = (byte)0xda; 792 private static final byte MAGIC2 = (byte)0x27; 793 794 /** 795 * File format authentication values 796 */ 797 private static final byte CHAR_SET_ = 0; 798 private static final byte CHAR_SIZE_ = 2; 799 800 /** 801 * Error messages 802 */ 803 private static final String MAGIC_NUMBER_AUTHENTICATION_FAILED_ = 804 "ICU data file error: Not an ICU data file"; 805 private static final String HEADER_AUTHENTICATION_FAILED_ = 806 "ICU data file error: Header authentication failed, please check if you have a valid ICU data file"; 807 } 808