name.martingeisse.api.i18n.ClassHierarchyLocalizationContext.java Source code

Java tutorial

Introduction

Here is the source code for name.martingeisse.api.i18n.ClassHierarchyLocalizationContext.java

Source

/**
 * Copyright (c) 2010 Martin Geisse
 *
 * This file is distributed under the terms of the MIT license.
 */

package name.martingeisse.api.i18n;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import name.martingeisse.common.util.ParameterUtil;

import org.apache.commons.collections.MapUtils;
import org.apache.log4j.Logger;

/**
 * The is the most common localization context. It loops through the classes,
 * starting with a specific class and stopping with {@link Object}. For each
 * class in the hierarchy, it looks for a property file named
 * "classname_locale.properties". For example, for the class {@link String}
 * and locale en_US it would look at
 * 
 *       java/lang/String_en_US.properties
 *       java/lang/Object_en_US.properties
 *
 */
public final class ClassHierarchyLocalizationContext implements ILocalizationContext {

    /**
     * the logger
     */
    @SuppressWarnings("unused")
    private static Logger logger = Logger.getLogger(ClassHierarchyLocalizationContext.class);

    /**
     * the cachedProperties
     */
    private static final ConcurrentHashMap<Class<?>, ILocalizationContext> cachedInstances = new ConcurrentHashMap<Class<?>, ILocalizationContext>();

    // static initializer
    static {

        // this saves ourselves a HashMap read for every localized property
        cachedInstances.put(Object.class, new EmptyLocalizationContext());

    }

    /**
     * Returns the instance of this class for the specified origin class.
     * 
     * Note that synchronization in this method allows two independent callers
     * to create an instance of this class. We avoid using both by calling
     * get() on the map in the final step, so the only effect is that one
     * instance is created in vain (all instances have equal content).
     * 
     * @param c the class
     * @return the instance of this class
     */
    public static ILocalizationContext getForClass(Class<?> c) {
        if (c == null) {
            return null;
        }
        ILocalizationContext instance = cachedInstances.get(c);
        if (instance != null) {
            return instance;
        }
        instance = new ClassHierarchyLocalizationContext(c);
        cachedInstances.putIfAbsent(c, instance);
        return cachedInstances.get(c);
    }

    /**
     * the origin
     */
    private final Class<?> origin;

    /**
     * the parentClassContext
     */
    private final ILocalizationContext parentClassContext;

    /**
     * the localizationProperties
     */
    private final HashMap<Locale, Map<String, String>> localizationProperties;

    /**
     * Constructor.
     * @param origin the class to start with
     */
    public ClassHierarchyLocalizationContext(Class<?> origin) {
        this.origin = ParameterUtil.ensureNotNull(origin, "origin");
        this.parentClassContext = getForClass(origin.getSuperclass());
        this.localizationProperties = new HashMap<Locale, Map<String, String>>();
    }

    /* (non-Javadoc)
     * @see name.martingeisse.api.i18n.ILocalizationContext#getLocalizationProperty(java.lang.String, java.util.Locale)
     */
    @Override
    public String getLocalizationProperty(String key, Locale locale) {
        String property = getOriginLocalizationProperty(key, locale);
        if (property != null) {
            return property;
        } else if (parentClassContext != null) {
            return parentClassContext.getLocalizationProperty(key, locale);
        } else {
            return null;
        }
    }

    /**
     * Returns the localization property only for the origin class,
     * not taking base classes into account.
     */
    private String getOriginLocalizationProperty(String key, Locale locale) {
        Map<String, String> properties;

        // look for cached properties
        properties = localizationProperties.get(locale);
        if (properties != null) {
            return properties.get(key);
        }

        // try to load a .properties file, using EMPTY_MAP as a fallback if none exists
        properties = loadProperties(origin, locale);
        localizationProperties.put(locale, properties);
        return properties.get(key);

    }

    /**
     * Loads the .properties file for the origin class and the specified locale and returns
     * either its contents as a map, or an empty map if the file cannot be found.
     * 
     * This method is static to emphasize that it does not use or affect the calling instance
     * in any other way.
     * 
     * @param origin the origin class
     * @param locale the locale
     * @return the properties
     */
    private static Map<String, String> loadProperties(Class<?> origin, Locale locale) {
        String filename = origin.getSimpleName() + '_' + locale + ".properties";
        try (InputStream inputStream = origin.getResourceAsStream(filename)) {
            if (inputStream == null) {
                @SuppressWarnings("unchecked")
                Map<String, String> emptyMap = MapUtils.EMPTY_MAP;
                return emptyMap;
            }
            Properties properties = new Properties();
            properties.load(inputStream);
            @SuppressWarnings("unchecked")
            Map<String, String> typedProperties = (Map<String, String>) (Map<?, ?>) properties;
            return typedProperties;
        } catch (IOException e) {
            logger.error("could not load i18n property file", e);
            return null;
        }
    }

}