• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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