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.10 2006/12/03 15:33:33 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 65: * occurence 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: /** 80: * The resource bundle that will be used for local lookups. 81: */ 82: private ResourceBundle resources; 83: 84: /** 85: * A cache for string values, as looking up the cache is faster than looking 86: * up the value in the bundle. 87: */ 88: private TreeMap cache; 89: /** 90: * The current lookup path when performing non local lookups. This prevents 91: * infinite loops during such lookups. 92: */ 93: private TreeSet lookupPath; 94: 95: /** 96: * The name of the local resource bundle. 97: */ 98: private String resourceBase; 99: 100: /** 101: * The locale for this bundle. 102: */ 103: private Locale locale; 104: 105: /** 106: * Creates a new instance. 107: * 108: * @param baseName the base name of the resource bundle, a fully qualified 109: * class name 110: */ 111: public ResourceBundleSupport(final Locale locale, final String baseName) 112: { 113: this(locale, ResourceBundle.getBundle(baseName, locale), baseName); 114: } 115: 116: /** 117: * Creates a new instance. 118: * 119: * @param locale the locale for which this resource bundle is 120: * created. 121: * @param resourceBundle the resourcebundle 122: * @param baseName the base name of the resource bundle, a fully 123: * qualified class name 124: */ 125: protected ResourceBundleSupport(final Locale locale, 126: final ResourceBundle resourceBundle, 127: final String baseName) 128: { 129: if (locale == null) 130: { 131: throw new NullPointerException("Locale must not be null"); 132: } 133: if (resourceBundle == null) 134: { 135: throw new NullPointerException("Resources must not be null"); 136: } 137: if (baseName == null) 138: { 139: throw new NullPointerException("BaseName must not be null"); 140: } 141: this.locale = locale; 142: this.resources = resourceBundle; 143: this.resourceBase = baseName; 144: this.cache = new TreeMap(); 145: this.lookupPath = new TreeSet(); 146: } 147: 148: /** 149: * Creates a new instance. 150: * 151: * @param locale the locale for which the resource bundle is 152: * created. 153: * @param resourceBundle the resourcebundle 154: */ 155: public ResourceBundleSupport(final Locale locale, 156: final ResourceBundle resourceBundle) 157: { 158: this(locale, resourceBundle, resourceBundle.toString()); 159: } 160: 161: /** 162: * Creates a new instance. 163: * 164: * @param baseName the base name of the resource bundle, a fully qualified 165: * class name 166: */ 167: public ResourceBundleSupport(final String baseName) 168: { 169: this(Locale.getDefault(), ResourceBundle.getBundle(baseName), baseName); 170: } 171: 172: /** 173: * Creates a new instance. 174: * 175: * @param resourceBundle the resourcebundle 176: * @param baseName the base name of the resource bundle, a fully 177: * qualified class name 178: */ 179: protected ResourceBundleSupport(final ResourceBundle resourceBundle, 180: final String baseName) 181: { 182: this(Locale.getDefault(), resourceBundle, baseName); 183: } 184: 185: /** 186: * Creates a new instance. 187: * 188: * @param resourceBundle the resourcebundle 189: */ 190: public ResourceBundleSupport(final ResourceBundle resourceBundle) 191: { 192: this(Locale.getDefault(), resourceBundle, resourceBundle.toString()); 193: } 194: 195: /** 196: * The base name of the resource bundle. 197: * 198: * @return the resource bundle's name. 199: */ 200: protected final String getResourceBase() 201: { 202: return this.resourceBase; 203: } 204: 205: /** 206: * Gets a string for the given key from this resource bundle or one of its 207: * parents. If the key is a link, the link is resolved and the referenced 208: * string is returned instead. 209: * 210: * @param key the key for the desired string 211: * @return the string for the given key 212: * @throws NullPointerException if <code>key</code> is <code>null</code> 213: * @throws MissingResourceException if no object for the given key can be 214: * found 215: * @throws ClassCastException if the object found for the given key is 216: * not a string 217: */ 218: public synchronized String getString(final String key) 219: { 220: final String retval = (String) this.cache.get(key); 221: if (retval != null) 222: { 223: return retval; 224: } 225: this.lookupPath.clear(); 226: return internalGetString(key); 227: } 228: 229: /** 230: * Performs the lookup for the given key. If the key points to a link the 231: * link is resolved and that key is looked up instead. 232: * 233: * @param key the key for the string 234: * @return the string for the given key 235: */ 236: protected String internalGetString(final String key) 237: { 238: if (this.lookupPath.contains(key)) 239: { 240: throw new MissingResourceException 241: ("InfiniteLoop in resource lookup", 242: getResourceBase(), this.lookupPath.toString()); 243: } 244: final String fromResBundle = this.resources.getString(key); 245: if (fromResBundle.startsWith("@@")) 246: { 247: // global forward ... 248: final int idx = fromResBundle.indexOf('@', 2); 249: if (idx == -1) 250: { 251: throw new MissingResourceException 252: ("Invalid format for global lookup key.", getResourceBase(), key); 253: } 254: try 255: { 256: final ResourceBundle res = ResourceBundle.getBundle 257: (fromResBundle.substring(2, idx)); 258: return res.getString(fromResBundle.substring(idx + 1)); 259: } 260: catch (Exception e) 261: { 262: Log.error("Error during global lookup", e); 263: throw new MissingResourceException 264: ("Error during global lookup", getResourceBase(), key); 265: } 266: } 267: else if (fromResBundle.startsWith("@")) 268: { 269: // local forward ... 270: final String newKey = fromResBundle.substring(1); 271: this.lookupPath.add(key); 272: final String retval = internalGetString(newKey); 273: 274: this.cache.put(key, retval); 275: return retval; 276: } 277: else 278: { 279: this.cache.put(key, fromResBundle); 280: return fromResBundle; 281: } 282: } 283: 284: /** 285: * Returns an scaled icon suitable for buttons or menus. 286: * 287: * @param key the name of the resource bundle key 288: * @param large true, if the image should be scaled to 24x24, or false for 289: * 16x16 290: * @return the icon. 291: */ 292: public Icon getIcon(final String key, final boolean large) 293: { 294: final String name = getString(key); 295: return createIcon(name, true, large); 296: } 297: 298: /** 299: * Returns an unscaled icon. 300: * 301: * @param key the name of the resource bundle key 302: * @return the icon. 303: */ 304: public Icon getIcon(final String key) 305: { 306: final String name = getString(key); 307: return createIcon(name, false, false); 308: } 309: 310: /** 311: * Returns the mnemonic stored at the given resourcebundle key. The mnemonic 312: * should be either the symbolic name of one of the KeyEvent.VK_* constants 313: * (without the 'VK_') or the character for that key. 314: * <p/> 315: * For the enter key, the resource bundle would therefore either contain 316: * "ENTER" or "\n". 317: * <pre> 318: * a.resourcebundle.key=ENTER 319: * an.other.resourcebundle.key=\n 320: * </pre> 321: * 322: * @param key the resourcebundle key 323: * @return the mnemonic 324: */ 325: public Integer getMnemonic(final String key) 326: { 327: final String name = getString(key); 328: return createMnemonic(name); 329: } 330: 331: 332: public Integer getOptionalMnemonic(final String key) 333: { 334: final String name = getString(key); 335: if (name != null && name.length() > 0) 336: { 337: return createMnemonic(name); 338: } 339: return null; 340: } 341: 342: /** 343: * Returns the keystroke stored at the given resourcebundle key. 344: * <p/> 345: * The keystroke will be composed of a simple key press and the plattform's 346: * MenuKeyMask. 347: * <p/> 348: * The keystrokes character key should be either the symbolic name of one of 349: * the KeyEvent.VK_* constants or the character for that key. 350: * <p/> 351: * For the 'A' key, the resource bundle would therefore either contain 352: * "VK_A" or "a". 353: * <pre> 354: * a.resourcebundle.key=VK_A 355: * an.other.resourcebundle.key=a 356: * </pre> 357: * 358: * @param key the resourcebundle key 359: * @return the mnemonic 360: * @see Toolkit#getMenuShortcutKeyMask() 361: */ 362: public KeyStroke getKeyStroke(final String key) 363: { 364: return getKeyStroke(key, getMenuKeyMask()); 365: } 366: 367: public KeyStroke getOptionalKeyStroke(final String key) 368: { 369: return getOptionalKeyStroke(key, getMenuKeyMask()); 370: } 371: 372: /** 373: * Returns the keystroke stored at the given resourcebundle key. 374: * <p/> 375: * The keystroke will be composed of a simple key press and the given 376: * KeyMask. If the KeyMask is zero, a plain Keystroke is returned. 377: * <p/> 378: * The keystrokes character key should be either the symbolic name of one of 379: * the KeyEvent.VK_* constants or the character for that key. 380: * <p/> 381: * For the 'A' key, the resource bundle would therefore either contain 382: * "VK_A" or "a". 383: * <pre> 384: * a.resourcebundle.key=VK_A 385: * an.other.resourcebundle.key=a 386: * </pre> 387: * 388: * @param key the resourcebundle key 389: * @return the mnemonic 390: * @see Toolkit#getMenuShortcutKeyMask() 391: */ 392: public KeyStroke getKeyStroke(final String key, final int mask) 393: { 394: final String name = getString(key); 395: return KeyStroke.getKeyStroke(createMnemonic(name).intValue(), mask); 396: } 397: 398: public KeyStroke getOptionalKeyStroke(final String key, final int mask) 399: { 400: final String name = getString(key); 401: 402: if (name != null && name.length() > 0) 403: { 404: return KeyStroke.getKeyStroke(createMnemonic(name).intValue(), mask); 405: } 406: return null; 407: } 408: 409: /** 410: * Returns a JMenu created from a resource bundle definition. 411: * <p/> 412: * The menu definition consists of two keys, the name of the menu and the 413: * mnemonic for that menu. Both keys share a common prefix, which is 414: * extended by ".name" for the name of the menu and ".mnemonic" for the 415: * mnemonic. 416: * <p/> 417: * <pre> 418: * # define the file menu 419: * menu.file.name=File 420: * menu.file.mnemonic=F 421: * </pre> 422: * The menu definition above can be used to create the menu by calling 423: * <code>createMenu ("menu.file")</code>. 424: * 425: * @param keyPrefix the common prefix for that menu 426: * @return the created menu 427: */ 428: public JMenu createMenu(final String keyPrefix) 429: { 430: final JMenu retval = new JMenu(); 431: retval.setText(getString(keyPrefix + ".name")); 432: retval.setMnemonic(getMnemonic(keyPrefix + ".mnemonic").intValue()); 433: return retval; 434: } 435: 436: /** 437: * Returns a URL pointing to a resource located in the classpath. The 438: * resource is looked up using the given key. 439: * <p/> 440: * Example: The load a file named 'logo.gif' which is stored in a java 441: * package named 'org.jfree.resources': 442: * <pre> 443: * mainmenu.logo=org/jfree/resources/logo.gif 444: * </pre> 445: * The URL for that file can be queried with: <code>getResource("mainmenu.logo");</code>. 446: * 447: * @param key the key for the resource 448: * @return the resource URL 449: */ 450: public URL getResourceURL(final String key) 451: { 452: final String name = getString(key); 453: final URL in = ObjectUtilities.getResource(name, ResourceBundleSupport.class); 454: if (in == null) 455: { 456: Log.warn("Unable to find file in the class path: " + name + "; key=" + key); 457: } 458: return in; 459: } 460: 461: 462: /** 463: * Attempts to load an image from classpath. If this fails, an empty image 464: * icon is returned. 465: * 466: * @param resourceName the name of the image. The name should be a global 467: * resource name. 468: * @param scale true, if the image should be scaled, false otherwise 469: * @param large true, if the image should be scaled to 24x24, or 470: * false for 16x16 471: * @return the image icon. 472: */ 473: private ImageIcon createIcon(final String resourceName, final boolean scale, 474: final boolean large) 475: { 476: final URL in = ObjectUtilities.getResource(resourceName, ResourceBundleSupport.class); 477: ; 478: if (in == null) 479: { 480: Log.warn("Unable to find file in the class path: " + resourceName); 481: return new ImageIcon(createTransparentImage(1, 1)); 482: } 483: final Image img = Toolkit.getDefaultToolkit().createImage(in); 484: if (img == null) 485: { 486: Log.warn("Unable to instantiate the image: " + resourceName); 487: return new ImageIcon(createTransparentImage(1, 1)); 488: } 489: if (scale) 490: { 491: if (large) 492: { 493: return new ImageIcon(img.getScaledInstance(24, 24, Image.SCALE_SMOOTH)); 494: } 495: return new ImageIcon(img.getScaledInstance(16, 16, Image.SCALE_SMOOTH)); 496: } 497: return new ImageIcon(img); 498: } 499: 500: /** 501: * Creates the Mnemonic from the given String. The String consists of the 502: * name of the VK constants of the class KeyEvent without VK_*. 503: * 504: * @param keyString the string 505: * @return the mnemonic as integer 506: */ 507: private Integer createMnemonic(final String keyString) 508: { 509: if (keyString == null) 510: { 511: throw new NullPointerException("Key is null."); 512: } 513: if (keyString.length() == 0) 514: { 515: throw new IllegalArgumentException("Key is empty."); 516: } 517: int character = keyString.charAt(0); 518: if (keyString.startsWith("VK_")) 519: { 520: try 521: { 522: final Field f = KeyEvent.class.getField(keyString); 523: final Integer keyCode = (Integer) f.get(null); 524: character = keyCode.intValue(); 525: } 526: catch (Exception nsfe) 527: { 528: // ignore the exception ... 529: } 530: } 531: return new Integer(character); 532: } 533: 534: /** 535: * Returns the plattforms default menu shortcut keymask. 536: * 537: * @return the default key mask. 538: */ 539: private int getMenuKeyMask() 540: { 541: try 542: { 543: return Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); 544: } 545: catch (UnsupportedOperationException he) 546: { 547: // headless exception extends UnsupportedOperation exception, 548: // but the HeadlessException is not defined in older JDKs... 549: return InputEvent.CTRL_MASK; 550: } 551: } 552: 553: /** 554: * Creates a transparent image. These can be used for aligning menu items. 555: * 556: * @param width the width. 557: * @param height the height. 558: * @return the created transparent image. 559: */ 560: private BufferedImage createTransparentImage(final int width, 561: final int height) 562: { 563: final BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 564: final int[] data = img.getRGB(0, 0, width, height, null, 0, width); 565: Arrays.fill(data, 0x00000000); 566: img.setRGB(0, 0, width, height, data, 0, width); 567: return img; 568: } 569: 570: /** 571: * Creates a transparent icon. The Icon can be used for aligning menu 572: * items. 573: * 574: * @param width the width of the new icon 575: * @param height the height of the new icon 576: * @return the created transparent icon. 577: */ 578: public Icon createTransparentIcon(final int width, final int height) 579: { 580: return new ImageIcon(createTransparentImage(width, height)); 581: } 582: 583: /** 584: * Formats the message stored in the resource bundle (using a 585: * MessageFormat). 586: * 587: * @param key the resourcebundle key 588: * @param parameter the parameter for the message 589: * @return the formated string 590: */ 591: public String formatMessage(final String key, final Object parameter) 592: { 593: return formatMessage(key, new Object[]{parameter}); 594: } 595: 596: /** 597: * Formats the message stored in the resource bundle (using a 598: * MessageFormat). 599: * 600: * @param key the resourcebundle key 601: * @param par1 the first parameter for the message 602: * @param par2 the second parameter for the message 603: * @return the formated string 604: */ 605: public String formatMessage(final String key, 606: final Object par1, 607: final Object par2) 608: { 609: return formatMessage(key, new Object[]{par1, par2}); 610: } 611: 612: /** 613: * Formats the message stored in the resource bundle (using a 614: * MessageFormat). 615: * 616: * @param key the resourcebundle key 617: * @param parameters the parameter collection for the message 618: * @return the formated string 619: */ 620: public String formatMessage(final String key, final Object[] parameters) 621: { 622: final MessageFormat format = new MessageFormat(getString(key)); 623: format.setLocale(getLocale()); 624: return format.format(parameters); 625: } 626: 627: /** 628: * Returns the current locale for this resource bundle. 629: * 630: * @return the locale. 631: */ 632: public Locale getLocale() 633: { 634: return locale; 635: } 636: }