001 /* ======================================================================== 002 * JCommon : a free general purpose class library for the Java(tm) platform 003 * ======================================================================== 004 * 005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jcommon/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * --------------------- 028 * ReadOnlyIterator.java 029 * --------------------- 030 * (C)opyright 2003, 2004, by Thomas Morgner and Contributors. 031 * 032 * Original Author: Thomas Morgner; 033 * Contributor(s): -; 034 * 035 * $Id: ResourceBundleSupport.java,v 1.11 2008/09/10 09:24:41 mungady Exp $ 036 * 037 * Changes 038 * ------------------------- 039 */ 040 package org.jfree.util; 041 042 import java.awt.Image; 043 import java.awt.Toolkit; 044 import java.awt.event.InputEvent; 045 import java.awt.event.KeyEvent; 046 import java.awt.image.BufferedImage; 047 import java.lang.reflect.Field; 048 import java.net.URL; 049 import java.text.MessageFormat; 050 import java.util.Arrays; 051 import java.util.Locale; 052 import java.util.MissingResourceException; 053 import java.util.ResourceBundle; 054 import java.util.TreeMap; 055 import java.util.TreeSet; 056 import javax.swing.Icon; 057 import javax.swing.ImageIcon; 058 import javax.swing.JMenu; 059 import javax.swing.KeyStroke; 060 061 /** 062 * An utility class to ease up using property-file resource bundles. 063 * <p/> 064 * The class support references within the resource bundle set to minimize the 065 * occurence of duplicate keys. References are given in the format: 066 * <pre> 067 * a.key.name=@referenced.key 068 * </pre> 069 * <p/> 070 * A lookup to a key in an other resource bundle should be written by 071 * <pre> 072 * a.key.name=@@resourcebundle_name@referenced.key 073 * </pre> 074 * 075 * @author Thomas Morgner 076 */ 077 public class ResourceBundleSupport 078 { 079 /** 080 * The resource bundle that will be used for local lookups. 081 */ 082 private ResourceBundle resources; 083 084 /** 085 * A cache for string values, as looking up the cache is faster than looking 086 * up the value in the bundle. 087 */ 088 private TreeMap cache; 089 /** 090 * The current lookup path when performing non local lookups. This prevents 091 * infinite loops during such lookups. 092 */ 093 private TreeSet lookupPath; 094 095 /** 096 * The name of the local resource bundle. 097 */ 098 private String resourceBase; 099 100 /** 101 * The locale for this bundle. 102 */ 103 private Locale locale; 104 105 /** 106 * Creates a new instance. 107 * 108 * @param locale the locale. 109 * @param baseName the base name of the resource bundle, a fully qualified 110 * class name 111 */ 112 public ResourceBundleSupport(final Locale locale, final String baseName) 113 { 114 this(locale, ResourceBundle.getBundle(baseName, locale), baseName); 115 } 116 117 /** 118 * Creates a new instance. 119 * 120 * @param locale the locale for which this resource bundle is 121 * created. 122 * @param resourceBundle the resourcebundle 123 * @param baseName the base name of the resource bundle, a fully 124 * qualified class name 125 */ 126 protected ResourceBundleSupport(final Locale locale, 127 final ResourceBundle resourceBundle, 128 final String baseName) 129 { 130 if (locale == null) 131 { 132 throw new NullPointerException("Locale must not be null"); 133 } 134 if (resourceBundle == null) 135 { 136 throw new NullPointerException("Resources must not be null"); 137 } 138 if (baseName == null) 139 { 140 throw new NullPointerException("BaseName must not be null"); 141 } 142 this.locale = locale; 143 this.resources = resourceBundle; 144 this.resourceBase = baseName; 145 this.cache = new TreeMap(); 146 this.lookupPath = new TreeSet(); 147 } 148 149 /** 150 * Creates a new instance. 151 * 152 * @param locale the locale for which the resource bundle is 153 * created. 154 * @param resourceBundle the resourcebundle 155 */ 156 public ResourceBundleSupport(final Locale locale, 157 final ResourceBundle resourceBundle) 158 { 159 this(locale, resourceBundle, resourceBundle.toString()); 160 } 161 162 /** 163 * Creates a new instance. 164 * 165 * @param baseName the base name of the resource bundle, a fully qualified 166 * class name 167 */ 168 public ResourceBundleSupport(final String baseName) 169 { 170 this(Locale.getDefault(), ResourceBundle.getBundle(baseName), baseName); 171 } 172 173 /** 174 * Creates a new instance. 175 * 176 * @param resourceBundle the resourcebundle 177 * @param baseName the base name of the resource bundle, a fully 178 * qualified class name 179 */ 180 protected ResourceBundleSupport(final ResourceBundle resourceBundle, 181 final String baseName) 182 { 183 this(Locale.getDefault(), resourceBundle, baseName); 184 } 185 186 /** 187 * Creates a new instance. 188 * 189 * @param resourceBundle the resourcebundle 190 */ 191 public ResourceBundleSupport(final ResourceBundle resourceBundle) 192 { 193 this(Locale.getDefault(), resourceBundle, resourceBundle.toString()); 194 } 195 196 /** 197 * The base name of the resource bundle. 198 * 199 * @return the resource bundle's name. 200 */ 201 protected final String getResourceBase() 202 { 203 return this.resourceBase; 204 } 205 206 /** 207 * Gets a string for the given key from this resource bundle or one of its 208 * parents. If the key is a link, the link is resolved and the referenced 209 * string is returned instead. 210 * 211 * @param key the key for the desired string 212 * @return the string for the given key 213 * @throws NullPointerException if <code>key</code> is <code>null</code> 214 * @throws MissingResourceException if no object for the given key can be 215 * found 216 * @throws ClassCastException if the object found for the given key is 217 * not a string 218 */ 219 public synchronized String getString(final String key) 220 { 221 final String retval = (String) this.cache.get(key); 222 if (retval != null) 223 { 224 return retval; 225 } 226 this.lookupPath.clear(); 227 return internalGetString(key); 228 } 229 230 /** 231 * Performs the lookup for the given key. If the key points to a link the 232 * link is resolved and that key is looked up instead. 233 * 234 * @param key the key for the string 235 * @return the string for the given key 236 */ 237 protected String internalGetString(final String key) 238 { 239 if (this.lookupPath.contains(key)) 240 { 241 throw new MissingResourceException 242 ("InfiniteLoop in resource lookup", 243 getResourceBase(), this.lookupPath.toString()); 244 } 245 final String fromResBundle = this.resources.getString(key); 246 if (fromResBundle.startsWith("@@")) 247 { 248 // global forward ... 249 final int idx = fromResBundle.indexOf('@', 2); 250 if (idx == -1) 251 { 252 throw new MissingResourceException 253 ("Invalid format for global lookup key.", getResourceBase(), key); 254 } 255 try 256 { 257 final ResourceBundle res = ResourceBundle.getBundle 258 (fromResBundle.substring(2, idx)); 259 return res.getString(fromResBundle.substring(idx + 1)); 260 } 261 catch (Exception e) 262 { 263 Log.error("Error during global lookup", e); 264 throw new MissingResourceException 265 ("Error during global lookup", getResourceBase(), key); 266 } 267 } 268 else if (fromResBundle.startsWith("@")) 269 { 270 // local forward ... 271 final String newKey = fromResBundle.substring(1); 272 this.lookupPath.add(key); 273 final String retval = internalGetString(newKey); 274 275 this.cache.put(key, retval); 276 return retval; 277 } 278 else 279 { 280 this.cache.put(key, fromResBundle); 281 return fromResBundle; 282 } 283 } 284 285 /** 286 * Returns an scaled icon suitable for buttons or menus. 287 * 288 * @param key the name of the resource bundle key 289 * @param large true, if the image should be scaled to 24x24, or false for 290 * 16x16 291 * @return the icon. 292 */ 293 public Icon getIcon(final String key, final boolean large) 294 { 295 final String name = getString(key); 296 return createIcon(name, true, large); 297 } 298 299 /** 300 * Returns an unscaled icon. 301 * 302 * @param key the name of the resource bundle key 303 * @return the icon. 304 */ 305 public Icon getIcon(final String key) 306 { 307 final String name = getString(key); 308 return createIcon(name, false, false); 309 } 310 311 /** 312 * Returns the mnemonic stored at the given resourcebundle key. The mnemonic 313 * should be either the symbolic name of one of the KeyEvent.VK_* constants 314 * (without the 'VK_') or the character for that key. 315 * <p/> 316 * For the enter key, the resource bundle would therefore either contain 317 * "ENTER" or "\n". 318 * <pre> 319 * a.resourcebundle.key=ENTER 320 * an.other.resourcebundle.key=\n 321 * </pre> 322 * 323 * @param key the resourcebundle key 324 * @return the mnemonic 325 */ 326 public Integer getMnemonic(final String key) 327 { 328 final String name = getString(key); 329 return createMnemonic(name); 330 } 331 332 /** 333 * Returns an optional mnemonic. 334 * 335 * @param key the key. 336 * 337 * @return The mnemonic. 338 */ 339 public Integer getOptionalMnemonic(final String key) 340 { 341 final String name = getString(key); 342 if (name != null && name.length() > 0) 343 { 344 return createMnemonic(name); 345 } 346 return null; 347 } 348 349 /** 350 * Returns the keystroke stored at the given resourcebundle key. 351 * <p/> 352 * The keystroke will be composed of a simple key press and the plattform's 353 * MenuKeyMask. 354 * <p/> 355 * The keystrokes character key should be either the symbolic name of one of 356 * the KeyEvent.VK_* constants or the character for that key. 357 * <p/> 358 * For the 'A' key, the resource bundle would therefore either contain 359 * "VK_A" or "a". 360 * <pre> 361 * a.resourcebundle.key=VK_A 362 * an.other.resourcebundle.key=a 363 * </pre> 364 * 365 * @param key the resourcebundle key 366 * @return the mnemonic 367 * @see Toolkit#getMenuShortcutKeyMask() 368 */ 369 public KeyStroke getKeyStroke(final String key) 370 { 371 return getKeyStroke(key, getMenuKeyMask()); 372 } 373 374 /** 375 * Returns an optional key stroke. 376 * 377 * @param key the key. 378 * 379 * @return The key stroke. 380 */ 381 public KeyStroke getOptionalKeyStroke(final String key) 382 { 383 return getOptionalKeyStroke(key, getMenuKeyMask()); 384 } 385 386 /** 387 * Returns the keystroke stored at the given resourcebundle key. 388 * <p/> 389 * The keystroke will be composed of a simple key press and the given 390 * KeyMask. If the KeyMask is zero, a plain Keystroke is returned. 391 * <p/> 392 * The keystrokes character key should be either the symbolic name of one of 393 * the KeyEvent.VK_* constants or the character for that key. 394 * <p/> 395 * For the 'A' key, the resource bundle would therefore either contain 396 * "VK_A" or "a". 397 * <pre> 398 * a.resourcebundle.key=VK_A 399 * an.other.resourcebundle.key=a 400 * </pre> 401 * 402 * @param key the resourcebundle key. 403 * @param mask the mask. 404 * 405 * @return the mnemonic 406 * @see Toolkit#getMenuShortcutKeyMask() 407 */ 408 public KeyStroke getKeyStroke(final String key, final int mask) 409 { 410 final String name = getString(key); 411 return KeyStroke.getKeyStroke(createMnemonic(name).intValue(), mask); 412 } 413 414 /** 415 * Returns an optional key stroke. 416 * 417 * @param key the key. 418 * @param mask the mask. 419 * 420 * @return The key stroke. 421 */ 422 public KeyStroke getOptionalKeyStroke(final String key, final int mask) 423 { 424 final String name = getString(key); 425 426 if (name != null && name.length() > 0) 427 { 428 return KeyStroke.getKeyStroke(createMnemonic(name).intValue(), mask); 429 } 430 return null; 431 } 432 433 /** 434 * Returns a JMenu created from a resource bundle definition. 435 * <p/> 436 * The menu definition consists of two keys, the name of the menu and the 437 * mnemonic for that menu. Both keys share a common prefix, which is 438 * extended by ".name" for the name of the menu and ".mnemonic" for the 439 * mnemonic. 440 * <p/> 441 * <pre> 442 * # define the file menu 443 * menu.file.name=File 444 * menu.file.mnemonic=F 445 * </pre> 446 * The menu definition above can be used to create the menu by calling 447 * <code>createMenu ("menu.file")</code>. 448 * 449 * @param keyPrefix the common prefix for that menu 450 * @return the created menu 451 */ 452 public JMenu createMenu(final String keyPrefix) 453 { 454 final JMenu retval = new JMenu(); 455 retval.setText(getString(keyPrefix + ".name")); 456 retval.setMnemonic(getMnemonic(keyPrefix + ".mnemonic").intValue()); 457 return retval; 458 } 459 460 /** 461 * Returns a URL pointing to a resource located in the classpath. The 462 * resource is looked up using the given key. 463 * <p/> 464 * Example: The load a file named 'logo.gif' which is stored in a java 465 * package named 'org.jfree.resources': 466 * <pre> 467 * mainmenu.logo=org/jfree/resources/logo.gif 468 * </pre> 469 * The URL for that file can be queried with: <code>getResource("mainmenu.logo");</code>. 470 * 471 * @param key the key for the resource 472 * @return the resource URL 473 */ 474 public URL getResourceURL(final String key) 475 { 476 final String name = getString(key); 477 final URL in = ObjectUtilities.getResource(name, ResourceBundleSupport.class); 478 if (in == null) 479 { 480 Log.warn("Unable to find file in the class path: " + name + "; key=" + key); 481 } 482 return in; 483 } 484 485 486 /** 487 * Attempts to load an image from classpath. If this fails, an empty image 488 * icon is returned. 489 * 490 * @param resourceName the name of the image. The name should be a global 491 * resource name. 492 * @param scale true, if the image should be scaled, false otherwise 493 * @param large true, if the image should be scaled to 24x24, or 494 * false for 16x16 495 * @return the image icon. 496 */ 497 private ImageIcon createIcon(final String resourceName, final boolean scale, 498 final boolean large) 499 { 500 final URL in = ObjectUtilities.getResource(resourceName, ResourceBundleSupport.class); 501 ; 502 if (in == null) 503 { 504 Log.warn("Unable to find file in the class path: " + resourceName); 505 return new ImageIcon(createTransparentImage(1, 1)); 506 } 507 final Image img = Toolkit.getDefaultToolkit().createImage(in); 508 if (img == null) 509 { 510 Log.warn("Unable to instantiate the image: " + resourceName); 511 return new ImageIcon(createTransparentImage(1, 1)); 512 } 513 if (scale) 514 { 515 if (large) 516 { 517 return new ImageIcon(img.getScaledInstance(24, 24, Image.SCALE_SMOOTH)); 518 } 519 return new ImageIcon(img.getScaledInstance(16, 16, Image.SCALE_SMOOTH)); 520 } 521 return new ImageIcon(img); 522 } 523 524 /** 525 * Creates the Mnemonic from the given String. The String consists of the 526 * name of the VK constants of the class KeyEvent without VK_*. 527 * 528 * @param keyString the string 529 * @return the mnemonic as integer 530 */ 531 private Integer createMnemonic(final String keyString) 532 { 533 if (keyString == null) 534 { 535 throw new NullPointerException("Key is null."); 536 } 537 if (keyString.length() == 0) 538 { 539 throw new IllegalArgumentException("Key is empty."); 540 } 541 int character = keyString.charAt(0); 542 if (keyString.startsWith("VK_")) 543 { 544 try 545 { 546 final Field f = KeyEvent.class.getField(keyString); 547 final Integer keyCode = (Integer) f.get(null); 548 character = keyCode.intValue(); 549 } 550 catch (Exception nsfe) 551 { 552 // ignore the exception ... 553 } 554 } 555 return new Integer(character); 556 } 557 558 /** 559 * Returns the plattforms default menu shortcut keymask. 560 * 561 * @return the default key mask. 562 */ 563 private int getMenuKeyMask() 564 { 565 try 566 { 567 return Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); 568 } 569 catch (UnsupportedOperationException he) 570 { 571 // headless exception extends UnsupportedOperation exception, 572 // but the HeadlessException is not defined in older JDKs... 573 return InputEvent.CTRL_MASK; 574 } 575 } 576 577 /** 578 * Creates a transparent image. These can be used for aligning menu items. 579 * 580 * @param width the width. 581 * @param height the height. 582 * @return the created transparent image. 583 */ 584 private BufferedImage createTransparentImage(final int width, 585 final int height) 586 { 587 final BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 588 final int[] data = img.getRGB(0, 0, width, height, null, 0, width); 589 Arrays.fill(data, 0x00000000); 590 img.setRGB(0, 0, width, height, data, 0, width); 591 return img; 592 } 593 594 /** 595 * Creates a transparent icon. The Icon can be used for aligning menu 596 * items. 597 * 598 * @param width the width of the new icon 599 * @param height the height of the new icon 600 * @return the created transparent icon. 601 */ 602 public Icon createTransparentIcon(final int width, final int height) 603 { 604 return new ImageIcon(createTransparentImage(width, height)); 605 } 606 607 /** 608 * Formats the message stored in the resource bundle (using a 609 * MessageFormat). 610 * 611 * @param key the resourcebundle key 612 * @param parameter the parameter for the message 613 * @return the formated string 614 */ 615 public String formatMessage(final String key, final Object parameter) 616 { 617 return formatMessage(key, new Object[]{parameter}); 618 } 619 620 /** 621 * Formats the message stored in the resource bundle (using a 622 * MessageFormat). 623 * 624 * @param key the resourcebundle key 625 * @param par1 the first parameter for the message 626 * @param par2 the second parameter for the message 627 * @return the formated string 628 */ 629 public String formatMessage(final String key, 630 final Object par1, 631 final Object par2) 632 { 633 return formatMessage(key, new Object[]{par1, par2}); 634 } 635 636 /** 637 * Formats the message stored in the resource bundle (using a 638 * MessageFormat). 639 * 640 * @param key the resourcebundle key 641 * @param parameters the parameter collection for the message 642 * @return the formated string 643 */ 644 public String formatMessage(final String key, final Object[] parameters) 645 { 646 final MessageFormat format = new MessageFormat(getString(key)); 647 format.setLocale(getLocale()); 648 return format.format(parameters); 649 } 650 651 /** 652 * Returns the current locale for this resource bundle. 653 * 654 * @return the locale. 655 */ 656 public Locale getLocale() 657 { 658 return this.locale; 659 } 660 }