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