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