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