ICUData.java

// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
 *******************************************************************************
 * Copyright (C) 2004-2014, International Business Machines Corporation and
 * others. All Rights Reserved.
 *******************************************************************************
 *
 * Created on Feb 4, 2004
 *
 */
package com.ibm.icu.impl;

import com.ibm.icu.util.VersionInfo;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.MissingResourceException;
import java.util.logging.Logger;

/** Provides access to ICU data files as InputStreams. Implements security checking. */
public final class ICUData {
    /** The data path to be used with getBundleInstance API */
    static final String ICU_DATA_PATH = "com/ibm/icu/impl/";

    /**
     * The ICU data package name. This is normally the name of the .dat package, and the prefix
     * (plus '/') of the package entry names. Not used when loading from resources packaged in the
     * .jar.
     */
    static final String PACKAGE_NAME = "icudt" + VersionInfo.ICU_DATA_VERSION_PATH;

    /** The data path to be used with Class.getResourceAsStream(). */
    public static final String ICU_BUNDLE = "data/icudata";

    /**
     * The base name of ICU data to be used with ClassLoader.getResourceAsStream(),
     * ICUResourceBundle.getBundleInstance() etc.
     */
    public static final String ICU_BASE_NAME = ICU_DATA_PATH + ICU_BUNDLE;

    /** The base name of collation data to be used with getBundleInstance API */
    public static final String ICU_COLLATION_BASE_NAME = ICU_BASE_NAME + "/coll";

    /** The base name of rbbi data to be used with getData API */
    public static final String ICU_BRKITR_NAME = "brkitr";

    /** The base name of rbbi data to be used with getBundleInstance API */
    public static final String ICU_BRKITR_BASE_NAME = ICU_BASE_NAME + '/' + ICU_BRKITR_NAME;

    /** The base name of rbnf data to be used with getBundleInstance API */
    public static final String ICU_RBNF_BASE_NAME = ICU_BASE_NAME + "/rbnf";

    /** The base name of transliterator data to be used with getBundleInstance API */
    public static final String ICU_TRANSLIT_BASE_NAME = ICU_BASE_NAME + "/translit";

    public static final String ICU_LANG_BASE_NAME = ICU_BASE_NAME + "/lang";
    public static final String ICU_CURR_BASE_NAME = ICU_BASE_NAME + "/curr";
    public static final String ICU_REGION_BASE_NAME = ICU_BASE_NAME + "/region";
    public static final String ICU_ZONE_BASE_NAME = ICU_BASE_NAME + "/zone";
    public static final String ICU_UNIT_BASE_NAME = ICU_BASE_NAME + "/unit";

    /**
     * For testing (otherwise false): When reading an InputStream from a Class or ClassLoader (that
     * is, not from a file), log when the stream contains ICU binary data.
     *
     * <p>This cannot be ICUConfig'ured because ICUConfig calls ICUData.getStream() to read the
     * properties file, so we would get a circular dependency in the class initialization.
     */
    private static final boolean logBinaryDataFromInputStream = false;

    private static final Logger logger =
            logBinaryDataFromInputStream ? Logger.getLogger(ICUData.class.getName()) : null;
    private static final Logger tracingLogger =
            ICUDebug.enabled("dataTracing")
                    ? Logger.getLogger(ICUData.class.getName() + ".tracing")
                    : null;

    public static boolean exists(final String resourceName) {
        URL i = null;
        if (System.getSecurityManager() != null) {
            i =
                    AccessController.doPrivileged(
                            new PrivilegedAction<URL>() {
                                @Override
                                public URL run() {
                                    return ICUData.class.getResource(resourceName);
                                }
                            });
        } else {
            i = ICUData.class.getResource(resourceName);
        }
        return i != null;
    }

    private static InputStream getStream(
            final Class<?> root, final String resourceName, boolean required) {
        InputStream i = null;
        if (System.getSecurityManager() != null) {
            i =
                    AccessController.doPrivileged(
                            new PrivilegedAction<InputStream>() {
                                @Override
                                public InputStream run() {
                                    return root.getResourceAsStream(resourceName);
                                }
                            });
        } else {
            i = root.getResourceAsStream(resourceName);
        }

        if (i == null && required) {
            throw new MissingResourceException(
                    "could not locate data " + resourceName,
                    root.getPackage().getName(),
                    resourceName);
        }
        checkStreamForBinaryData(i, resourceName);
        return i;
    }

    /** Should be called only from ICUBinary.getData() or from convenience overloads here. */
    static InputStream getStream(
            final ClassLoader loader, final String resourceName, boolean required) {
        if (tracingLogger != null) {
            tracingLogger.info(
                    "Using resource file "
                            + resourceName
                            + (required ? " (required)" : " (optional)"));
        }

        InputStream i = null;
        if (System.getSecurityManager() != null) {
            i =
                    AccessController.doPrivileged(
                            new PrivilegedAction<InputStream>() {
                                @Override
                                public InputStream run() {
                                    return loader.getResourceAsStream(resourceName);
                                }
                            });
        } else {
            i = loader.getResourceAsStream(resourceName);
        }
        if (i == null && required) {
            throw new MissingResourceException(
                    "could not locate data", loader.toString(), resourceName);
        }
        checkStreamForBinaryData(i, resourceName);
        return i;
    }

    @SuppressWarnings("unused") // used if logBinaryDataFromInputStream == true
    private static void checkStreamForBinaryData(InputStream is, String resourceName) {
        if (logBinaryDataFromInputStream && is != null && resourceName.indexOf(PACKAGE_NAME) >= 0) {
            try {
                is.mark(32);
                byte[] b = new byte[32];
                int len = is.read(b);
                if (len == 32 && b[2] == (byte) 0xda && b[3] == 0x27) {
                    String msg =
                            String.format(
                                    "ICU binary data file loaded from Class/ClassLoader as InputStream "
                                            + "from %s: MappedData %02x%02x%02x%02x  dataFormat %02x%02x%02x%02x",
                                    resourceName,
                                    b[0],
                                    b[1],
                                    b[2],
                                    b[3],
                                    b[12],
                                    b[13],
                                    b[14],
                                    b[15]);
                    logger.info(msg);
                }
                is.reset();
            } catch (IOException ignored) {
            }
        }
    }

    public static InputStream getStream(ClassLoader loader, String resourceName) {
        return getStream(loader, resourceName, false);
    }

    public static InputStream getRequiredStream(ClassLoader loader, String resourceName) {
        return getStream(loader, resourceName, true);
    }

    /**
     * Convenience override that calls getStream(ICUData.class, resourceName, false); Returns null
     * if the resource could not be found.
     */
    public static InputStream getStream(String resourceName) {
        return getStream(ICUData.class, resourceName, false);
    }

    /**
     * Convenience method that calls getStream(ICUData.class, resourceName, true).
     *
     * @throws MissingResourceException if the resource could not be found
     */
    public static InputStream getRequiredStream(String resourceName) {
        return getStream(ICUData.class, resourceName, true);
    }

    /**
     * Convenience override that calls getStream(root, resourceName, false); Returns null if the
     * resource could not be found.
     */
    public static InputStream getStream(Class<?> root, String resourceName) {
        return getStream(root, resourceName, false);
    }

    /**
     * Convenience method that calls getStream(root, resourceName, true).
     *
     * @throws MissingResourceException if the resource could not be found
     */
    public static InputStream getRequiredStream(Class<?> root, String resourceName) {
        return getStream(root, resourceName, true);
    }
}