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