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