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) 2004-2014, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 *******************************************************************************
8 *
9 * Created on Feb 4, 2004
10 *
11 */
12package com.ibm.icu.impl;
13
14import java.io.IOException;
15import java.io.InputStream;
16import java.net.URL;
17import java.security.AccessController;
18import java.security.PrivilegedAction;
19import java.util.MissingResourceException;
20import java.util.logging.Logger;
21
22import com.ibm.icu.util.VersionInfo;
23
24/**
25 * Provides access to ICU data files as InputStreams.  Implements security checking.
26 */
27public final class ICUData {
28    /**
29     * The data path to be used with getBundleInstance API
30     */
31    static final String ICU_DATA_PATH = "com/ibm/icu/impl/";
32    /**
33     * The ICU data package name.
34     * This is normally the name of the .dat package, and the prefix (plus '/')
35     * of the package entry names.
36     */
37    static final String PACKAGE_NAME = "icudt" + VersionInfo.ICU_DATA_VERSION_PATH;
38    /**
39     * The data path to be used with Class.getResourceAsStream().
40     */
41    public static final String ICU_BUNDLE = "data/" + PACKAGE_NAME;
42
43    /**
44     * The base name of ICU data to be used with ClassLoader.getResourceAsStream(),
45     * ICUResourceBundle.getBundleInstance() etc.
46     */
47    public static final String ICU_BASE_NAME = ICU_DATA_PATH + ICU_BUNDLE;
48
49    /**
50     * The base name of collation data to be used with getBundleInstance API
51     */
52    public static final String ICU_COLLATION_BASE_NAME = ICU_BASE_NAME + "/coll";
53
54    /**
55     * The base name of rbbi data to be used with getData API
56     */
57    public static final String ICU_BRKITR_NAME = "brkitr";
58
59    /**
60     * The base name of rbbi data to be used with getBundleInstance API
61     */
62    public static final String ICU_BRKITR_BASE_NAME = ICU_BASE_NAME + '/' + ICU_BRKITR_NAME;
63
64    /**
65     * The base name of rbnf data to be used with getBundleInstance API
66     */
67    public static final String ICU_RBNF_BASE_NAME = ICU_BASE_NAME + "/rbnf";
68
69    /**
70     * The base name of transliterator data to be used with getBundleInstance API
71     */
72    public static final String ICU_TRANSLIT_BASE_NAME = ICU_BASE_NAME + "/translit";
73
74    public static final String ICU_LANG_BASE_NAME = ICU_BASE_NAME + "/lang";
75    public static final String ICU_CURR_BASE_NAME = ICU_BASE_NAME + "/curr";
76    public static final String ICU_REGION_BASE_NAME = ICU_BASE_NAME + "/region";
77    public static final String ICU_ZONE_BASE_NAME = ICU_BASE_NAME + "/zone";
78    public static final String ICU_UNIT_BASE_NAME = ICU_BASE_NAME + "/unit";
79
80    /**
81     * For testing (otherwise false): When reading an InputStream from a Class or ClassLoader
82     * (that is, not from a file), log when the stream contains ICU binary data.
83     *
84     * This cannot be ICUConfig'ured because ICUConfig calls ICUData.getStream()
85     * to read the properties file, so we would get a circular dependency
86     * in the class initialization.
87     */
88    private static final boolean logBinaryDataFromInputStream = false;
89    private static final Logger logger = logBinaryDataFromInputStream ?
90            Logger.getLogger(ICUData.class.getName()) : null;
91
92    public static boolean exists(final String resourceName) {
93        URL i = null;
94        if (System.getSecurityManager() != null) {
95            i = AccessController.doPrivileged(new PrivilegedAction<URL>() {
96                    @Override
97                    public URL run() {
98                        return ICUData.class.getResource(resourceName);
99                    }
100                });
101        } else {
102            i = ICUData.class.getResource(resourceName);
103        }
104        return i != null;
105    }
106
107    private static InputStream getStream(final Class<?> root, final String resourceName, boolean required) {
108        InputStream i = null;
109        if (System.getSecurityManager() != null) {
110            i = AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
111                    @Override
112                    public InputStream run() {
113                        return root.getResourceAsStream(resourceName);
114                    }
115                });
116        } else {
117            i = root.getResourceAsStream(resourceName);
118        }
119
120        if (i == null && required) {
121            throw new MissingResourceException("could not locate data " +resourceName, root.getPackage().getName(), resourceName);
122        }
123        checkStreamForBinaryData(i, resourceName);
124        return i;
125    }
126
127    /**
128     * Should be called only from ICUBinary.getData() or from convenience overloads here.
129     */
130    static InputStream getStream(final ClassLoader loader, final String resourceName, boolean required) {
131        InputStream i = null;
132        if (System.getSecurityManager() != null) {
133            i = AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
134                    @Override
135                    public InputStream run() {
136                        return loader.getResourceAsStream(resourceName);
137                    }
138                });
139        } else {
140            i = loader.getResourceAsStream(resourceName);
141        }
142        if (i == null && required) {
143            throw new MissingResourceException("could not locate data", loader.toString(), resourceName);
144        }
145        checkStreamForBinaryData(i, resourceName);
146        return i;
147    }
148
149    @SuppressWarnings("unused")  // used if logBinaryDataFromInputStream == true
150    private static void checkStreamForBinaryData(InputStream is, String resourceName) {
151        if (logBinaryDataFromInputStream && is != null && resourceName.indexOf(PACKAGE_NAME) >= 0) {
152            try {
153                is.mark(32);
154                byte[] b = new byte[32];
155                int len = is.read(b);
156                if (len == 32 && b[2] == (byte)0xda && b[3] == 0x27) {
157                    String msg = String.format(
158                            "ICU binary data file loaded from Class/ClassLoader as InputStream " +
159                            "from %s: MappedData %02x%02x%02x%02x  dataFormat %02x%02x%02x%02x",
160                            resourceName,
161                            b[0], b[1], b[2], b[3],
162                            b[12], b[13], b[14], b[15]);
163                    logger.info(msg);
164                }
165                is.reset();
166            } catch (IOException ignored) {
167            }
168        }
169    }
170
171    public static InputStream getStream(ClassLoader loader, String resourceName){
172        return getStream(loader,resourceName, false);
173    }
174
175    public static InputStream getRequiredStream(ClassLoader loader, String resourceName){
176        return getStream(loader, resourceName, true);
177    }
178
179    /**
180     * Convenience override that calls getStream(ICUData.class, resourceName, false);
181     * Returns null if the resource could not be found.
182     */
183    public static InputStream getStream(String resourceName) {
184        return getStream(ICUData.class, resourceName, false);
185    }
186
187    /**
188     * Convenience method that calls getStream(ICUData.class, resourceName, true).
189     * @throws MissingResourceException if the resource could not be found
190     */
191    public static InputStream getRequiredStream(String resourceName) {
192        return getStream(ICUData.class, resourceName, true);
193    }
194
195    /**
196     * Convenience override that calls getStream(root, resourceName, false);
197     * Returns null if the resource could not be found.
198     */
199    public static InputStream getStream(Class<?> root, String resourceName) {
200        return getStream(root, resourceName, false);
201    }
202
203    /**
204     * Convenience method that calls getStream(root, resourceName, true).
205     * @throws MissingResourceException if the resource could not be found
206     */
207    public static InputStream getRequiredStream(Class<?> root, String resourceName) {
208        return getStream(root, resourceName, true);
209    }
210}
211