Source for org.jfree.util.ResourceBundleSupport

   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: }