Java tutorial
/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2002-2017 Hitachi Vantara.. All rights reserved. */ package org.pentaho.reporting.libraries.base.util; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.swing.*; import java.awt.*; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; import java.lang.reflect.Field; import java.net.URL; import java.text.MessageFormat; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.StringTokenizer; /** * An utility class to ease up using property-file resource bundles. * <p/> * The class support references within the resource bundle set to minimize the occurence of duplicate keys. References * are given in the format: * <pre> * a.key.name=@referenced.key * </pre> * <p/> * A lookup to a key in an other resource bundle should be written by * <pre> * a.key.name=@@resourcebundle_name@referenced.key * </pre> * * @author Thomas Morgner * @noinspection HardCodedStringLiteral */ public class ResourceBundleSupport { /** * A logger for debug-messages. */ private static final Log logger = LogFactory.getLog(ResourceBundleSupport.class); /** * The resource bundle that will be used for local lookups. */ private ResourceBundle resources; /** * A cache for string values, as looking up the cache is faster than looking up the value in the bundle. */ private HashMap<String, String> cache; /** * The current lookup path when performing non local lookups. This prevents infinite loops during such lookups. */ private HashSet<String> lookupPath; /** * The name of the local resource bundle. This property is only used for debugging and logging. */ private String resourceBase; /** * The locale for this bundle. */ private Locale locale; private ClassLoader sourceClassLoader; private static final Integer INVALID_MNEMONIC = new Integer(0); /** * Creates a new instance. * * @param locale the locale that should be used to load the resource-bundle. * @param baseName the base name of the resource bundle, a fully qualified class name * @param classLoader the class-loader from where to load resources. */ public ResourceBundleSupport(final Locale locale, final String baseName, final ClassLoader classLoader) { this(locale, ResourceBundle.getBundle(baseName, locale, classLoader), baseName, classLoader); } /** * Creates a new instance. * * @param locale the locale for which this resource bundle is created. * @param resourceBundle the resourcebundle * @param baseName the base name of the resource bundle, a fully qualified class name * @param classLoader the class-loader from where to load resources. */ public ResourceBundleSupport(final Locale locale, final ResourceBundle resourceBundle, final String baseName, final ClassLoader classLoader) { if (locale == null) { throw new NullPointerException("Locale must not be null"); } if (resourceBundle == null) { throw new NullPointerException("Resources must not be null"); } if (baseName == null) { throw new NullPointerException("BaseName must not be null"); } if (classLoader == null) { throw new NullPointerException("ClassLoader must not be null"); } this.sourceClassLoader = classLoader; this.locale = locale; this.resources = resourceBundle; this.resourceBase = baseName; this.cache = new HashMap<String, String>(); this.lookupPath = new HashSet<String>(); } /** * Creates a new instance. * * @param locale the locale for which the resource bundle is created. * @param resourceBundle the resourcebundle * @param classLoader the class-loader from where to load resources. */ public ResourceBundleSupport(final Locale locale, final ResourceBundle resourceBundle, final ClassLoader classLoader) { this(locale, resourceBundle, resourceBundle.toString(), classLoader); } /** * The base name of the resource bundle. * * @return the resource bundle's name. */ protected final String getResourceBase() { return this.resourceBase; } /** * Gets a string for the given key from this resource bundle or one of its parents. If the key is a link, the link is * resolved and the referenced string is returned instead. * * @param key the key for the desired string * @return the string for the given key * @throws NullPointerException if <code>key</code> is <code>null</code> * @throws java.util.MissingResourceException if no object for the given key can be found * @throws ClassCastException if the object found for the given key is not a string */ public synchronized String strictString(final String key) { if (key == null) { throw new NullPointerException(); } final String retval = this.cache.get(key); if (retval != null) { return retval; } this.lookupPath.clear(); return internalGetString(key); } /** * Performs the lookup for the given key. If the key points to a link the link is resolved and that key is looked up * instead. * * @param key the key for the string * @return the string for the given key */ protected String internalGetString(final String key) { if (key == null) { throw new NullPointerException(); } if (this.lookupPath.contains(key)) { throw new MissingResourceException("InfiniteLoop in resource lookup", getResourceBase(), this.lookupPath.toString()); } final String fromResBundle = this.resources.getString(key); if (fromResBundle.length() > 0 && fromResBundle.charAt(0) == '@') { if (fromResBundle.length() > 1 && fromResBundle.charAt(1) == '@') { // global forward ... final int idx = fromResBundle.indexOf('@', 2); if (idx == -1) { throw new MissingResourceException("Invalid format for global lookup key.", getResourceBase(), key); } try { final ResourceBundle res = ResourceBundle.getBundle(fromResBundle.substring(2, idx), locale, sourceClassLoader); return res.getString(fromResBundle.substring(idx + 1)); } catch (Exception e) { logger.error("Error during global lookup", e); throw new MissingResourceException("Error during global lookup", getResourceBase(), key); } } else { // local forward ... final String newKey = fromResBundle.substring(1); this.lookupPath.add(key); final String retval = internalGetString(newKey); this.cache.put(key, retval); return retval; } } else { this.cache.put(key, fromResBundle); return fromResBundle; } } /** * Returns an scaled icon suitable for buttons or menus. * * @param key the name of the resource bundle key * @param large true, if the image should be scaled to 24x24, or false for 16x16 * @return the icon. */ public Icon getIcon(final String key, final boolean large) { if (key == null) { throw new NullPointerException(); } final String name = strictString(key); return createIcon(name, true, large); } /** * Returns an unscaled icon. * * @param key the name of the resource bundle key * @return the icon. */ public Icon getIcon(final String key) { if (key == null) { throw new NullPointerException(); } final String name = strictString(key); return createIcon(name, false, false); } /** * Returns the mnemonic stored at the given resourcebundle key. The mnemonic should be either the symbolic name of one * of the KeyEvent.VK_* constants (without the 'VK_') or the character for that key. * <p/> * For the enter key, the resource bundle would therefore either contain "ENTER" or "\n". * <pre> * a.resourcebundle.key=ENTER * an.other.resourcebundle.key=\n * </pre> * * @param key the resourcebundle key * @return the mnemonic */ public Integer getMnemonic(final String key) { if (key == null) { throw new NullPointerException(); } final String name = strictString(key); return createMnemonic(name); } /** * Returns the mnemonic stored at the given resourcebundle key. The mnemonic should be either the symbolic name of one * of the KeyEvent.VK_* constants (without the 'VK_') or the character for that key. * <p/> * For the enter key, the resource bundle would therefore either contain "ENTER" or "\n". * <pre> * a.resourcebundle.key=ENTER * an.other.resourcebundle.key=\n * </pre> * * @param key the resourcebundle key * @return the mnemonic or null, if the mnemonic is not defined. */ public Integer getOptionalMnemonic(final String key) { if (key == null) { throw new NullPointerException(); } final String name = getOptionalString(key); if (name != null && name.length() > 0) { return createMnemonic(name); } return INVALID_MNEMONIC; } /** * Returns the keystroke stored at the given resourcebundle key. * <p/> * The keystroke will be composed of a simple key press and the plattform's MenuKeyMask. * <p/> * The keystrokes character key should be either the symbolic name of one of the KeyEvent.VK_* constants or the * character for that key. * <p/> * For the 'A' key, the resource bundle would therefore either contain "VK_A" or "a". * <pre> * a.resourcebundle.key=VK_A * an.other.resourcebundle.key=a * </pre> * * @param key the resourcebundle key * @return the keystroke * @see java.awt.Toolkit#getMenuShortcutKeyMask() */ public KeyStroke getKeyStroke(final String key) { String name = strictString(key); if (StringUtils.isEmpty(name)) { return null; } boolean explicitNone = false; int mask = 0; final StringTokenizer strtok = new StringTokenizer(name); while (strtok.hasMoreTokens()) { final String token = strtok.nextToken(); if ("shift".equals(token)) { mask |= KeyEvent.SHIFT_MASK; } else if ("alt".equals(token)) { mask |= KeyEvent.ALT_MASK; } else if ("ctrl".equals(token)) { mask |= KeyEvent.CTRL_MASK; } else if ("meta".equals(token)) { mask |= KeyEvent.META_MASK; } else if ("menu".equals(token)) { mask |= getMenuKeyMask(); } else if ("none".equals(token)) { explicitNone = true; } else { name = token; } } if (explicitNone == true) { mask = 0; } else if (mask == 0) { mask = getMenuKeyMask(); } //noinspection MagicConstant return KeyStroke.getKeyStroke(createMnemonic(name).intValue(), mask); } /** * Returns the keystroke stored at the given resourcebundle key. * <p/> * The keystroke will be composed of a simple key press and a keystroke mask pattern. The pattern should be specified * via the words "shift", "alt", "ctrl", "meta" or "menu". Menu should be used to reference the platform specific menu * shortcut. For the sake of safety, menu should only be combined with "shift" and/or "alt" for menu keystrokes. * <p/> * The keystrokes character key should be either the symbolic name of one of the KeyEvent.VK_* constants or the * character for that key. * <p/> * For the 'A' key, the resource bundle would therefore either contain "VK_A" or "a". * <pre> * a.resourcebundle.key=VK_A * an.other.resourcebundle.key=a * </pre> * * @param key the resourcebundle key * @return the keystroke * @see java.awt.Toolkit#getMenuShortcutKeyMask() */ public KeyStroke getOptionalKeyStroke(final String key) { try { String name = getOptionalString(key); if (StringUtils.isEmpty(name)) { return null; } boolean noneSelected = false; int mask = 0; final StringTokenizer strtok = new StringTokenizer(name); while (strtok.hasMoreTokens()) { final String token = strtok.nextToken(); if ("shift".equals(token)) { mask |= KeyEvent.SHIFT_MASK; } else if ("alt".equals(token)) { mask |= KeyEvent.ALT_MASK; } else if ("ctrl".equals(token)) { mask |= KeyEvent.CTRL_MASK; } else if ("meta".equals(token)) { mask |= KeyEvent.META_MASK; } else if ("menu".equals(token)) { mask |= getMenuKeyMask(); } else if ("none".equals(token)) { noneSelected = true; } else { name = token; } } if (noneSelected) { mask = 0; } else if (mask == 0) { mask = getMenuKeyMask(); } //noinspection MagicConstant return KeyStroke.getKeyStroke(createMnemonic(name).intValue(), mask); } catch (MissingResourceException mre) { return null; } } /** * Returns the keystroke stored at the given resourcebundle key. * <p/> * The keystroke will be composed of a simple key press and the given KeyMask. If the KeyMask is zero, a plain * Keystroke is returned. * <p/> * The keystrokes character key should be either the symbolic name of one of the KeyEvent.VK_* constants or the * character for that key. * <p/> * For the 'A' key, the resource bundle would therefore either contain "VK_A" or "a". * <pre> * a.resourcebundle.key=VK_A * an.other.resourcebundle.key=a * </pre> * * @param key the resourcebundle key * @param mask the key-moifier mask to be used to create the keystroke. * @return the keystroke that has been generated. * @see java.awt.Toolkit#getMenuShortcutKeyMask() */ public KeyStroke getKeyStroke(final String key, final int mask) { if (key == null) { throw new NullPointerException(); } final String name = strictString(key); //noinspection MagicConstant return KeyStroke.getKeyStroke(createMnemonic(name).intValue(), mask); } /** * Returns the keystroke stored at the given resourcebundle key. * <p/> * The keystroke will be composed of a simple key press and the given KeyMask. If the KeyMask is zero, a plain * Keystroke is returned. * <p/> * The keystrokes character key should be either the symbolic name of one of the KeyEvent.VK_* constants or the * character for that key. * <p/> * For the 'A' key, the resource bundle would therefore either contain "VK_A" or "a". * <pre> * a.resourcebundle.key=VK_A * an.other.resourcebundle.key=a * </pre> * * @param key the resourcebundle key * @param mask the key-moifier mask to be used to create the keystroke. * @return the keystroke or null if the key is not defined. * @see java.awt.Toolkit#getMenuShortcutKeyMask() */ public KeyStroke getOptionalKeyStroke(final String key, final int mask) { if (key == null) { throw new NullPointerException(); } final String name = getOptionalString(key); if (name != null && name.length() > 0) { //noinspection MagicConstant return KeyStroke.getKeyStroke(createMnemonic(name).intValue(), mask); } return null; } /** * Returns a JMenu created from a resource bundle definition. * <p/> * The menu definition consists of two keys, the name of the menu and the mnemonic for that menu. Both keys share a * common prefix, which is extended by ".name" for the name of the menu and ".mnemonic" for the mnemonic. * <p/> * <pre> * # define the file menu * menu.file.name=File * menu.file.mnemonic=F * </pre> * The menu definition above can be used to create the menu by calling <code>createMenu ("menu.file")</code>. * * @param keyPrefix the common prefix for that menu * @return the created menu */ public JMenu createMenu(final String keyPrefix) { if (keyPrefix == null) { throw new NullPointerException(); } final JMenu retval = new JMenu(); retval.setText(strictString(keyPrefix + ".name")); final Integer mnemonic = getOptionalMnemonic(keyPrefix + ".mnemonic"); if (mnemonic != null) { retval.setMnemonic(mnemonic.intValue()); } return retval; } /** * Returns a URL pointing to a resource located in the classpath. The resource is looked up using the given key. * <p/> * Example: The load a file named 'logo.gif' which is stored in a java package named 'org.jfree.resources': * <pre> * mainmenu.logo=org/jfree/resources/logo.gif * </pre> * The URL for that file can be queried with: <code>getResource("mainmenu.logo");</code>. * * @param key the key for the resource * @return the resource URL */ public URL getResourceURL(final String key) { if (key == null) { throw new NullPointerException(); } final String name = strictString(key); final URL in = sourceClassLoader.getResource(name); if (in == null) { logger.warn("Unable to find file in the class path: " + name + "; key=" + key); } return in; } /** * Attempts to load an image from classpath. If this fails, an empty image icon is returned. * * @param resourceName the name of the image. The name should be a global resource name. * @param scale true, if the image should be scaled, false otherwise * @param large true, if the image should be scaled to 24x24, or false for 16x16 * @return the image icon. */ private ImageIcon createIcon(final String resourceName, final boolean scale, final boolean large) { final URL in = sourceClassLoader.getResource(resourceName); if (in == null) { logger.warn("Unable to find file in the class path: " + resourceName); return new ImageIcon(createTransparentImage(1, 1)); } final Image img = Toolkit.getDefaultToolkit().createImage(in); if (img == null) { logger.warn("Unable to instantiate the image: " + resourceName); return new ImageIcon(createTransparentImage(1, 1)); } if (scale) { if (large) { return new ImageIcon(img.getScaledInstance(24, 24, Image.SCALE_SMOOTH)); } return new ImageIcon(img.getScaledInstance(16, 16, Image.SCALE_SMOOTH)); } return new ImageIcon(img); } /** * Creates the Mnemonic from the given String. The String consists of the name of the VK constants of the class * KeyEvent without VK_*. * * @param keyString the string * @return the mnemonic as integer */ private Integer createMnemonic(final String keyString) { if (keyString == null) { throw new NullPointerException("Key is null."); } if (keyString.length() == 0) { throw new IllegalArgumentException("Key is empty."); } int character = keyString.charAt(0); if (keyString.startsWith("VK_")) // NON-NLS { try { final Field f = KeyEvent.class.getField(keyString); final Integer keyCode = (Integer) f.get(null); character = keyCode.intValue(); } catch (Exception nsfe) { // ignore the exception ... } } return Integer.valueOf(character); } /** * Returns the plattforms default menu shortcut keymask. * * @return the default key mask. */ private int getMenuKeyMask() { try { return Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); } catch (UnsupportedOperationException he) { // headless exception extends UnsupportedOperation exception, // but the HeadlessException is not defined in older JDKs... return InputEvent.CTRL_MASK; } } /** * Creates a transparent image. These can be used for aligning menu items. * * @param width the width. * @param height the height. * @return the created transparent image. */ private BufferedImage createTransparentImage(final int width, final int height) { final BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); final int[] data = img.getRGB(0, 0, width, height, null, 0, width); img.setRGB(0, 0, width, height, data, 0, width); return img; } /** * Formats the message stored in the resource bundle (using a MessageFormat). * * @param key the resourcebundle key * @param parameter the parameter for the message * @return the formated string */ public String formatMessage(final String key, final Object parameter) { return formatMessage(key, new Object[] { parameter }); } /** * Formats the message stored in the resource bundle (using a MessageFormat). * * @param key the resourcebundle key * @param par1 the first parameter for the message * @param par2 the second parameter for the message * @return the formated string */ public String formatMessage(final String key, final Object par1, final Object par2) { return formatMessage(key, new Object[] { par1, par2 }); } /** * Formats the message stored in the resource bundle (using a MessageFormat). * * @param key the resourcebundle key * @param parameters the parameter collection for the message * @return the formated string */ public String formatMessage(final String key, final Object[] parameters) { final MessageFormat format = new MessageFormat(strictString(key)); format.setLocale(getLocale()); return format.format(parameters); } public String getString(final String key, final Object[] parameters) { try { return formatMessage(key, parameters); } catch (MissingResourceException mre) { logger.warn("ResourceBundleSupport#getString(,,)", mre); return '!' + key + '!'; } } public String getString(final String key) { try { return strictString(key); } catch (MissingResourceException mre) { logger.warn("ResourceBundleSupport#getString(,,)", mre); return '!' + key + '!'; } } public String getOptionalString(final String key) { try { return strictString(key); } catch (Exception e) { logger.trace("Optional String is undefined", e); // ignore it return null; } } public String getString(final String key, final Object par1) { try { return formatMessage(key, par1); } catch (MissingResourceException mre) { logger.warn("ResourceBundleSupport#getString(,,)", mre); return '!' + key + '!'; } } public String getString(final String key, final Object par1, final Object par2) { try { return formatMessage(key, par1, par2); } catch (MissingResourceException mre) { logger.warn("ResourceBundleSupport#getString(,,)", mre); return '!' + key + '!'; } } /** * Returns the current locale for this resource bundle. * * @return the locale. */ public Locale getLocale() { return locale; } }