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