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