17935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/*
27935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
39fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer * Copyright (C) 1996-2015, International Business Machines Corporation and
47935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * others. All Rights Reserved.
57935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
67935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
77935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
87935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpackage com.ibm.icu.impl;
97935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.DataOutputStream;
117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.File;
127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.FileInputStream;
137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.FileNotFoundException;
147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.IOException;
157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.InputStream;
167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.nio.ByteBuffer;
177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.nio.ByteOrder;
187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.nio.channels.FileChannel;
197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.ArrayList;
207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.List;
217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.MissingResourceException;
229fdd2feb5742e9f969903946b56584e4c8104e69Markus Schererimport java.util.Set;
237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.ICUUncheckedIOException;
257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.VersionInfo;
267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpublic final class ICUBinary {
287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Reads the ICU .dat package file format.
307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Most methods do not modify the ByteBuffer in any way,
317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * not even its position or other state.
327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final class DatPackageReader {
347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * .dat package data format ID "CmnD".
367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private static final int DATA_FORMAT = 0x436d6e44;
387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private static final class IsAcceptable implements Authenticate {
407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // @Override when we switch to Java 6
417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            public boolean isDataVersionAcceptable(byte version[]) {
427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return version[0] == 1;
437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private static final IsAcceptable IS_ACCEPTABLE = new IsAcceptable();
467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Checks that the ByteBuffer contains a valid, usable ICU .dat package.
497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Moves the buffer position from 0 to after the data header.
507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
519fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        static boolean validate(ByteBuffer bytes) {
527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            try {
537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                readHeader(bytes, DATA_FORMAT, IS_ACCEPTABLE);
547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } catch (IOException ignored) {
557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return false;
567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int count = bytes.getInt(bytes.position());  // Do not move the position.
587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (count <= 0) {
597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return false;
607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // For each item, there is one ToC entry (8 bytes) and a name string
627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // and a data item of at least 16 bytes.
637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // (We assume no data item duplicate elimination for now.)
647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (bytes.position() + 4 + count * (8 + 16) > bytes.capacity()) {
657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return false;
667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (!startsWithPackageName(bytes, getNameOffset(bytes, 0)) ||
687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    !startsWithPackageName(bytes, getNameOffset(bytes, count - 1))) {
697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return false;
707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return true;
727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private static boolean startsWithPackageName(ByteBuffer bytes, int start) {
757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Compare all but the trailing 'b' or 'l' which depends on the platform.
767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int length = ICUData.PACKAGE_NAME.length() - 1;
777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (int i = 0; i < length; ++i) {
787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (bytes.get(start + i) != ICUData.PACKAGE_NAME.charAt(i)) {
797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    return false;
807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Check for 'b' or 'l' followed by '/'.
837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            byte c = bytes.get(start + length++);
847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if ((c != 'b' && c != 'l') || bytes.get(start + length) != '/') {
857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return false;
867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return true;
887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
909fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        static ByteBuffer getData(ByteBuffer bytes, CharSequence key) {
919fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            int index = binarySearch(bytes, key);
929fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            if (index >= 0) {
939fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                ByteBuffer data = bytes.duplicate();
949fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                data.position(getDataOffset(bytes, index));
959fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                data.limit(getDataOffset(bytes, index + 1));
969fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                return ICUBinary.sliceWithOrder(data);
979fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            } else {
989fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                return null;
999fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            }
1009fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        }
1019fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer
1029fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        static void addBaseNamesInFolder(ByteBuffer bytes, String folder, String suffix, Set<String> names) {
1039fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            // Find the first data item name that starts with the folder name.
1049fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            int index = binarySearch(bytes, folder);
1059fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            if (index < 0) {
1069fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                index = ~index;  // Normal: Otherwise the folder itself is the name of a data item.
1079fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            }
1089fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer
1099fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            int base = bytes.position();
1109fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            int count = bytes.getInt(base);
1119fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            StringBuilder sb = new StringBuilder();
1129fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            while (index < count && addBaseName(bytes, index, folder, suffix, sb, names)) {
1139fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                ++index;
1149fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            }
1159fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        }
1169fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer
1179fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        private static int binarySearch(ByteBuffer bytes, CharSequence key) {
1187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int base = bytes.position();
1197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int count = bytes.getInt(base);
1207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Do a binary search for the key.
1227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int start = 0;
1237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int limit = count;
1247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            while (start < limit) {
1257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int mid = (start + limit) >>> 1;
1267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int nameOffset = getNameOffset(bytes, mid);
1277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Skip "icudt54b/".
1287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                nameOffset += ICUData.PACKAGE_NAME.length() + 1;
1297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int result = compareKeys(key, bytes, nameOffset);
1307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (result < 0) {
1317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    limit = mid;
1327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else if (result > 0) {
1337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    start = mid + 1;
1347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
1357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // We found it!
1369fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                    return mid;
1377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
1387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
1399fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            return ~start;  // Not found or table is empty.
1407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private static int getNameOffset(ByteBuffer bytes, int index) {
1437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int base = bytes.position();
1447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assert 0 <= index && index < bytes.getInt(base);  // count
1457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // The count integer (4 bytes)
1467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // is followed by count (nameOffset, dataOffset) integer pairs (8 bytes per pair).
1477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return base + bytes.getInt(base + 4 + index * 8);
1487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private static int getDataOffset(ByteBuffer bytes, int index) {
1517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int base = bytes.position();
1527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int count = bytes.getInt(base);
1537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (index == count) {
1547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Return the limit of the last data item.
1557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return bytes.capacity();
1567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
1577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assert 0 <= index && index < count;
1587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // The count integer (4 bytes)
1597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // is followed by count (nameOffset, dataOffset) integer pairs (8 bytes per pair).
1607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // The dataOffset follows the nameOffset (skip another 4 bytes).
1617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return base + bytes.getInt(base + 4 + 4 + index * 8);
1627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1639fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer
1649fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        static boolean addBaseName(ByteBuffer bytes, int index,
1659fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                String folder, String suffix, StringBuilder sb, Set<String> names) {
1669fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            int offset = getNameOffset(bytes, index);
1679fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            // Skip "icudt54b/".
1689fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            offset += ICUData.PACKAGE_NAME.length() + 1;
1699fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            if (folder.length() != 0) {
1709fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                // Test name.startsWith(folder + '/').
1719fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                for (int i = 0; i < folder.length(); ++i, ++offset) {
1729fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                    if (bytes.get(offset) != folder.charAt(i)) {
1739fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                        return false;
1749fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                    }
1759fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                }
1769fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                if (bytes.get(offset++) != '/') {
1779fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                    return false;
1789fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                }
1799fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            }
1809fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            // Collect the NUL-terminated name and test for a subfolder, then test for the suffix.
1819fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            sb.setLength(0);
1829fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            byte b;
1839fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            while ((b = bytes.get(offset++)) != 0) {
1849fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                char c = (char) b;
1859fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                if (c == '/') {
1869fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                    return true;  // Skip subfolder contents.
1879fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                }
1889fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                sb.append(c);
1899fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            }
1909fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            int nameLimit = sb.length() - suffix.length();
1919fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            if (sb.lastIndexOf(suffix, nameLimit) >= 0) {
1929fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                names.add(sb.substring(0, nameLimit));
1939fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            }
1949fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            return true;
1959fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        }
1967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1989fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer    private static abstract class DataFile {
1999fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        protected final String itemPath;
2009fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer
2019fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        DataFile(String item) {
2029fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            itemPath = item;
2039fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        }
2049fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        @Override
2059fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        public String toString() {
2069fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            return itemPath;
2079fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        }
2089fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer
2099fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        abstract ByteBuffer getData(String requestedPath);
2109fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer
2117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
2129fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer         * @param folder The relative ICU data folder, like "" or "coll".
2139fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer         * @param suffix Usually ".res".
2149fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer         * @param names File base names relative to the folder are added without the suffix,
2159fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer         *        for example "de_CH".
2167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
2179fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        abstract void addBaseNamesInFolder(String folder, String suffix, Set<String> names);
2189fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer    }
2199fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer
2209fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer    private static final class SingleDataFile extends DataFile {
2219fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        private final File path;
2229fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer
2239fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        SingleDataFile(String item, File path) {
2249fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            super(item);
2259fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            this.path = path;
2269fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        }
2279fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        @Override
2289fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        public String toString() {
2299fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            return path.toString();
2309fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        }
2319fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer
2329fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        @Override
2339fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        ByteBuffer getData(String requestedPath) {
2349fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            if (requestedPath.equals(itemPath)) {
2359fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                return mapFile(path);
2369fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            } else {
2379fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                return null;
2389fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            }
2399fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        }
2409fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer
2419fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        @Override
2429fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        void addBaseNamesInFolder(String folder, String suffix, Set<String> names) {
2439fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            if (itemPath.length() > folder.length() + suffix.length() &&
2449fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                    itemPath.startsWith(folder) &&
2459fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                    itemPath.endsWith(suffix) &&
2469fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                    itemPath.charAt(folder.length()) == '/' &&
2479fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                    itemPath.indexOf('/', folder.length() + 1) < 0) {
2489fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                names.add(itemPath.substring(folder.length() + 1,
2499fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                        itemPath.length() - suffix.length()));
2509fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            }
2519fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        }
2529fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer    }
2539fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer
2549fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer    private static final class PackageDataFile extends DataFile {
2557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
2567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * .dat package bytes, or null if not a .dat package.
2577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * position() is after the header.
2587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Do not modify the position or other state, for thread safety.
2597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
2609fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        private final ByteBuffer pkgBytes;
2617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2629fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        PackageDataFile(String item, ByteBuffer bytes) {
2639fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            super(item);
2647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            pkgBytes = bytes;
2657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2669fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer
2679fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        @Override
2689fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        ByteBuffer getData(String requestedPath) {
2699fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            return DatPackageReader.getData(pkgBytes, requestedPath);
2709fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        }
2719fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer
2729fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        @Override
2739fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        void addBaseNamesInFolder(String folder, String suffix, Set<String> names) {
2749fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            DatPackageReader.addBaseNamesInFolder(pkgBytes, folder, suffix, names);
2757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2779fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer
2787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final List<DataFile> icuDataFiles = new ArrayList<DataFile>();
2797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static {
2817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Normally com.ibm.icu.impl.ICUBinary.dataPath.
2827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String dataPath = ICUConfig.get(ICUBinary.class.getName() + ".dataPath");
2837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (dataPath != null) {
2847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            addDataFilesFromPath(dataPath, icuDataFiles);
2857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static void addDataFilesFromPath(String dataPath, List<DataFile> files) {
2897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Split the path and find files in each location.
2907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // This splitting code avoids the regex pattern compilation in String.split()
2917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // and its array allocation.
2927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // (There is no simple by-character split()
2937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // and the StringTokenizer "is discouraged in new code".)
2947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int pathStart = 0;
2957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        while (pathStart < dataPath.length()) {
2967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int sepIndex = dataPath.indexOf(File.pathSeparatorChar, pathStart);
2977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int pathLimit;
2987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (sepIndex >= 0) {
2997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                pathLimit = sepIndex;
3007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
3017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                pathLimit = dataPath.length();
3027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String path = dataPath.substring(pathStart, pathLimit).trim();
3047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (path.endsWith(File.separator)) {
3057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                path = path.substring(0, path.length() - 1);
3067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (path.length() != 0) {
3087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                addDataFilesFromFolder(new File(path), new StringBuilder(), icuDataFiles);
3097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (sepIndex < 0) {
3117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
3127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            pathStart = sepIndex + 1;
3147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static void addDataFilesFromFolder(File folder, StringBuilder itemPath,
3187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            List<DataFile> dataFiles) {
3197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        File[] files = folder.listFiles();
3207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (files == null || files.length == 0) {
3217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return;
3227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int folderPathLength = itemPath.length();
3247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (folderPathLength > 0) {
3257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // The item path must use the ICU file separator character,
3267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // not the platform-dependent File.separatorChar,
3277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // so that the enumerated item paths match the paths requested by ICU code.
3287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            itemPath.append('/');
3297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ++folderPathLength;
3307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (File file : files) {
3327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String fileName = file.getName();
3337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (fileName.endsWith(".txt")) {
3347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                continue;
3357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            itemPath.append(fileName);
3377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (file.isDirectory()) {
3387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // TODO: Within a folder, put all single files before all .dat packages?
3397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                addDataFilesFromFolder(file, itemPath, dataFiles);
3407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if (fileName.endsWith(".dat")) {
3417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ByteBuffer pkgBytes = mapFile(file);
3427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (pkgBytes != null && DatPackageReader.validate(pkgBytes)) {
3439fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                    dataFiles.add(new PackageDataFile(itemPath.toString(), pkgBytes));
3447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
3457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
3469fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                dataFiles.add(new SingleDataFile(itemPath.toString(), file));
3477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            itemPath.setLength(folderPathLength);
3497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
3537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Compares the length-specified input key with the
3547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * NUL-terminated table key. (ASCII)
3557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
3567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static int compareKeys(CharSequence key, ByteBuffer bytes, int offset) {
3577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0;; ++i, ++offset) {
3587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int c2 = bytes.get(offset);
3597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (c2 == 0) {
3607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (i == key.length()) {
3617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    return 0;
3627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
3637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    return 1;  // key > table key because key is longer.
3647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
3657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if (i == key.length()) {
3667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return -1;  // key < table key because key is shorter.
3677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int diff = (int)key.charAt(i) - c2;
3697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (diff != 0) {
3707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return diff;
3717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // public inner interface ------------------------------------------------
3767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
3787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Special interface for data authentication
3797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
3807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static interface Authenticate
3817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    {
3827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
3837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Method used in ICUBinary.readHeader() to provide data format
3847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * authentication.
3857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @param version version of the current data
3867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @return true if dataformat is an acceptable version, false otherwise
3877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
3887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public boolean isDataVersionAcceptable(byte version[]);
3897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // public methods --------------------------------------------------------
3927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
3947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Loads an ICU binary data file and returns it as a ByteBuffer.
3957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The buffer contents is normally read-only, but its position etc. can be modified.
3967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
3977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu".
3987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return The data as a read-only ByteBuffer,
3997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         or null if the resource could not be found.
4007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
4017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static ByteBuffer getData(String itemPath) {
4027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return getData(null, null, itemPath, false);
4037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
4067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Loads an ICU binary data file and returns it as a ByteBuffer.
4077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The buffer contents is normally read-only, but its position etc. can be modified.
4087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
4097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param loader Used for loader.getResourceAsStream() unless the data is found elsewhere.
4107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param resourceName Resource name for use with the loader.
4117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu".
4127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return The data as a read-only ByteBuffer,
4137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         or null if the resource could not be found.
4147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
4157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static ByteBuffer getData(ClassLoader loader, String resourceName, String itemPath) {
4167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return getData(loader, resourceName, itemPath, false);
4177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
4207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Loads an ICU binary data file and returns it as a ByteBuffer.
4217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The buffer contents is normally read-only, but its position etc. can be modified.
4227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
4237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu".
4247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return The data as a read-only ByteBuffer.
4257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws MissingResourceException if required==true and the resource could not be found
4267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
4277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static ByteBuffer getRequiredData(String itemPath) {
4287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return getData(null, null, itemPath, true);
4297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
4327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Loads an ICU binary data file and returns it as a ByteBuffer.
4337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The buffer contents is normally read-only, but its position etc. can be modified.
4347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
4357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param loader Used for loader.getResourceAsStream() unless the data is found elsewhere.
4367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param resourceName Resource name for use with the loader.
4377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu".
4387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return The data as a read-only ByteBuffer.
4397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws MissingResourceException if required==true and the resource could not be found
4407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
4417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert//    public static ByteBuffer getRequiredData(ClassLoader loader, String resourceName,
4427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert//            String itemPath) {
4437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert//        return getData(loader, resourceName, itemPath, true);
4447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert//    }
4457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
4477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Loads an ICU binary data file and returns it as a ByteBuffer.
4487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The buffer contents is normally read-only, but its position etc. can be modified.
4497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
4507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param loader Used for loader.getResourceAsStream() unless the data is found elsewhere.
4517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param resourceName Resource name for use with the loader.
4527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu".
4537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param required If the resource cannot be found,
4547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *        this method returns null (!required) or throws an exception (required).
4557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return The data as a read-only ByteBuffer,
4567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         or null if required==false and the resource could not be found.
4577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws MissingResourceException if required==true and the resource could not be found
4587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
4597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static ByteBuffer getData(ClassLoader loader, String resourceName,
4607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String itemPath, boolean required) {
4617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ByteBuffer bytes = getDataFromFile(itemPath);
4627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (bytes != null) {
4637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return bytes;
4647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (loader == null) {
466f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            loader = ClassLoaderUtil.getClassLoader(ICUData.class);
4677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (resourceName == null) {
4697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            resourceName = ICUData.ICU_BASE_NAME + '/' + itemPath;
4707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
471aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert        ByteBuffer buffer = null;
4727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        try {
473aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert            @SuppressWarnings("resource")  // Closed by getByteBufferFromInputStreamAndCloseStream().
474aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert            InputStream is = ICUData.getStream(loader, resourceName, required);
475aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert            if (is == null) {
476aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert                return null;
477aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert            }
478aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert            buffer = getByteBufferFromInputStreamAndCloseStream(is);
4797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } catch (IOException e) {
4807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new ICUUncheckedIOException(e);
4817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
482aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert        return buffer;
4837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static ByteBuffer getDataFromFile(String itemPath) {
4867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (DataFile dataFile : icuDataFiles) {
4879fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            ByteBuffer data = dataFile.getData(itemPath);
4889fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            if (data != null) {
4899fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer                return data;
4907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
4917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return null;
4937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
495aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert    @SuppressWarnings("resource")  // Closing a file closes its channel.
4967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static ByteBuffer mapFile(File path) {
4977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        FileInputStream file;
4987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        try {
4997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            file = new FileInputStream(path);
5007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            FileChannel channel = file.getChannel();
501aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert            ByteBuffer bytes = null;
502aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert            try {
503aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert                bytes = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
504aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert            } finally {
505aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert                file.close();
506aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert            }
5077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return bytes;
508aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert        } catch (FileNotFoundException ignored) {
5097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            System.err.println(ignored);
5107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } catch (IOException ignored) {
5117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            System.err.println(ignored);
5127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return null;
5147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
5179fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer     * @param folder The relative ICU data folder, like "" or "coll".
5189fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer     * @param suffix Usually ".res".
5199fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer     * @param names File base names relative to the folder are added without the suffix,
5209fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer     *        for example "de_CH".
5219fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer     */
5229fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer    public static void addBaseNamesInFileFolder(String folder, String suffix, Set<String> names) {
5239fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        for (DataFile dataFile : icuDataFiles) {
5249fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer            dataFile.addBaseNamesInFolder(folder, suffix, names);
5259fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer        }
5269fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer    }
5279fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer
5289fdd2feb5742e9f969903946b56584e4c8104e69Markus Scherer    /**
5297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Same as readHeader(), but returns a VersionInfo rather than a compact int.
5307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static VersionInfo readHeaderAndDataVersion(ByteBuffer bytes,
5327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                                             int dataFormat,
5337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                                             Authenticate authenticate)
5347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                                                throws IOException {
5357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return getVersionInfoFromCompactInt(readHeader(bytes, dataFormat, authenticate));
5367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
5397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Reads an ICU data header, checks the data format, and returns the data version.
5407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
5417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>Assumes that the ByteBuffer position is 0 on input.
5427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The buffer byte order is set according to the data.
5437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The buffer position is advanced past the header (including UDataInfo and comment).
5447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
5457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>See C++ ucmndata.h and unicode/udata.h.
5467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
5477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return dataVersion
5487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IOException if this is not a valid ICU data item of the expected dataFormat
5497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static int readHeader(ByteBuffer bytes, int dataFormat, Authenticate authenticate)
5517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throws IOException {
5527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assert bytes.position() == 0;
5537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        byte magic1 = bytes.get(2);
5547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        byte magic2 = bytes.get(3);
5557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (magic1 != MAGIC1 || magic2 != MAGIC2) {
5567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IOException(MAGIC_NUMBER_AUTHENTICATION_FAILED_);
5577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        byte isBigEndian = bytes.get(8);
5607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        byte charsetFamily = bytes.get(9);
5617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        byte sizeofUChar = bytes.get(10);
5627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (isBigEndian < 0 || 1 < isBigEndian ||
5637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                charsetFamily != CHAR_SET_ || sizeofUChar != CHAR_SIZE_) {
5647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IOException(HEADER_AUTHENTICATION_FAILED_);
5657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        bytes.order(isBigEndian != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
5677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int headerSize = bytes.getChar(0);
5697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int sizeofUDataInfo = bytes.getChar(4);
5707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (sizeofUDataInfo < 20 || headerSize < (sizeofUDataInfo + 4)) {
5717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IOException("Internal Error: Header size error");
5727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // TODO: Change Authenticate to take int major, int minor, int milli, int micro
5747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // to avoid array allocation.
5757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        byte[] formatVersion = new byte[] {
5767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            bytes.get(16), bytes.get(17), bytes.get(18), bytes.get(19)
5777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        };
5787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (bytes.get(12) != (byte)(dataFormat >> 24) ||
5797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                bytes.get(13) != (byte)(dataFormat >> 16) ||
5807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                bytes.get(14) != (byte)(dataFormat >> 8) ||
5817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                bytes.get(15) != (byte)dataFormat ||
5827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                (authenticate != null && !authenticate.isDataVersionAcceptable(formatVersion))) {
5837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IOException(HEADER_AUTHENTICATION_FAILED_ +
5847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    String.format("; data format %02x%02x%02x%02x, format version %d.%d.%d.%d",
5857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            bytes.get(12), bytes.get(13), bytes.get(14), bytes.get(15),
5867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            formatVersion[0] & 0xff, formatVersion[1] & 0xff,
5877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            formatVersion[2] & 0xff, formatVersion[3] & 0xff));
5887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        bytes.position(headerSize);
5917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return  // dataVersion
5927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ((int)bytes.get(20) << 24) |
5937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ((bytes.get(21) & 0xff) << 16) |
5947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ((bytes.get(22) & 0xff) << 8) |
5957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                (bytes.get(23) & 0xff);
5967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
5997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Writes an ICU data header.
6007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Does not write a copyright string.
6017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
6027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return The length of the header (number of bytes written).
6037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IOException from the DataOutputStream
6047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
6057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static int writeHeader(int dataFormat, int formatVersion, int dataVersion,
6067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            DataOutputStream dos) throws IOException {
6077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // ucmndata.h MappedData
6087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        dos.writeChar(32);  // headerSize
6097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        dos.writeByte(MAGIC1);
6107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        dos.writeByte(MAGIC2);
6117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // unicode/udata.h UDataInfo
6127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        dos.writeChar(20);  // sizeof(UDataInfo)
6137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        dos.writeChar(0);  // reservedWord
6147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        dos.writeByte(1);  // isBigEndian
6157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        dos.writeByte(CHAR_SET_);  // charsetFamily
6167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        dos.writeByte(CHAR_SIZE_);  // sizeofUChar
6177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        dos.writeByte(0);  // reservedByte
6187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        dos.writeInt(dataFormat);
6197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        dos.writeInt(formatVersion);
6207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        dos.writeInt(dataVersion);
6217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // 8 bytes padding for 32 bytes headerSize (multiple of 16).
6227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        dos.writeLong(0);
6237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assert dos.size() == 32;
6247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return 32;
6257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static void skipBytes(ByteBuffer bytes, int skipLength) {
6287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (skipLength > 0) {
6297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            bytes.position(bytes.position() + skipLength);
6307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
6347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Same as ByteBuffer.slice() plus preserving the byte order.
6357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
6367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static ByteBuffer sliceWithOrder(ByteBuffer bytes) {
6377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ByteBuffer b = bytes.slice();
6387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return b.order(bytes.order());
6397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
6427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Reads the entire contents from the stream into a byte array
6437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * and wraps it into a ByteBuffer. Closes the InputStream at the end.
6447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
645aacdd6f022693689b3bf76f70670711f3254a441Fredrik Roubert    public static ByteBuffer getByteBufferFromInputStreamAndCloseStream(InputStream is) throws IOException {
6467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        try {
647d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer            // is.available() may return 0, or 1, or the total number of bytes in the stream,
648d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer            // or some other number.
649d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer            // Do not try to use is.available() == 0 to find the end of the stream!
650d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer            byte[] bytes;
6517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int avail = is.available();
652d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer            if (avail > 32) {
653d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                // There are more bytes available than just the ICU data header length.
654d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                // With luck, it is the total number of bytes.
655d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                bytes = new byte[avail];
656d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer            } else {
657d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                bytes = new byte[128];  // empty .res files are even smaller
6587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
659d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer            // Call is.read(...) until one returns a negative value.
660d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer            int length = 0;
661d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer            for(;;) {
662d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                if (length < bytes.length) {
663d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    int numRead = is.read(bytes, length, bytes.length - length);
664d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    if (numRead < 0) {
665d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                        break;  // end of stream
666d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    }
667d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    length += numRead;
668d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                } else {
669d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    // See if we are at the end of the stream before we grow the array.
670d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    int nextByte = is.read();
671d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    if (nextByte < 0) {
672d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                        break;
673d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    }
674d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    int capacity = 2 * bytes.length;
675d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    if (capacity < 128) {
676d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                        capacity = 128;
677d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    } else if (capacity < 0x4000) {
678d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                        capacity *= 2;  // Grow faster until we reach 16kB.
679d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    }
680d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    // TODO Java 6 replace new byte[] and arraycopy(): bytes = Arrays.copyOf(bytes, capacity);
681d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    byte[] newBytes = new byte[capacity];
682d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    System.arraycopy(bytes, 0, newBytes, 0, length);
683d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    bytes = newBytes;
684d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                    bytes[length++] = (byte) nextByte;
685d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer                }
686d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer            }
687d7f3b880a48cd3947bc8bdb1ea9d55a3e3c93011Markus Scherer            return ByteBuffer.wrap(bytes, 0, length);
6887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } finally {
6897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            is.close();
6907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
6947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns a VersionInfo for the bytes in the compact version integer.
6957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
6967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static VersionInfo getVersionInfoFromCompactInt(int version) {
6977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return VersionInfo.getInstance(
6987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                version >>> 24, (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff);
6997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
7007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
7027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns an array of the bytes in the compact version integer.
7037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
7047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static byte[] getVersionByteArrayFromCompactInt(int version) {
7057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new byte[] {
7067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                (byte)(version >> 24),
7077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                (byte)(version >> 16),
7087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                (byte)(version >> 8),
7097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                (byte)(version)
7107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        };
7117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
7127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // private variables -------------------------------------------------
7147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
7167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    * Magic numbers to authenticate the data file
7177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    */
7187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final byte MAGIC1 = (byte)0xda;
7197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final byte MAGIC2 = (byte)0x27;
7207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
7227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    * File format authentication values
7237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    */
7247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final byte CHAR_SET_ = 0;
7257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final byte CHAR_SIZE_ = 2;
7267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
7287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    * Error messages
7297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    */
7307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final String MAGIC_NUMBER_AUTHENTICATION_FAILED_ =
7317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                       "ICU data file error: Not an ICU data file";
7327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final String HEADER_AUTHENTICATION_FAILED_ =
7337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        "ICU data file error: Header authentication failed, please check if you have a valid ICU data file";
7347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert}
735