• 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#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
isDataVersionAcceptable(byte version[])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          */
validate(ByteBuffer bytes)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 
startsWithPackageName(ByteBuffer bytes, int start)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 
getData(ByteBuffer bytes, CharSequence key)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 
addBaseNamesInFolder(ByteBuffer bytes, String folder, String suffix, Set<String> names)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 
binarySearch(ByteBuffer bytes, CharSequence key)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 
getNameOffset(ByteBuffer bytes, int index)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