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