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