components/visual-panels/core/src/java/util/com/oracle/solaris/vp/util/misc/finder/MessageFinder.java
author Dan Labrecque <dan.labrecque@oracle.com>
Thu, 24 May 2012 04:16:47 -0400
changeset 827 0944d8c0158b
permissions -rw-r--r--
7169052 Integrate Visual Panels into Userland

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
 */

package com.oracle.solaris.vp.util.misc.finder;

import java.text.MessageFormat;
import java.util.*;
import com.oracle.solaris.vp.util.misc.TextUtil;

public class MessageFinder extends ItemFinder {
    //
    // Static data
    //

    /**
     * The default base name suffix ("{@code resources.Resources}") to append to
     * package names when searching for a {@code ResourceBundle}.
     */
    public static final String DEFAULT_BASE_NAME_SUFFIX = "resources.Resources";

    //
    // Instance data
    //

    // Additional paths to search for resource strings
    private List<String> addlPaths = new ArrayList<String>();
    private List<String> roAddlPaths = Collections.unmodifiableList(addlPaths);

    private String baseNameSuffix = DEFAULT_BASE_NAME_SUFFIX;
    private Locale locale;

    //
    // MessageFinder methods
    //

    /**
     * Add a base path to search for {@code ResourceBundle}s.
     *
     * @param	    path
     *		    a base path suitable for construction of a
     *		    {@code ResourceBundle}
     */
    public void addSearchPath(String path) {
	synchronized (addlPaths) {
	    if (addlPaths.indexOf(path) == -1) {
		addlPaths.add(path);
	    }
	}
    }

    /**
     * Gets the additional paths to search for {@code ResourceBundle}s.
     *
     * @return	    an unmodifiable {@code List}
     */
    public List<String> getSearchPaths() {
	return roAddlPaths;
    }

    /**
     * Get the base name suffix that is appended to package names when searching
     * for a {@code ResourceBundle}.
     */
    public String getBaseNameSuffix() {
	return baseNameSuffix;
    }

    /**
     * Gets the {@code Locale} used when retrieving {@code ResourceBundle}s.
     * The default value is the {@code Locale} returned by {@code
     * Locale.getDefault()}.
     */
    public Locale getLocale() {
	return locale == null ? Locale.getDefault() : locale;
    }

    /**
     * Return a parameterized localized string for the given key.  See the class
     * notes for details on the search algorithm.
     *
     * @param	    loader
     *		    the {@code ClassLoader} to load the resource, or {@code
     *		    null} to use the system {@code ClassLoader}
     *
     * @param	    pkg
     *		    a point in the package hierarchy to search for resources, or
     *		    {@code null} to search only from the top
     *
     * @param	    key
     *		    the identifying key in the {@code ResourceBundle}, which may
     *		    contain an optional numerical suffix (.[0-9]+) that will be
     *		    stripped off to form a backup key in case this key is not
     *		    found
     *
     * @param	    args
     *		    parameters to place into the localized string, per
     *		    the {@code java.text.MessageFormat} class.
     *
     * @return	    a parameterized localized string for the given key, or
     *		    {@code null} if no such resource is found
     */
    public String getString(ClassLoader loader, String pkg, String key,
	Object... args) {

	Locale locale = getLocale();
	String suffix = getBaseNameSuffix();

	List<String> paths = getSearchPathsFromPackage(pkg);
	paths.addAll(getSearchPaths());

	// If key ends with a numerical element, strip it off to form a backup
	// key in case this key doesn't match
	List<String> keys = new ArrayList<String>(2);
	keys.add(key);
	String altKey = key.replaceFirst("\\.\\d+$", "");
	if (!altKey.equals(key)) {
	    keys.add(altKey);
	}

	for (String path : paths) {
	    String bundleName = path.isEmpty() ? suffix : (path + "." + suffix);

	    try {
		ResourceBundle bundle = ResourceBundle.getBundle(
		    bundleName, locale, loader);

		for (String k : keys) {
		    try {
			// Throws a MissingResourceException
			String value = bundle.getString(k);

			String[] groups = TextUtil.match(value,
			    "^@forward:(((\\w+\\.)*\\w+):)?((\\w+\\.)*\\w+)");
			if (groups != null) {
			    String fPkg = groups[2] == null ? pkg : groups[2];
			    String fKey = groups[4];
			    return getString(loader, fPkg, fKey, args);
			}

			if (args != null) {
			    value = MessageFormat.format(value, args);
			}
			return value;

		    } catch (MissingResourceException ignore) {
		    }
		}

	    // Thrown by getBundle
	    } catch (MissingResourceException ignore) {
	    }
	}

	// Track down missing/bad resources during development;
	// vpanels.debug.messages is a system property containing a regular
	// expression matching all keys that should NOT be reported when no
	// message is found.
	String regex = System.getProperty("vpanels.debug.messages");
	if (regex != null && !key.matches(regex)) {
	    System.err.printf("Returning null for key: %s\n", key);
	    Thread.currentThread().dumpStack();
	}

	return null;
    }

    /**
     * Return a parameterized localized string for the given key.  See the class
     * notes for details on the search algorithm.
     *
     * @param	    pkg
     *		    a point in the package hierarchy to search for resources
     *
     * @param	    key
     *		    the identifying key in the {@code ResourceBundle}
     *
     * @param	    args
     *		    parameters to place into the localized string, per
     *		    the {@code java.text.MessageFormat} class.
     *
     * @return	    a parameterized localized string for the given key, or
     *		    {@code null} if no such resource is found
     */
    public String getStringFromPackage(String pkg, String key, Object... args) {
	return getString(getCallerClass().getClassLoader(), pkg, key, args);
    }

    /**
     * Return a parameterized localized string for the given key.  All
     * {@code ResourceBundle}s of the form {@code resources/Resources} in the
     * package hierarchy above the calling class are searched.
     *
     * @param	    key
     *		    the identifying key in the {@code ResourceBundle}
     *
     * @param	    args
     *		    parameters to place into the localized string, per
     *		    the {@code java.text.MessageFormat} class.
     *
     * @return	    a parameterized localized string for the given key, or
     *		    {@code null} if no such resource is found
     */
    public String getString(String key, Object... args) {
	Class caller = getCallerClass();
        return getString(caller.getClassLoader(), caller.getPackage().getName(),
	    key, args);
    }

    /**
     * Set the base name suffix to append to package names when searching for a
     * {@code ResourceBundle}.	The default value is identified by the {@link
     * #DEFAULT_BASE_NAME_SUFFIX DEFAULT_BASE_NAME_SUFFIX} static field.
     */
    public void setBaseNameSuffix(String baseNameSuffix) {
	this.baseNameSuffix = baseNameSuffix;
    }

    /**
     * Sets the {@code Locale} used when retrieving {@code ResourceBundle}s.
     *
     * @param	    locale
     *		    a {@code Locale} object, or {@code null} to use the
     *		    default {@code Locale}
     */
    public void setLocale(Locale locale) {
	this.locale = locale;
    }

    //
    // Private methods
    //

    private List<String> getSearchPathsFromPackage(String pkg) {
	if (pkg == null) {
	    pkg = "";
	}

	List<String> paths = new ArrayList<String>();
	while (!pkg.isEmpty()) {
	    paths.add(pkg);
	    pkg = TextUtil.getPackageName(pkg);
	}
	paths.add(pkg);

	return paths;
    }
}