/* * @(#)ResourceBundleUtil.java * * Copyright (c) 1996-2010 The authors and contributors of JHotDraw. * You may not use, copy or modify this file, except in compliance with the * accompanying license terms. */ package org.jhotdraw.util; import javax.annotation.Nullable; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.*; import javax.swing.*; import java.text.*; import java.net.*; /** * This is a convenience wrapper for accessing resources stored in a * ResourceBundle. * <p> * <b>Placeholders</b><br> * On top of the functionality provided by ResourceBundle, a property value * can include text from another property, by specifying the desired * property name and format type between <code>"${"</code> and <code>"}"</code>. * <p> * For example, if there is a {@code "imagedir"} property with the value * {@code "/org/jhotdraw/undo/images"}, then this could be used in an attribute * like this: <code>${imagedir}/editUndo.png</code>. This is resolved at run-time * as {@code /org/jhotdraw/undo/images/editUndo.png}. * <p> * Property names in placeholders can contain modifiers. Modifiers are written * between @code "[$"} and {@code "]"}. Each modifier has a fallback chain. * <p> * For example, if the property name modifier {@code "os"} has the value "win", * and its fallback chain is {@code "mac","default"}, then the property name * <code>${preferences.text.[$os]}</code> is first evaluted to {@code * preferences.text.win}, and - if no property with this name exists - * it is evaluated to {@code preferences.text.mac}, and then to * {@code preferences.text.default}. * <p> * The property name modifier "os" is defined by default. It can assume the * values "win", "mac" and "other". Its fallback chain is "default". * <p> * The format type can be optinally specified after a comma. * The following format types are supported: * <ul> * <li>{@code string} This is the default format.</li> * <li>{@code accelerator} This format replaces all occurences of the keywords * shift, control, ctrl, meta, alt, altGraph by properties which start with * {@code accelerator.}. For example, shift is replaced by {@code accelerator.shift}. * </li> * </ul> * * @author Werner Randelshofer * @version $Id$ */ public class ResourceBundleUtil implements Serializable { private static final long serialVersionUID = 1L; private static final HashSet<String> acceleratorKeys = new HashSet<String>( Arrays.asList(new String[]{ "shift", "control", "ctrl", "meta", "alt", "altGraph" })); /** The wrapped resource bundle. */ private transient ResourceBundle resource; /** The locale. */ private Locale locale; /** The base class */ private Class<?> baseClass = getClass(); /** * The base name of the resource bundle. */ private String baseName; /** * The global verbose property. */ private static boolean isVerbose = false; /** * The global map of property name modifiers. * The key of this map is the name of the property name modifier, * the value of this map is a fallback chain. */ private static HashMap<String, String[]> propertyNameModifiers = new HashMap<String, String[]>(); static { String osName = System.getProperty("os.name").toLowerCase(); String os; if (osName.startsWith("mac os x")) { os = "mac"; } else if (osName.startsWith("windows")) { os = "win"; } else { os = "other"; } propertyNameModifiers.put("os", new String[]{os, "default"}); } /** * Creates a new ResouceBundleUtil which wraps * the provided resource bundle. */ public ResourceBundleUtil(String baseName, Locale locale) { this.locale = locale; this.baseName = baseName; this.resource = ResourceBundle.getBundle(baseName, locale); } /** * Returns the wrapped resource bundle. * @return The wrapped resource bundle. */ public ResourceBundle getWrappedBundle() { return resource; } /** * Get a String from the ResourceBundle. * <br>Convenience method to save casting. * * @param key The key of the property. * @return The value of the property. Returns the key * if the property is missing. */ public String getString(String key) { try { String value = getStringRecursive(key); // System.out.println("ResourceBundleUtil "+baseName+" get("+key+"):"+value); return value; } catch (MissingResourceException e) { // System.out.println("ResourceBundleUtil "+baseName+" get("+key+"):***MISSING***"); if (isVerbose) { System.err.println("Warning ResourceBundleUtil[" + baseName + "] \"" + key + "\" not found."); //e.printStackTrace(); } return key; } } /** * Recursive part of the getString method. * * @param key * @throws java.util.MissingResourceException */ private String getStringRecursive(String key) throws MissingResourceException { String value = resource.getString(key); // Substitute placeholders in the value for (int p1 = value.indexOf("${"); p1 != -1; p1 = value.indexOf("${")) { int p2 = value.indexOf('}', p1 + 2); if (p2 == -1) { break; } String placeholderKey = value.substring(p1 + 2, p2); String placeholderFormat; int p3 = placeholderKey.indexOf(','); if (p3 != -1) { placeholderFormat = placeholderKey.substring(p3 + 1); placeholderKey = placeholderKey.substring(0, p3); } else { placeholderFormat = "string"; } ArrayList<String> fallbackKeys = new ArrayList<>(); generateFallbackKeys(placeholderKey, fallbackKeys); String placeholderValue = null; for (String fk : fallbackKeys) { try { placeholderValue = getStringRecursive(fk); break; } catch (MissingResourceException e) { } } if (placeholderValue == null) { throw new MissingResourceException("\""+key+"\" not found in "+baseName, baseName, key); } // Do post-processing depending on placeholder format if ("accelerator".equals(placeholderFormat)) { // Localize the keywords shift, control, ctrl, meta, alt, altGraph StringBuilder b = new StringBuilder(); for (String s : placeholderValue.split(" ")) { if (acceleratorKeys.contains(s)) { b.append(getString("accelerator." + s)); } else { b.append(s); } } placeholderValue = b.toString(); } // Insert placeholder value into value value = value.substring(0, p1) + placeholderValue + value.substring(p2 + 1); } return value; } /** * Generates fallback keys by processing all property name modifiers * in the key. */ private void generateFallbackKeys(String key, ArrayList<String> fallbackKeys) { int p1 = key.indexOf("[$"); if (p1 == -1) { fallbackKeys.add(key); } else { int p2 = key.indexOf(']', p1 + 2); if (p2 == -1) { return; } String modifierKey = key.substring(p1 + 2, p2); String[] modifierValues = propertyNameModifiers.get(modifierKey); if (modifierValues == null) { modifierValues = new String[]{"default"}; } for (String mv : modifierValues) { generateFallbackKeys(key.substring(0, p1) + mv + key.substring(p2 + 1), fallbackKeys); } } } /** * Returns a formatted string using javax.text.MessageFormat. * @param key * @param arguments * @return formatted String */ public String getFormatted(String key, Object... arguments) { return MessageFormat.format(getString(key), arguments); } /** * Returns a formatted string using java.util.Formatter(). * @param key * @param arguments * @return formatted String */ public String format(String key, Object... arguments) { //return String.format(resource.getLocale(), getString(key), arguments); return new Formatter(resource.getLocale()).format(getString(key), arguments).toString(); } /** * Get an Integer from the ResourceBundle. * <br>Convenience method to save casting. * * @param key The key of the property. * @return The value of the property. Returns -1 * if the property is missing. */ public Integer getInteger(String key) { try { return Integer.valueOf(getStringRecursive(key)); } catch (MissingResourceException e) { if (isVerbose) { System.err.println("Warning ResourceBundleUtil[" + baseName + "] \"" + key + "\" not found."); //e.printStackTrace(); } return -1; } } /** * Get a small image icon from the ResourceBundle for use on a {@code JMenuItem}. * <br>Convenience method . * * @param key The key of the property. * This method appends ".smallIcon" to the key. * @return The value of the property. Returns null * if the property is missing. */ @Nullable public ImageIcon getSmallIconProperty(String key, Class<?> baseClass) { ImageIcon icon = getIconProperty(key,".smallIcon",baseClass); if (icon == null) icon = getIconProperty(key,".icon",baseClass); return icon; } /** * Get a large image icon from the ResourceBundle for use on a {@code JButton}. * <br>Convenience method . * * @param key The key of the property. * This method appends ".largeIcon" to the key. * @return The value of the property. Returns null * if the property is missing. */ @Nullable public ImageIcon getLargeIconProperty(String key, Class<?> baseClass) { ImageIcon icon = getIconProperty(key,".largeIcon",baseClass); if (icon == null) icon = getIconProperty(key,".icon",baseClass); return icon; } private ImageIcon getIconProperty(String key, String suffix, Class<?> baseClass) { try { String rsrcName = getStringRecursive(key + suffix); if ("".equals(rsrcName)) { return null; } URL url = baseClass.getResource(rsrcName); if (isVerbose && url == null) { System.err.println("Warning ResourceBundleUtil[" + baseName + "].getIconProperty \"" + key + suffix +"\" resource:" + rsrcName + " not found."); } return (url == null) ? null : new ImageIcon(url); } catch (MissingResourceException e) { if (isVerbose) { System.err.println("Warning ResourceBundleUtil[" + baseName + "].getIconProperty \"" + key + suffix + "\" not found."); //e.printStackTrace(); } return null; } } /** * Get a Mnemonic from the ResourceBundle. * <br>Convenience method. * * @param key The key of the property. * @return The first char of the value of the property. * Returns '\0' if the property is missing. */ public char getMnemonic(String key) { String s = getStringRecursive(key); return (s == null || s.length() == 0) ? '\0' : s.charAt(0); } /** * Gets a char for a JavaBeans "mnemonic" property from the ResourceBundle. * <br>Convenience method. * * @param key The key of the property. This method appends ".mnemonic" to the key. * @return The first char of the value of the property. * Returns '\0' if the property is missing. */ public char getMnemonicProperty(String key) { String s; try { s = getStringRecursive(key + ".mnemonic"); } catch (MissingResourceException e) { if (isVerbose) { System.err.println("Warning ResourceBundleUtil[" + baseName + "] \"" + key + ".mnemonic\" not found."); //e.printStackTrace(); } s = null; } return (s == null || s.length() == 0) ? '\0' : s.charAt(0); } /** * Get a String for a JavaBeans "toolTipText" property from the ResourceBundle. * <br>Convenience method. * * @param key The key of the property. This method appends ".toolTipText" to the key. * @return The ToolTip. Returns null if no tooltip is defined. */ @Nullable public String getToolTipTextProperty(String key) { try { String value = getStringRecursive(key + ".toolTipText"); return value; } catch (MissingResourceException e) { if (isVerbose) { System.err.println("Warning ResourceBundleUtil[" + baseName + "] \"" + key + ".toolTipText\" not found."); //e.printStackTrace(); } return null; } } /** * Get a String for a JavaBeans "text" property from the ResourceBundle. * <br>Convenience method. * * @param key The key of the property. This method appends ".text" to the key. * @return The ToolTip. Returns null if no tooltip is defined. */ @Nullable public String getTextProperty(String key) { try { String value = getStringRecursive(key + ".text"); return value; } catch (MissingResourceException e) { if (isVerbose) { System.err.println("Warning ResourceBundleUtil[" + baseName + "] \"" + key + ".text\" not found."); //e.printStackTrace(); } return null; } } /** * Get a KeyStroke from the ResourceBundle. * <BR>Convenience method. * * @param key The key of the property. * @return <code>javax.swing.KeyStroke.getKeyStroke(value)</code>. * Returns null if the property is missing. */ @Nullable public KeyStroke getKeyStroke(String key) { KeyStroke ks = null; try { String s = getStringRecursive(key); ks = (s == null) ? (KeyStroke) null : KeyStroke.getKeyStroke(s); } catch (NoSuchElementException e) { } return ks; } /** * Gets a KeyStroke for a JavaBeans "accelerator" property from the ResourceBundle. * <BR>Convenience method. * * @param key The key of the property. This method adds ".accelerator" to the key. * @return <code>javax.swing.KeyStroke.getKeyStroke(value)</code>. * Returns null if the property is missing. */ @Nullable public KeyStroke getAcceleratorProperty(String key) { KeyStroke ks = null; try { String s; s = getStringRecursive(key + ".accelerator"); ks = (s == null) ? (KeyStroke) null : KeyStroke.getKeyStroke(s); } catch (MissingResourceException e) { if (isVerbose) { System.err.println("Warning ResourceBundleUtil[" + baseName + "] \"" + key + ".accelerator\" not found."); //e.printStackTrace(); } } catch (NoSuchElementException e) { } return ks; } /** * Get the appropriate ResourceBundle subclass. * * @see java.util.ResourceBundle */ public static ResourceBundleUtil getBundle(String baseName) throws MissingResourceException { return getBundle(baseName, LocaleUtil.getDefault()); } public void setBaseClass(Class<?> baseClass) { this.baseClass = baseClass; } public Class<?> getBaseClass() { return baseClass; } public void configureAction(Action action, String argument) { configureAction(action, argument, getBaseClass()); } public void configureAction(Action action, String argument, Class<?> baseClass) { action.putValue(Action.NAME, getTextProperty(argument)); String shortDescription = getToolTipTextProperty(argument); if (shortDescription != null && shortDescription.length() > 0) { action.putValue(Action.SHORT_DESCRIPTION, shortDescription); } action.putValue(Action.ACCELERATOR_KEY, getAcceleratorProperty(argument)); action.putValue(Action.MNEMONIC_KEY, Integer.valueOf(getMnemonicProperty(argument))); action.putValue(Action.SMALL_ICON, getSmallIconProperty(argument, baseClass)); action.putValue(Action.LARGE_ICON_KEY, getLargeIconProperty(argument, baseClass)); } public void configureButton(AbstractButton button, String argument) { configureButton(button, argument, getBaseClass()); } public void configureButton(AbstractButton button, String argument, Class<?> baseClass) { button.setText(getTextProperty(argument)); //button.setACCELERATOR_KEY, getAcceleratorProperty(argument)); //action.putValue(Action.MNEMONIC_KEY, new Integer(getMnemonicProperty(argument))); button.setIcon(getLargeIconProperty(argument, baseClass)); button.setToolTipText(getToolTipTextProperty(argument)); } public void configureToolBarButton(AbstractButton button, String argument) { configureToolBarButton(button, argument, getBaseClass()); } public void configureToolBarButton(AbstractButton button, String argument, Class<?> baseClass) { Icon icon = getLargeIconProperty(argument, baseClass); if (icon != null) { button.setIcon(getLargeIconProperty(argument, baseClass)); button.setText(null); } else { button.setIcon(null); button.setText(getTextProperty(argument)); } button.setToolTipText(getToolTipTextProperty(argument)); } /** Configures a menu item with a text, an accelerator, a mnemonic and * a menu icon. */ public void configureMenu(JMenuItem menu, String argument) { menu.setText(getTextProperty(argument)); if (!(menu instanceof JMenu)) { menu.setAccelerator(getAcceleratorProperty(argument)); } menu.setMnemonic(getMnemonicProperty(argument)); menu.setIcon(getLargeIconProperty(argument, baseClass)); } public JMenuItem createMenuItem(Action a, String baseName) { JMenuItem mi = new JMenuItem(); mi.setAction(a); configureMenu(mi, baseName); return mi; } /** * Get the appropriate ResourceBundle subclass. * * @see java.util.ResourceBundle */ public static ResourceBundleUtil getBundle(String baseName, Locale locale) throws MissingResourceException { ResourceBundleUtil r; r = new ResourceBundleUtil(baseName, locale); return r; } @Override public String toString() { return super.toString() + "[" + baseName + ", " + resource + "]"; } public static void setVerbose(boolean newValue) { isVerbose = newValue; } public static boolean isVerbose() { return isVerbose; } /** * Puts a property name modifier along with a fallback chain. * * @param name The name of the modifier. * @param fallbackChain The fallback chain of the modifier. */ public static void putPropertyNameModifier(String name, String... fallbackChain) { propertyNameModifiers.put(name, fallbackChain); } /** * Removes a property name modifier. */ public static void removePropertyNameModifier(String name) { propertyNameModifiers.remove(name); } /** Read object from ObjectInputStream and re-establish ResourceBundle. */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { // our "pseudo-constructor" in.defaultReadObject(); // re-establish the "resource" variable this.resource = ResourceBundle.getBundle(baseName, locale); } }