Frames | No Frames |
1: /* ======================================================================== 2: * JCommon : a free general purpose class library for the Java(tm) platform 3: * ======================================================================== 4: * 5: * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. 6: * 7: * Project Info: http://www.jfree.org/jcommon/index.html 8: * 9: * This library is free software; you can redistribute it and/or modify it 10: * under the terms of the GNU Lesser General Public License as published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * --------------------- 28: * ReadOnlyIterator.java 29: * --------------------- 30: * (C)opyright 2003, 2004, by Thomas Morgner and Contributors. 31: * 32: * Original Author: Thomas Morgner; 33: * Contributor(s): -; 34: * 35: * $Id: ResourceBundleSupport.java,v 1.9 2006/09/26 11:22:10 taqua Exp $ 36: * 37: * Changes 38: * ------------------------- 39: */ 40: package org.jfree.util; 41: 42: import java.awt.Image; 43: import java.awt.Toolkit; 44: import java.awt.event.InputEvent; 45: import java.awt.event.KeyEvent; 46: import java.awt.image.BufferedImage; 47: import java.lang.reflect.Field; 48: import java.net.URL; 49: import java.text.MessageFormat; 50: import java.util.Arrays; 51: import java.util.Locale; 52: import java.util.MissingResourceException; 53: import java.util.ResourceBundle; 54: import java.util.TreeMap; 55: import java.util.TreeSet; 56: import javax.swing.Icon; 57: import javax.swing.ImageIcon; 58: import javax.swing.JMenu; 59: import javax.swing.KeyStroke; 60: 61: /** 62: * An utility class to ease up using property-file resource bundles. 63: * <p/> 64: * The class support references within the resource bundle set to minimize the occurence 65: * of duplicate keys. References are given in the format: 66: * <pre> 67: * a.key.name=@referenced.key 68: * </pre> 69: * <p/> 70: * A lookup to a key in an other resource bundle should be written by 71: * <pre> 72: * a.key.name=@@resourcebundle_name@referenced.key 73: * </pre> 74: * 75: * @author Thomas Morgner 76: */ 77: public class ResourceBundleSupport { 78: /** 79: * The resource bundle that will be used for local lookups. 80: */ 81: private ResourceBundle resources; 82: 83: /** 84: * A cache for string values, as looking up the cache is faster than looking up the 85: * value in the bundle. 86: */ 87: private TreeMap cache; 88: /** 89: * The current lookup path when performing non local lookups. This prevents infinite 90: * loops during such lookups. 91: */ 92: private TreeSet lookupPath; 93: 94: /** 95: * The name of the local resource bundle. 96: */ 97: private String resourceBase; 98: 99: /** The locale for this bundle. */ 100: private Locale locale; 101: 102: /** 103: * Creates a new instance. 104: * 105: * @param baseName the base name of the resource bundle, a fully qualified class name 106: */ 107: public ResourceBundleSupport(final Locale locale, final String baseName) { 108: this(locale, ResourceBundle.getBundle(baseName, locale), baseName); 109: } 110: 111: /** 112: * Creates a new instance. 113: * 114: * @param locale the locale for which this resource bundle is created. 115: * @param resourceBundle the resourcebundle 116: * @param baseName the base name of the resource bundle, a fully qualified class name 117: */ 118: protected ResourceBundleSupport(final Locale locale, 119: final ResourceBundle resourceBundle, 120: final String baseName) { 121: if (locale == null) { 122: throw new NullPointerException("Locale must not be null"); 123: } 124: if (resourceBundle == null) { 125: throw new NullPointerException("Resources must not be null"); 126: } 127: if (baseName == null) { 128: throw new NullPointerException("BaseName must not be null"); 129: } 130: this.locale = locale; 131: this.resources = resourceBundle; 132: this.resourceBase = baseName; 133: this.cache = new TreeMap(); 134: this.lookupPath = new TreeSet(); 135: } 136: 137: /** 138: * Creates a new instance. 139: * 140: * @param locale the locale for which the resource bundle is created. 141: * @param resourceBundle the resourcebundle 142: */ 143: public ResourceBundleSupport(final Locale locale, final ResourceBundle resourceBundle) { 144: this(locale, resourceBundle, resourceBundle.toString()); 145: } 146: 147: /** 148: * Creates a new instance. 149: * 150: * @param baseName the base name of the resource bundle, a fully qualified class name 151: */ 152: public ResourceBundleSupport(final String baseName) { 153: this(Locale.getDefault(), ResourceBundle.getBundle(baseName), baseName); 154: } 155: 156: /** 157: * Creates a new instance. 158: * 159: * @param resourceBundle the resourcebundle 160: * @param baseName the base name of the resource bundle, a fully qualified class name 161: */ 162: protected ResourceBundleSupport(final ResourceBundle resourceBundle, 163: final String baseName) { 164: this(Locale.getDefault(), resourceBundle, baseName); 165: } 166: 167: /** 168: * Creates a new instance. 169: * 170: * @param resourceBundle the resourcebundle 171: */ 172: public ResourceBundleSupport(final ResourceBundle resourceBundle) { 173: this(Locale.getDefault(), resourceBundle, resourceBundle.toString()); 174: } 175: 176: /** 177: * The base name of the resource bundle. 178: * 179: * @return the resource bundle's name. 180: */ 181: protected final String getResourceBase() { 182: return this.resourceBase; 183: } 184: 185: /** 186: * Gets a string for the given key from this resource bundle or one of its parents. If 187: * the key is a link, the link is resolved and the referenced string is returned 188: * instead. 189: * 190: * @param key the key for the desired string 191: * @return the string for the given key 192: * @throws NullPointerException if <code>key</code> is <code>null</code> 193: * @throws MissingResourceException if no object for the given key can be found 194: * @throws ClassCastException if the object found for the given key is not a 195: * string 196: */ 197: public synchronized String getString(final String key) { 198: final String retval = (String) this.cache.get(key); 199: if (retval != null) { 200: return retval; 201: } 202: this.lookupPath.clear(); 203: return internalGetString(key); 204: } 205: 206: /** 207: * Performs the lookup for the given key. If the key points to a link the link is 208: * resolved and that key is looked up instead. 209: * 210: * @param key the key for the string 211: * @return the string for the given key 212: */ 213: protected String internalGetString(final String key) { 214: if (this.lookupPath.contains(key)) { 215: throw new MissingResourceException 216: ("InfiniteLoop in resource lookup", 217: getResourceBase(), this.lookupPath.toString()); 218: } 219: final String fromResBundle = this.resources.getString(key); 220: if (fromResBundle.startsWith("@@")) { 221: // global forward ... 222: final int idx = fromResBundle.indexOf('@', 2); 223: if (idx == -1) { 224: throw new MissingResourceException 225: ("Invalid format for global lookup key.", getResourceBase(), key); 226: } 227: try { 228: final ResourceBundle res = ResourceBundle.getBundle 229: (fromResBundle.substring(2, idx)); 230: return res.getString(fromResBundle.substring(idx + 1)); 231: } 232: catch (Exception e) { 233: Log.error("Error during global lookup", e); 234: throw new MissingResourceException 235: ("Error during global lookup", getResourceBase(), key); 236: } 237: } 238: else if (fromResBundle.startsWith("@")) { 239: // local forward ... 240: final String newKey = fromResBundle.substring(1); 241: this.lookupPath.add(key); 242: final String retval = internalGetString(newKey); 243: 244: this.cache.put(key, retval); 245: return retval; 246: } 247: else { 248: this.cache.put(key, fromResBundle); 249: return fromResBundle; 250: } 251: } 252: 253: /** 254: * Returns an scaled icon suitable for buttons or menus. 255: * 256: * @param key the name of the resource bundle key 257: * @param large true, if the image should be scaled to 24x24, or false for 16x16 258: * @return the icon. 259: */ 260: public Icon getIcon(final String key, final boolean large) { 261: final String name = getString(key); 262: return createIcon(name, true, large); 263: } 264: 265: /** 266: * Returns an unscaled icon. 267: * 268: * @param key the name of the resource bundle key 269: * @return the icon. 270: */ 271: public Icon getIcon(final String key) { 272: final String name = getString(key); 273: return createIcon(name, false, false); 274: } 275: 276: /** 277: * Returns the mnemonic stored at the given resourcebundle key. The mnemonic should be 278: * either the symbolic name of one of the KeyEvent.VK_* constants (without the 'VK_') or 279: * the character for that key. 280: * <p/> 281: * For the enter key, the resource bundle would therefore either contain "ENTER" or 282: * "\n". 283: * <pre> 284: * a.resourcebundle.key=ENTER 285: * an.other.resourcebundle.key=\n 286: * </pre> 287: * 288: * @param key the resourcebundle key 289: * @return the mnemonic 290: */ 291: public Integer getMnemonic(final String key) { 292: final String name = getString(key); 293: return createMnemonic(name); 294: } 295: 296: /** 297: * Returns the keystroke stored at the given resourcebundle key. 298: * <p/> 299: * The keystroke will be composed of a simple key press and the plattform's 300: * MenuKeyMask. 301: * <p/> 302: * The keystrokes character key should be either the symbolic name of one of the 303: * KeyEvent.VK_* constants or the character for that key. 304: * <p/> 305: * For the 'A' key, the resource bundle would therefore either contain "VK_A" or 306: * "a". 307: * <pre> 308: * a.resourcebundle.key=VK_A 309: * an.other.resourcebundle.key=a 310: * </pre> 311: * 312: * @param key the resourcebundle key 313: * @return the mnemonic 314: * @see Toolkit#getMenuShortcutKeyMask() 315: */ 316: public KeyStroke getKeyStroke(final String key) { 317: return getKeyStroke(key, getMenuKeyMask()); 318: } 319: 320: /** 321: * Returns the keystroke stored at the given resourcebundle key. 322: * <p/> 323: * The keystroke will be composed of a simple key press and the given 324: * KeyMask. If the KeyMask is zero, a plain Keystroke is returned. 325: * <p/> 326: * The keystrokes character key should be either the symbolic name of one of the 327: * KeyEvent.VK_* constants or the character for that key. 328: * <p/> 329: * For the 'A' key, the resource bundle would therefore either contain "VK_A" or 330: * "a". 331: * <pre> 332: * a.resourcebundle.key=VK_A 333: * an.other.resourcebundle.key=a 334: * </pre> 335: * 336: * @param key the resourcebundle key 337: * @return the mnemonic 338: * @see Toolkit#getMenuShortcutKeyMask() 339: */ 340: public KeyStroke getKeyStroke(final String key, final int mask) { 341: final String name = getString(key); 342: return KeyStroke.getKeyStroke(createMnemonic(name).intValue(), mask); 343: 344: } 345: 346: /** 347: * Returns a JMenu created from a resource bundle definition. 348: * <p/> 349: * The menu definition consists of two keys, the name of the menu and the mnemonic for 350: * that menu. Both keys share a common prefix, which is extended by ".name" for the name 351: * of the menu and ".mnemonic" for the mnemonic. 352: * <p/> 353: * <pre> 354: * # define the file menu 355: * menu.file.name=File 356: * menu.file.mnemonic=F 357: * </pre> 358: * The menu definition above can be used to create the menu by calling <code>createMenu 359: * ("menu.file")</code>. 360: * 361: * @param keyPrefix the common prefix for that menu 362: * @return the created menu 363: */ 364: public JMenu createMenu(final String keyPrefix) { 365: final JMenu retval = new JMenu(); 366: retval.setText(getString(keyPrefix + ".name")); 367: retval.setMnemonic(getMnemonic(keyPrefix + ".mnemonic").intValue()); 368: return retval; 369: } 370: 371: /** 372: * Returns a URL pointing to a resource located in the classpath. The resource is looked 373: * up using the given key. 374: * <p/> 375: * Example: The load a file named 'logo.gif' which is stored in a java package named 376: * 'org.jfree.resources': 377: * <pre> 378: * mainmenu.logo=org/jfree/resources/logo.gif 379: * </pre> 380: * The URL for that file can be queried with: <code>getResource("mainmenu.logo");</code>. 381: * 382: * @param key the key for the resource 383: * @return the resource URL 384: */ 385: public URL getResourceURL(final String key) { 386: final String name = getString(key); 387: final URL in = ObjectUtilities.getResource(name, ResourceBundleSupport.class); 388: if (in == null) { 389: Log.warn("Unable to find file in the class path: " + name + "; key=" + key); 390: } 391: return in; 392: } 393: 394: 395: /** 396: * Attempts to load an image from classpath. If this fails, an empty image icon is 397: * returned. 398: * 399: * @param resourceName the name of the image. The name should be a global resource 400: * name. 401: * @param scale true, if the image should be scaled, false otherwise 402: * @param large true, if the image should be scaled to 24x24, or false for 16x16 403: * @return the image icon. 404: */ 405: private ImageIcon createIcon(final String resourceName, final boolean scale, 406: final boolean large) { 407: final URL in = ObjectUtilities.getResource(resourceName, ResourceBundleSupport.class);; 408: if (in == null) { 409: Log.warn("Unable to find file in the class path: " + resourceName); 410: return new ImageIcon(createTransparentImage(1, 1)); 411: } 412: final Image img = Toolkit.getDefaultToolkit().createImage(in); 413: if (img == null) { 414: Log.warn("Unable to instantiate the image: " + resourceName); 415: return new ImageIcon(createTransparentImage(1, 1)); 416: } 417: if (scale) { 418: if (large) { 419: return new ImageIcon(img.getScaledInstance(24, 24, Image.SCALE_SMOOTH)); 420: } 421: return new ImageIcon(img.getScaledInstance(16, 16, Image.SCALE_SMOOTH)); 422: } 423: return new ImageIcon(img); 424: } 425: 426: /** 427: * Creates the Mnemonic from the given String. The String consists of the name of the VK 428: * constants of the class KeyEvent without VK_*. 429: * 430: * @param keyString the string 431: * @return the mnemonic as integer 432: */ 433: private Integer createMnemonic(final String keyString) { 434: if (keyString == null) { 435: throw new NullPointerException("Key is null."); 436: } 437: if (keyString.length() == 0) { 438: throw new IllegalArgumentException("Key is empty."); 439: } 440: int character = keyString.charAt(0); 441: if (keyString.startsWith("VK_")) { 442: try { 443: final Field f = KeyEvent.class.getField(keyString); 444: final Integer keyCode = (Integer) f.get(null); 445: character = keyCode.intValue(); 446: } 447: catch (Exception nsfe) { 448: // ignore the exception ... 449: } 450: } 451: return new Integer(character); 452: } 453: 454: /** 455: * Returns the plattforms default menu shortcut keymask. 456: * 457: * @return the default key mask. 458: */ 459: private int getMenuKeyMask() { 460: try { 461: return Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); 462: } 463: catch (UnsupportedOperationException he) { 464: // headless exception extends UnsupportedOperation exception, 465: // but the HeadlessException is not defined in older JDKs... 466: return InputEvent.CTRL_MASK; 467: } 468: } 469: 470: /** 471: * Creates a transparent image. These can be used for aligning menu items. 472: * 473: * @param width the width. 474: * @param height the height. 475: * @return the created transparent image. 476: */ 477: private BufferedImage createTransparentImage(final int width, final int height) { 478: final BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 479: final int[] data = img.getRGB(0, 0, width, height, null, 0, width); 480: Arrays.fill(data, 0x00000000); 481: img.setRGB(0, 0, width, height, data, 0, width); 482: return img; 483: } 484: 485: /** 486: * Creates a transparent icon. The Icon can be used for aligning menu items. 487: * 488: * @param width the width of the new icon 489: * @param height the height of the new icon 490: * @return the created transparent icon. 491: */ 492: public Icon createTransparentIcon(final int width, final int height) { 493: return new ImageIcon(createTransparentImage(width, height)); 494: } 495: 496: /** 497: * Formats the message stored in the resource bundle (using a MessageFormat). 498: * 499: * @param key the resourcebundle key 500: * @param parameter the parameter for the message 501: * @return the formated string 502: */ 503: public String formatMessage(final String key, final Object parameter) { 504: return formatMessage(key, new Object[]{parameter}); 505: } 506: 507: /** 508: * Formats the message stored in the resource bundle (using a MessageFormat). 509: * 510: * @param key the resourcebundle key 511: * @param par1 the first parameter for the message 512: * @param par2 the second parameter for the message 513: * @return the formated string 514: */ 515: public String formatMessage(final String key, 516: final Object par1, 517: final Object par2) { 518: return formatMessage(key, new Object[]{par1, par2}); 519: } 520: 521: /** 522: * Formats the message stored in the resource bundle (using a MessageFormat). 523: * 524: * @param key the resourcebundle key 525: * @param parameters the parameter collection for the message 526: * @return the formated string 527: */ 528: public String formatMessage(final String key, final Object[] parameters) { 529: final MessageFormat format = new MessageFormat(getString(key)); 530: format.setLocale(getLocale()); 531: return format.format(parameters); 532: } 533: 534: /** 535: * Returns the current locale for this resource bundle. 536: * @return the locale. 537: */ 538: public Locale getLocale() { 539: return locale; 540: } 541: }