• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 /*
3  *******************************************************************************
4  * Copyright (C) 1996-2015, International Business Machines Corporation and
5  * others. All Rights Reserved.
6  *******************************************************************************
7  */
8 
9 package android.icu.impl;
10 
11 import java.io.DataOutputStream;
12 import java.io.File;
13 import java.io.FileInputStream;
14 import java.io.FileNotFoundException;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.nio.ByteBuffer;
18 import java.nio.ByteOrder;
19 import java.nio.channels.FileChannel;
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.MissingResourceException;
23 import java.util.Set;
24 
25 import android.icu.util.ICUUncheckedIOException;
26 import android.icu.util.VersionInfo;
27 
28 /**
29  * @hide Only a subset of ICU is exposed in Android
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 when we switch to Java 6
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<DataFile>();
283 
284     static {
285         // Normally android.icu.impl.ICUBinary.dataPath.
286         String dataPath = ICUConfig.get(ICUBinary.class.getName() + ".dataPath");
287         if (dataPath != null) {
288             addDataFilesFromPath(dataPath, icuDataFiles);
289         }
290     }
291 
292     private static void addDataFilesFromPath(String dataPath, List<DataFile> files) {
293         // Split the path and find files in each location.
294         // This splitting code avoids the regex pattern compilation in String.split()
295         // and its array allocation.
296         // (There is no simple by-character split()
297         // and the StringTokenizer "is discouraged in new code".)
298         int pathStart = 0;
299         while (pathStart < dataPath.length()) {
300             int sepIndex = dataPath.indexOf(File.pathSeparatorChar, pathStart);
301             int pathLimit;
302             if (sepIndex >= 0) {
303                 pathLimit = sepIndex;
304             } else {
305                 pathLimit = dataPath.length();
306             }
307             String path = dataPath.substring(pathStart, pathLimit).trim();
308             if (path.endsWith(File.separator)) {
309                 path = path.substring(0, path.length() - 1);
310             }
311             if (path.length() != 0) {
312                 addDataFilesFromFolder(new File(path), new StringBuilder(), icuDataFiles);
313             }
314             if (sepIndex < 0) {
315                 break;
316             }
317             pathStart = sepIndex + 1;
318         }
319     }
320 
321     private static void addDataFilesFromFolder(File folder, StringBuilder itemPath,
322             List<DataFile> dataFiles) {
323         File[] files = folder.listFiles();
324         if (files == null || files.length == 0) {
325             return;
326         }
327         int folderPathLength = itemPath.length();
328         if (folderPathLength > 0) {
329             // The item path must use the ICU file separator character,
330             // not the platform-dependent File.separatorChar,
331             // so that the enumerated item paths match the paths requested by ICU code.
332             itemPath.append('/');
333             ++folderPathLength;
334         }
335         for (File file : files) {
336             String fileName = file.getName();
337             if (fileName.endsWith(".txt")) {
338                 continue;
339             }
340             itemPath.append(fileName);
341             if (file.isDirectory()) {
342                 // TODO: Within a folder, put all single files before all .dat packages?
343                 addDataFilesFromFolder(file, itemPath, dataFiles);
344             } else if (fileName.endsWith(".dat")) {
345                 ByteBuffer pkgBytes = mapFile(file);
346                 if (pkgBytes != null && DatPackageReader.validate(pkgBytes)) {
347                     dataFiles.add(new PackageDataFile(itemPath.toString(), pkgBytes));
348                 }
349             } else {
350                 dataFiles.add(new SingleDataFile(itemPath.toString(), file));
351             }
352             itemPath.setLength(folderPathLength);
353         }
354     }
355 
356     /**
357      * Compares the length-specified input key with the
358      * NUL-terminated table key. (ASCII)
359      */
360     static int compareKeys(CharSequence key, ByteBuffer bytes, int offset) {
361         for (int i = 0;; ++i, ++offset) {
362             int c2 = bytes.get(offset);
363             if (c2 == 0) {
364                 if (i == key.length()) {
365                     return 0;
366                 } else {
367                     return 1;  // key > table key because key is longer.
368                 }
369             } else if (i == key.length()) {
370                 return -1;  // key < table key because key is shorter.
371             }
372             int diff = (int)key.charAt(i) - c2;
373             if (diff != 0) {
374                 return diff;
375             }
376         }
377     }
378 
379     static int compareKeys(CharSequence key, byte[] bytes, int offset) {
380         for (int i = 0;; ++i, ++offset) {
381             int c2 = bytes[offset];
382             if (c2 == 0) {
383                 if (i == key.length()) {
384                     return 0;
385                 } else {
386                     return 1;  // key > table key because key is longer.
387                 }
388             } else if (i == key.length()) {
389                 return -1;  // key < table key because key is shorter.
390             }
391             int diff = (int)key.charAt(i) - c2;
392             if (diff != 0) {
393                 return diff;
394             }
395         }
396     }
397 
398     // public inner interface ------------------------------------------------
399 
400     /**
401      * Special interface for data authentication
402      */
403     public static interface Authenticate
404     {
405         /**
406          * Method used in ICUBinary.readHeader() to provide data format
407          * authentication.
408          * @param version version of the current data
409          * @return true if dataformat is an acceptable version, false otherwise
410          */
411         public boolean isDataVersionAcceptable(byte version[]);
412     }
413 
414     // public methods --------------------------------------------------------
415 
416     /**
417      * Loads an ICU binary data file and returns it as a ByteBuffer.
418      * The buffer contents is normally read-only, but its position etc. can be modified.
419      *
420      * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu".
421      * @return The data as a read-only ByteBuffer,
422      *         or null if the resource could not be found.
423      */
424     public static ByteBuffer getData(String itemPath) {
425         return getData(null, null, itemPath, false);
426     }
427 
428     /**
429      * Loads an ICU binary data file and returns it as a ByteBuffer.
430      * The buffer contents is normally read-only, but its position etc. can be modified.
431      *
432      * @param loader Used for loader.getResourceAsStream() unless the data is found elsewhere.
433      * @param resourceName Resource name for use with the loader.
434      * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu".
435      * @return The data as a read-only ByteBuffer,
436      *         or null if the resource could not be found.
437      */
438     public static ByteBuffer getData(ClassLoader loader, String resourceName, String itemPath) {
439         return getData(loader, resourceName, itemPath, false);
440     }
441 
442     /**
443      * Loads an ICU binary data file and returns it as a ByteBuffer.
444      * The buffer contents is normally read-only, but its position etc. can be modified.
445      *
446      * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu".
447      * @return The data as a read-only ByteBuffer.
448      * @throws MissingResourceException if required==true and the resource could not be found
449      */
450     public static ByteBuffer getRequiredData(String itemPath) {
451         return getData(null, null, itemPath, true);
452     }
453 
454     /**
455      * Loads an ICU binary data file and returns it as a ByteBuffer.
456      * The buffer contents is normally read-only, but its position etc. can be modified.
457      *
458      * @param loader Used for loader.getResourceAsStream() unless the data is found elsewhere.
459      * @param resourceName Resource name for use with the loader.
460      * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu".
461      * @return The data as a read-only ByteBuffer.
462      * @throws MissingResourceException if required==true and the resource could not be found
463      */
464 //    public static ByteBuffer getRequiredData(ClassLoader loader, String resourceName,
465 //            String itemPath) {
466 //        return getData(loader, resourceName, itemPath, true);
467 //    }
468 
469     /**
470      * Loads an ICU binary data file and returns it as a ByteBuffer.
471      * The buffer contents is normally read-only, but its position etc. can be modified.
472      *
473      * @param loader Used for loader.getResourceAsStream() unless the data is found elsewhere.
474      * @param resourceName Resource name for use with the loader.
475      * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu".
476      * @param required If the resource cannot be found,
477      *        this method returns null (!required) or throws an exception (required).
478      * @return The data as a read-only ByteBuffer,
479      *         or null if required==false and the resource could not be found.
480      * @throws MissingResourceException if required==true and the resource could not be found
481      */
482     private static ByteBuffer getData(ClassLoader loader, String resourceName,
483             String itemPath, boolean required) {
484         ByteBuffer bytes = getDataFromFile(itemPath);
485         if (bytes != null) {
486             return bytes;
487         }
488         if (loader == null) {
489             loader = ClassLoaderUtil.getClassLoader(ICUData.class);
490         }
491         if (resourceName == null) {
492             resourceName = ICUData.ICU_BASE_NAME + '/' + itemPath;
493         }
494         ByteBuffer buffer = null;
495         try {
496             @SuppressWarnings("resource")  // Closed by getByteBufferFromInputStreamAndCloseStream().
497             InputStream is = ICUData.getStream(loader, resourceName, required);
498             if (is == null) {
499                 return null;
500             }
501             buffer = getByteBufferFromInputStreamAndCloseStream(is);
502         } catch (IOException e) {
503             throw new ICUUncheckedIOException(e);
504         }
505         return buffer;
506     }
507 
508     private static ByteBuffer getDataFromFile(String itemPath) {
509         for (DataFile dataFile : icuDataFiles) {
510             ByteBuffer data = dataFile.getData(itemPath);
511             if (data != null) {
512                 return data;
513             }
514         }
515         return null;
516     }
517 
518     @SuppressWarnings("resource")  // Closing a file closes its channel.
519     private static ByteBuffer mapFile(File path) {
520         FileInputStream file;
521         try {
522             file = new FileInputStream(path);
523             FileChannel channel = file.getChannel();
524             ByteBuffer bytes = null;
525             try {
526                 bytes = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
527             } finally {
528                 file.close();
529             }
530             return bytes;
531         } catch (FileNotFoundException ignored) {
532             System.err.println(ignored);
533         } catch (IOException ignored) {
534             System.err.println(ignored);
535         }
536         return null;
537     }
538 
539     /**
540      * @param folder The relative ICU data folder, like "" or "coll".
541      * @param suffix Usually ".res".
542      * @param names File base names relative to the folder are added without the suffix,
543      *        for example "de_CH".
544      */
545     public static void addBaseNamesInFileFolder(String folder, String suffix, Set<String> names) {
546         for (DataFile dataFile : icuDataFiles) {
547             dataFile.addBaseNamesInFolder(folder, suffix, names);
548         }
549     }
550 
551     /**
552      * Same as readHeader(), but returns a VersionInfo rather than a compact int.
553      */
554     public static VersionInfo readHeaderAndDataVersion(ByteBuffer bytes,
555                                                              int dataFormat,
556                                                              Authenticate authenticate)
557                                                                 throws IOException {
558         return getVersionInfoFromCompactInt(readHeader(bytes, dataFormat, authenticate));
559     }
560 
561     /**
562      * Reads an ICU data header, checks the data format, and returns the data version.
563      *
564      * <p>Assumes that the ByteBuffer position is 0 on input.
565      * The buffer byte order is set according to the data.
566      * The buffer position is advanced past the header (including UDataInfo and comment).
567      *
568      * <p>See C++ ucmndata.h and unicode/udata.h.
569      *
570      * @return dataVersion
571      * @throws IOException if this is not a valid ICU data item of the expected dataFormat
572      */
573     public static int readHeader(ByteBuffer bytes, int dataFormat, Authenticate authenticate)
574             throws IOException {
575         assert bytes.position() == 0;
576         byte magic1 = bytes.get(2);
577         byte magic2 = bytes.get(3);
578         if (magic1 != MAGIC1 || magic2 != MAGIC2) {
579             throw new IOException(MAGIC_NUMBER_AUTHENTICATION_FAILED_);
580         }
581 
582         byte isBigEndian = bytes.get(8);
583         byte charsetFamily = bytes.get(9);
584         byte sizeofUChar = bytes.get(10);
585         if (isBigEndian < 0 || 1 < isBigEndian ||
586                 charsetFamily != CHAR_SET_ || sizeofUChar != CHAR_SIZE_) {
587             throw new IOException(HEADER_AUTHENTICATION_FAILED_);
588         }
589         bytes.order(isBigEndian != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
590 
591         int headerSize = bytes.getChar(0);
592         int sizeofUDataInfo = bytes.getChar(4);
593         if (sizeofUDataInfo < 20 || headerSize < (sizeofUDataInfo + 4)) {
594             throw new IOException("Internal Error: Header size error");
595         }
596         // TODO: Change Authenticate to take int major, int minor, int milli, int micro
597         // to avoid array allocation.
598         byte[] formatVersion = new byte[] {
599             bytes.get(16), bytes.get(17), bytes.get(18), bytes.get(19)
600         };
601         if (bytes.get(12) != (byte)(dataFormat >> 24) ||
602                 bytes.get(13) != (byte)(dataFormat >> 16) ||
603                 bytes.get(14) != (byte)(dataFormat >> 8) ||
604                 bytes.get(15) != (byte)dataFormat ||
605                 (authenticate != null && !authenticate.isDataVersionAcceptable(formatVersion))) {
606             throw new IOException(HEADER_AUTHENTICATION_FAILED_ +
607                     String.format("; data format %02x%02x%02x%02x, format version %d.%d.%d.%d",
608                             bytes.get(12), bytes.get(13), bytes.get(14), bytes.get(15),
609                             formatVersion[0] & 0xff, formatVersion[1] & 0xff,
610                             formatVersion[2] & 0xff, formatVersion[3] & 0xff));
611         }
612 
613         bytes.position(headerSize);
614         return  // dataVersion
615                 ((int)bytes.get(20) << 24) |
616                 ((bytes.get(21) & 0xff) << 16) |
617                 ((bytes.get(22) & 0xff) << 8) |
618                 (bytes.get(23) & 0xff);
619     }
620 
621     /**
622      * Writes an ICU data header.
623      * Does not write a copyright string.
624      *
625      * @return The length of the header (number of bytes written).
626      * @throws IOException from the DataOutputStream
627      */
628     public static int writeHeader(int dataFormat, int formatVersion, int dataVersion,
629             DataOutputStream dos) throws IOException {
630         // ucmndata.h MappedData
631         dos.writeChar(32);  // headerSize
632         dos.writeByte(MAGIC1);
633         dos.writeByte(MAGIC2);
634         // unicode/udata.h UDataInfo
635         dos.writeChar(20);  // sizeof(UDataInfo)
636         dos.writeChar(0);  // reservedWord
637         dos.writeByte(1);  // isBigEndian
638         dos.writeByte(CHAR_SET_);  // charsetFamily
639         dos.writeByte(CHAR_SIZE_);  // sizeofUChar
640         dos.writeByte(0);  // reservedByte
641         dos.writeInt(dataFormat);
642         dos.writeInt(formatVersion);
643         dos.writeInt(dataVersion);
644         // 8 bytes padding for 32 bytes headerSize (multiple of 16).
645         dos.writeLong(0);
646         assert dos.size() == 32;
647         return 32;
648     }
649 
650     public static void skipBytes(ByteBuffer bytes, int skipLength) {
651         if (skipLength > 0) {
652             bytes.position(bytes.position() + skipLength);
653         }
654     }
655 
656     public static String getString(ByteBuffer bytes, int length, int additionalSkipLength) {
657         CharSequence cs = bytes.asCharBuffer();
658         String s = cs.subSequence(0, length).toString();
659         skipBytes(bytes, length * 2 + additionalSkipLength);
660         return s;
661     }
662 
663     public static char[] getChars(ByteBuffer bytes, int length, int additionalSkipLength) {
664         char[] dest = new char[length];
665         bytes.asCharBuffer().get(dest);
666         skipBytes(bytes, length * 2 + additionalSkipLength);
667         return dest;
668     }
669 
670     public static short[] getShorts(ByteBuffer bytes, int length, int additionalSkipLength) {
671         short[] dest = new short[length];
672         bytes.asShortBuffer().get(dest);
673         skipBytes(bytes, length * 2 + additionalSkipLength);
674         return dest;
675     }
676 
677     public static int[] getInts(ByteBuffer bytes, int length, int additionalSkipLength) {
678         int[] dest = new int[length];
679         bytes.asIntBuffer().get(dest);
680         skipBytes(bytes, length * 4 + additionalSkipLength);
681         return dest;
682     }
683 
684     public static long[] getLongs(ByteBuffer bytes, int length, int additionalSkipLength) {
685         long[] dest = new long[length];
686         bytes.asLongBuffer().get(dest);
687         skipBytes(bytes, length * 8 + additionalSkipLength);
688         return dest;
689     }
690 
691     /**
692      * Same as ByteBuffer.slice() plus preserving the byte order.
693      */
694     public static ByteBuffer sliceWithOrder(ByteBuffer bytes) {
695         ByteBuffer b = bytes.slice();
696         return b.order(bytes.order());
697     }
698 
699     /**
700      * Reads the entire contents from the stream into a byte array
701      * and wraps it into a ByteBuffer. Closes the InputStream at the end.
702      */
703     public static ByteBuffer getByteBufferFromInputStreamAndCloseStream(InputStream is) throws IOException {
704         try {
705             // is.available() may return 0, or 1, or the total number of bytes in the stream,
706             // or some other number.
707             // Do not try to use is.available() == 0 to find the end of the stream!
708             byte[] bytes;
709             int avail = is.available();
710             if (avail > 32) {
711                 // There are more bytes available than just the ICU data header length.
712                 // With luck, it is the total number of bytes.
713                 bytes = new byte[avail];
714             } else {
715                 bytes = new byte[128];  // empty .res files are even smaller
716             }
717             // Call is.read(...) until one returns a negative value.
718             int length = 0;
719             for(;;) {
720                 if (length < bytes.length) {
721                     int numRead = is.read(bytes, length, bytes.length - length);
722                     if (numRead < 0) {
723                         break;  // end of stream
724                     }
725                     length += numRead;
726                 } else {
727                     // See if we are at the end of the stream before we grow the array.
728                     int nextByte = is.read();
729                     if (nextByte < 0) {
730                         break;
731                     }
732                     int capacity = 2 * bytes.length;
733                     if (capacity < 128) {
734                         capacity = 128;
735                     } else if (capacity < 0x4000) {
736                         capacity *= 2;  // Grow faster until we reach 16kB.
737                     }
738                     // TODO Java 6 replace new byte[] and arraycopy(): bytes = Arrays.copyOf(bytes, capacity);
739                     byte[] newBytes = new byte[capacity];
740                     System.arraycopy(bytes, 0, newBytes, 0, length);
741                     bytes = newBytes;
742                     bytes[length++] = (byte) nextByte;
743                 }
744             }
745             return ByteBuffer.wrap(bytes, 0, length);
746         } finally {
747             is.close();
748         }
749     }
750 
751     /**
752      * Returns a VersionInfo for the bytes in the compact version integer.
753      */
754     public static VersionInfo getVersionInfoFromCompactInt(int version) {
755         return VersionInfo.getInstance(
756                 version >>> 24, (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff);
757     }
758 
759     /**
760      * Returns an array of the bytes in the compact version integer.
761      */
762     public static byte[] getVersionByteArrayFromCompactInt(int version) {
763         return new byte[] {
764                 (byte)(version >> 24),
765                 (byte)(version >> 16),
766                 (byte)(version >> 8),
767                 (byte)(version)
768         };
769     }
770 
771     // private variables -------------------------------------------------
772 
773     /**
774     * Magic numbers to authenticate the data file
775     */
776     private static final byte MAGIC1 = (byte)0xda;
777     private static final byte MAGIC2 = (byte)0x27;
778 
779     /**
780     * File format authentication values
781     */
782     private static final byte CHAR_SET_ = 0;
783     private static final byte CHAR_SIZE_ = 2;
784 
785     /**
786     * Error messages
787     */
788     private static final String MAGIC_NUMBER_AUTHENTICATION_FAILED_ =
789                        "ICU data file error: Not an ICU data file";
790     private static final String HEADER_AUTHENTICATION_FAILED_ =
791         "ICU data file error: Header authentication failed, please check if you have a valid ICU data file";
792 }
793