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