001/* UIDefaults.java -- database for all settings and interface bindings.
002   Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing;
040
041import java.awt.Color;
042import java.awt.Dimension;
043import java.awt.Font;
044import java.awt.Insets;
045import java.beans.PropertyChangeListener;
046import java.beans.PropertyChangeSupport;
047import java.lang.reflect.Method;
048import java.util.Hashtable;
049import java.util.LinkedList;
050import java.util.ListIterator;
051import java.util.Locale;
052import java.util.MissingResourceException;
053import java.util.ResourceBundle;
054
055import javax.swing.border.Border;
056import javax.swing.plaf.ComponentUI;
057import javax.swing.plaf.InputMapUIResource;
058
059/**
060 * UIDefaults is a database where all settings and interface bindings are
061 * stored into. A PLAF implementation fills one of these (see for example
062 * plaf/basic/BasicLookAndFeel.java) with "ButtonUI" -> new BasicButtonUI().
063 *
064 * @author Ronald Veldema (rveldema@cs.vu.nl)
065 */
066public class UIDefaults extends Hashtable<Object, Object>
067{
068
069  /** Our ResourceBundles. */
070  private LinkedList bundles;
071
072  /** The default locale. */
073  private Locale defaultLocale;
074
075  /** We use this for firing PropertyChangeEvents. */
076  private PropertyChangeSupport propertyChangeSupport;
077
078  /**
079   * Used for lazy instantiation of UIDefaults values so that they are not
080   * all loaded when a Swing application starts up, but only the values that
081   * are really needed. An <code>ActiveValue</code> is newly instantiated
082   * every time when the value is requested, as opposed to the normal
083   * {@link LazyValue} that is only instantiated once.
084   */
085  public static interface ActiveValue
086  {
087    Object createValue(UIDefaults table);
088  }
089
090  public static class LazyInputMap implements LazyValue
091  {
092    Object[] bind;
093    public LazyInputMap(Object[] bindings)
094    {
095      bind = bindings;
096    }
097    public Object createValue(UIDefaults table)
098    {
099      InputMapUIResource im = new InputMapUIResource();
100      for (int i = 0; 2 * i + 1 < bind.length; ++i)
101        {
102          Object curr = bind[2 * i];
103          if (curr instanceof KeyStroke)
104            im.put((KeyStroke) curr, bind[2 * i + 1]);
105          else
106            im.put(KeyStroke.getKeyStroke((String) curr),
107                  bind[2 * i + 1]);
108        }
109      return im;
110    }
111  }
112
113  /**
114   * Used for lazy instantiation of UIDefaults values so that they are not
115   * all loaded when a Swing application starts up, but only the values that
116   * are really needed. A <code>LazyValue</code> is only instantiated once,
117   * as opposed to the {@link ActiveValue} that is newly created every time
118   * it is requested.
119   */
120  public static interface LazyValue
121  {
122    Object createValue(UIDefaults table);
123  }
124
125  public static class ProxyLazyValue implements LazyValue
126  {
127    LazyValue inner;
128    public ProxyLazyValue(String s)
129    {
130      final String className = s;
131      inner = new LazyValue()
132        {
133          public Object createValue(UIDefaults table)
134          {
135            try
136              {
137                return Class
138                  .forName(className)
139                  .getConstructor(new Class[] {})
140                  .newInstance(new Object[] {});
141              }
142            catch (Exception e)
143              {
144                return null;
145              }
146          }
147        };
148    }
149
150    public ProxyLazyValue(String c, String m)
151    {
152      final String className = c;
153      final String methodName = m;
154      inner = new LazyValue()
155        {
156          public Object createValue(UIDefaults table)
157          {
158            try
159              {
160                return Class
161                  .forName(className)
162                  .getMethod(methodName, new Class[] {})
163                  .invoke(null, new Object[] {});
164              }
165            catch (Exception e)
166              {
167                return null;
168              }
169          }
170        };
171    }
172
173    public ProxyLazyValue(String c, Object[] os)
174    {
175      final String className = c;
176      final Object[] objs = os;
177      final Class[] clss = new Class[objs.length];
178      for (int i = 0; i < objs.length; ++i)
179        {
180          clss[i] = objs[i].getClass();
181        }
182      inner = new LazyValue()
183        {
184          public Object createValue(UIDefaults table)
185          {
186            try
187              {
188                return Class
189                  .forName(className)
190                  .getConstructor(clss)
191                  .newInstance(objs);
192              }
193            catch (Exception e)
194              {
195                return null;
196              }
197          }
198        };
199    }
200
201    public ProxyLazyValue(String c, String m, Object[] os)
202    {
203      final String className = c;
204      final String methodName = m;
205      final Object[] objs = os;
206      final Class[] clss = new Class[objs.length];
207      for (int i = 0; i < objs.length; ++i)
208        {
209          clss[i] = objs[i].getClass();
210        }
211      inner = new LazyValue()
212        {
213          public Object createValue(UIDefaults table)
214          {
215            try
216              {
217                return Class
218                  .forName(className)
219                  .getMethod(methodName, clss)
220                  .invoke(null, objs);
221              }
222            catch (Exception e)
223              {
224                return null;
225              }
226          }
227        };
228    }
229
230    public Object createValue(UIDefaults table)
231    {
232      return inner.createValue(table);
233    }
234  }
235
236  /** Our serialVersionUID for serialization. */
237  private static final long serialVersionUID = 7341222528856548117L;
238
239  /**
240   * Constructs a new empty UIDefaults instance.
241   */
242  public UIDefaults()
243  {
244    bundles = new LinkedList();
245    defaultLocale = Locale.getDefault();
246    propertyChangeSupport = new PropertyChangeSupport(this);
247  }
248
249  /**
250   * Constructs a new UIDefaults instance and loads the specified entries.
251   * The entries are expected to come in pairs, that means
252   * <code>entries[0]</code> is a key, <code>entries[1]</code> is a value,
253   * <code>entries[2]</code> a key and so forth.
254   *
255   * @param entries the entries to initialize the UIDefaults instance with
256   */
257  public UIDefaults(Object[] entries)
258  {
259    this();
260
261    for (int i = 0; (2 * i + 1) < entries.length; ++i)
262      put(entries[2 * i], entries[2 * i + 1]);
263  }
264
265  /**
266   * Returns the entry for the specified <code>key</code> in the default
267   * locale.
268   *
269   * @return the entry for the specified <code>key</code>
270   */
271  public Object get(Object key)
272  {
273    return this.get(key, getDefaultLocale());
274  }
275
276  /**
277   * Returns the entry for the specified <code>key</code> in the Locale
278   * <code>loc</code>.
279   *
280   * @param key the key for which we return the value
281   * @param loc the locale
282   */
283  public Object get(Object key, Locale loc)
284  {
285    Object obj = null;
286
287    if (super.containsKey(key))
288      {
289        obj = super.get(key);
290      }
291    else if (key instanceof String)
292      {
293        String keyString = (String) key;
294        ListIterator i = bundles.listIterator(0);
295        while (i.hasNext())
296          {
297            String bundle_name = (String) i.next();
298            ResourceBundle res =
299              ResourceBundle.getBundle(bundle_name, loc);
300            if (res != null)
301              {
302                try
303                  {
304                    obj = res.getObject(keyString);
305                    break;
306                  }
307                catch (MissingResourceException me)
308                  {
309                    // continue, this bundle has no such key
310                  }
311              }
312          }
313      }
314
315    // now we've found the object, resolve it.
316    // nb: LazyValues aren't supported in resource bundles, so it's correct
317    // to insert their results in the locale-less hashtable.
318
319    if (obj == null)
320      return null;
321
322    if (obj instanceof LazyValue)
323      {
324        Object resolved = ((LazyValue) obj).createValue(this);
325        super.remove(key);
326        super.put(key, resolved);
327        return resolved;
328      }
329    else if (obj instanceof ActiveValue)
330      {
331        return ((ActiveValue) obj).createValue(this);
332      }
333
334    return obj;
335  }
336
337  /**
338   * Puts a key and value into this UIDefaults object.<br>
339   * In contrast to
340   * {@link java.util.Hashtable}s <code>null</code>-values are accepted
341   * here and treated like #remove(key).
342   * <br>
343   * This fires a PropertyChangeEvent with key as name and the old and new
344   * values.
345   *
346   * @param key the key to put into the map
347   * @param value the value to put into the map
348   *
349   * @return the old value for key or <code>null</code> if <code>key</code>
350   *     had no value assigned
351   */
352  public Object put(Object key, Object value)
353  {
354    Object old = checkAndPut(key, value);
355
356    if (key instanceof String && old != value)
357      firePropertyChange((String) key, old, value);
358    return old;
359  }
360
361  /**
362   * Puts a set of key-value pairs into the map.
363   * The entries are expected to come in pairs, that means
364   * <code>entries[0]</code> is a key, <code>entries[1]</code> is a value,
365   * <code>entries[2]</code> a key and so forth.
366   * <br>
367   * If a value is <code>null</code> it is treated like #remove(key).
368   * <br>
369   * This unconditionally fires a PropertyChangeEvent with
370   * <code>&apos;UIDefaults&apos;</code> as name and <code>null</code> for
371   * old and new value.
372   *
373   * @param entries the entries to be put into the map
374   */
375  public void putDefaults(Object[] entries)
376  {
377    for (int i = 0; (2 * i + 1) < entries.length; ++i)
378  {
379        checkAndPut(entries[2 * i], entries[2 * i + 1]);
380      }
381    firePropertyChange("UIDefaults", null, null);
382  }
383
384  /**
385   * Checks the value for <code>null</code> and put it into the Hashtable, if
386   * it is not <code>null</code>. If the value is <code>null</code> then
387   * remove the corresponding key.
388   *
389   * @param key the key to put into this UIDefauls table
390   * @param value the value to put into this UIDefaults table
391   *
392   * @return the old value for <code>key</code>
393   */
394  private Object checkAndPut(Object key, Object value)
395  {
396    Object old;
397
398    if (value != null)
399      old = super.put(key, value);
400    else
401      old = super.remove(key);
402
403    return old;
404  }
405
406  /**
407   * Returns a font entry for the default locale.
408   *
409   * @param key the key to the requested entry
410   *
411   * @return the font entry for <code>key</code> or null if no such entry
412   *     exists
413   */
414  public Font getFont(Object key)
415  {
416    Object o = get(key);
417    return o instanceof Font ? (Font) o : null;
418  }
419
420  /**
421   * Returns a font entry for a specic locale.
422   *
423   * @param key the key to the requested entry
424   * @param locale the locale to the requested entry
425   *
426   * @return the font entry for <code>key</code> or null if no such entry
427   *     exists
428   */
429  public Font getFont(Object key, Locale locale)
430  {
431    Object o = get(key, locale);
432    return o instanceof Font ? (Font) o : null;
433  }
434
435  /**
436   * Returns a color entry for the default locale.
437   *
438   * @param key the key to the requested entry
439   *
440   * @return the color entry for <code>key</code> or null if no such entry
441   *     exists
442   */
443  public Color getColor(Object key)
444  {
445    Object o = get(key);
446    return o instanceof Color ? (Color) o : null;
447  }
448
449  /**
450   * Returns a color entry for a specic locale.
451   *
452   * @param key the key to the requested entry
453   * @param locale the locale to the requested entry
454   *
455   * @return the color entry for <code>key</code> or null if no such entry
456   *     exists
457   */
458  public Color getColor(Object key, Locale locale)
459  {
460    Object o = get(key, locale);
461    return o instanceof Color ? (Color) o : null;
462  }
463
464  /**
465   * Returns an icon entry for the default locale.
466   *
467   * @param key the key to the requested entry
468   *
469   * @return the icon entry for <code>key</code> or null if no such entry
470   *     exists
471   */
472  public Icon getIcon(Object key)
473  {
474    Object o = get(key);
475    return o instanceof Icon ? (Icon) o : null;
476  }
477
478  /**
479   * Returns an icon entry for a specic locale.
480   *
481   * @param key the key to the requested entry
482   * @param locale the locale to the requested entry
483   *
484   * @return the icon entry for <code>key</code> or null if no such entry
485   *     exists
486   */
487  public Icon getIcon(Object key, Locale locale)
488  {
489    Object o = get(key, locale);
490    return o instanceof Icon ? (Icon) o : null;
491  }
492
493  /**
494   * Returns a border entry for the default locale.
495   *
496   * @param key the key to the requested entry
497   *
498   * @return the border entry for <code>key</code> or null if no such entry
499   *     exists
500   */
501  public Border getBorder(Object key)
502  {
503    Object o = get(key);
504    return o instanceof Border ? (Border) o : null;
505  }
506
507  /**
508   * Returns a border entry for a specic locale.
509   *
510   * @param key the key to the requested entry
511   * @param locale the locale to the requested entry
512   *
513   * @return the border entry for <code>key</code> or null if no such entry
514   *     exists
515   */
516  public Border getBorder(Object key, Locale locale)
517  {
518    Object o = get(key, locale);
519    return o instanceof Border ? (Border) o : null;
520  }
521
522  /**
523   * Returns a string entry for the default locale.
524   *
525   * @param key the key to the requested entry
526   *
527   * @return the string entry for <code>key</code> or null if no such entry
528   *     exists
529   */
530  public String getString(Object key)
531  {
532    Object o = get(key);
533    return o instanceof String ? (String) o : null;
534  }
535
536  /**
537   * Returns a string entry for a specic locale.
538   *
539   * @param key the key to the requested entry
540   * @param locale the locale to the requested entry
541   *
542   * @return the string entry for <code>key</code> or null if no such entry
543   *     exists
544   */
545  public String getString(Object key, Locale locale)
546  {
547    Object o = get(key, locale);
548    return o instanceof String ? (String) o : null;
549  }
550
551  /**
552   * Returns an integer entry for the default locale.
553   *
554   * @param key the key to the requested entry
555   *
556   * @return the integer entry for <code>key</code> or null if no such entry
557   *     exists
558   */
559  public int getInt(Object key)
560  {
561    Object o = get(key);
562    return o instanceof Integer ? ((Integer) o).intValue() : 0;
563  }
564
565  /**
566   * Returns an integer entry for a specic locale.
567   *
568   * @param key the key to the requested entry
569   * @param locale the locale to the requested entry
570   *
571   * @return the integer entry for <code>key</code> or null if no such entry
572   *     exists
573   */
574  public int getInt(Object key, Locale locale)
575  {
576    Object o = get(key, locale);
577    return o instanceof Integer ? ((Integer) o).intValue() : 0;
578  }
579
580  /**
581   * Returns a boolean entry for the default locale.
582   *
583   * @param key the key to the requested entry
584   *
585   * @return The boolean entry for <code>key</code> or <code>false</code> if no
586   *         such entry exists.
587   */
588  public boolean getBoolean(Object key)
589  {
590    return Boolean.TRUE.equals(get(key));
591  }
592
593  /**
594   * Returns a boolean entry for a specic locale.
595   *
596   * @param key the key to the requested entry
597   * @param locale the locale to the requested entry
598   *
599   * @return the boolean entry for <code>key</code> or null if no such entry
600   *     exists
601   */
602  public boolean getBoolean(Object key, Locale locale)
603  {
604    return Boolean.TRUE.equals(get(key, locale));
605  }
606
607  /**
608   * Returns an insets entry for the default locale.
609   *
610   * @param key the key to the requested entry
611   *
612   * @return the insets entry for <code>key</code> or null if no such entry
613   *     exists
614   */
615  public Insets getInsets(Object key)
616  {
617    Object o = get(key);
618    return o instanceof Insets ? (Insets) o : null;
619  }
620
621  /**
622   * Returns an insets entry for a specic locale.
623   *
624   * @param key the key to the requested entry
625   * @param locale the locale to the requested entry
626   *
627   * @return the boolean entry for <code>key</code> or null if no such entry
628   *     exists
629   */
630  public Insets getInsets(Object key, Locale locale)
631  {
632    Object o = get(key, locale);
633    return o instanceof Insets ? (Insets) o : null;
634  }
635
636  /**
637   * Returns a dimension entry for the default locale.
638   *
639   * @param key the key to the requested entry
640   *
641   * @return the dimension entry for <code>key</code> or null if no such entry
642   *     exists
643   */
644  public Dimension getDimension(Object key)
645  {
646    Object o = get(key);
647    return o instanceof Dimension ? (Dimension) o : null;
648  }
649
650  /**
651   * Returns a dimension entry for a specic locale.
652   *
653   * @param key the key to the requested entry
654   * @param locale the locale to the requested entry
655   *
656   * @return the boolean entry for <code>key</code> or null if no such entry
657   *     exists
658   */
659  public Dimension getDimension(Object key, Locale locale)
660  {
661    Object o = get(key, locale);
662    return o instanceof Dimension ? (Dimension) o : null;
663  }
664
665  /**
666   * Returns the ComponentUI class that renders a component. <code>id</code>
667   * is the ID for which the String value of the classname is stored in
668   * this UIDefaults map.
669   *
670   * @param id the ID of the UI class
671   * @param loader the ClassLoader to use
672   *
673   * @return the UI class for <code>id</code>
674   */
675  public Class<? extends ComponentUI> getUIClass(String id, ClassLoader loader)
676  {
677    String className = (String) get(id);
678    if (className == null)
679      return null;
680    try
681      {
682        if (loader == null)
683          loader = ClassLoader.getSystemClassLoader();
684        return (Class<? extends ComponentUI>) loader.loadClass (className);
685      }
686    catch (Exception e)
687      {
688        return null;
689      }
690  }
691
692  /**
693   * Returns the ComponentUI class that renders a component. <code>id</code>
694   * is the ID for which the String value of the classname is stored in
695   * this UIDefaults map.
696   *
697   * @param id the ID of the UI class
698   *
699   * @return the UI class for <code>id</code>
700   */
701  public Class<? extends ComponentUI> getUIClass(String id)
702  {
703    return getUIClass (id, null);
704  }
705
706  /**
707   * If a key is requested in #get(key) that has no value, this method
708   * is called before returning <code>null</code>.
709   *
710   * @param msg the error message
711   */
712  protected void getUIError(String msg)
713  {
714    System.err.println ("UIDefaults.getUIError: " + msg);
715  }
716
717  /**
718   * Returns the {@link ComponentUI} for the specified {@link JComponent}.
719   *
720   * @param target the component for which the ComponentUI is requested
721   *
722   * @return the {@link ComponentUI} for the specified {@link JComponent}
723   */
724  public ComponentUI getUI(JComponent target)
725  {
726    String classId = target.getUIClassID ();
727    Class cls = getUIClass (classId);
728    if (cls == null)
729      {
730        getUIError ("failed to locate UI class:" + classId);
731        return null;
732      }
733
734    Method factory;
735
736    try
737      {
738        factory = cls.getMethod ("createUI", new Class[] { JComponent.class } );
739      }
740    catch (NoSuchMethodException nme)
741      {
742        getUIError ("failed to locate createUI method on " + cls.toString ());
743        return null;
744      }
745
746    try
747      {
748        return (ComponentUI) factory.invoke (null, new Object[] { target });
749      }
750    catch (java.lang.reflect.InvocationTargetException ite)
751      {
752        getUIError ("InvocationTargetException ("+ ite.getTargetException()
753                    +") calling createUI(...) on " + cls.toString ());
754        return null;
755      }
756    catch (Exception e)
757      {
758        getUIError ("exception calling createUI(...) on " + cls.toString ());
759        return null;
760      }
761  }
762
763  /**
764   * Adds a {@link PropertyChangeListener} to this UIDefaults map.
765   * Registered PropertyChangeListener are notified when values
766   * are beeing put into this UIDefaults map.
767   *
768   * @param listener the PropertyChangeListener to add
769   */
770  public void addPropertyChangeListener(PropertyChangeListener listener)
771  {
772    propertyChangeSupport.addPropertyChangeListener(listener);
773  }
774
775  /**
776   * Removes a PropertyChangeListener from this UIDefaults map.
777   *
778   * @param listener the PropertyChangeListener to remove
779   */
780  public void removePropertyChangeListener(PropertyChangeListener listener)
781  {
782    propertyChangeSupport.removePropertyChangeListener(listener);
783  }
784
785  /**
786   * Returns an array of all registered PropertyChangeListeners.
787   *
788   * @return all registered PropertyChangeListeners
789   */
790  public PropertyChangeListener[] getPropertyChangeListeners()
791  {
792    return propertyChangeSupport.getPropertyChangeListeners();
793  }
794
795  /**
796   * Fires a PropertyChangeEvent.
797   *
798   * @param property the property name
799   * @param oldValue the old value
800   * @param newValue the new value
801   */
802  protected void firePropertyChange(String property,
803                                    Object oldValue, Object newValue)
804  {
805    propertyChangeSupport.firePropertyChange(property, oldValue, newValue);
806  }
807
808  /**
809   * Adds a ResourceBundle for localized values.
810   *
811   * @param name the name of the ResourceBundle to add
812   */
813  public void addResourceBundle(String name)
814  {
815    bundles.addFirst(name);
816  }
817
818  /**
819   * Removes a ResourceBundle.
820   *
821   * @param name the name of the ResourceBundle to remove
822   */
823  public void removeResourceBundle(String name)
824  {
825    bundles.remove(name);
826  }
827
828  /**
829   * Sets the current locale to <code>loc</code>.
830   *
831   * @param loc the Locale to be set
832   */
833  public void setDefaultLocale(Locale loc)
834  {
835    defaultLocale = loc;
836  }
837
838  /**
839   * Returns the current default locale.
840   *
841   * @return the current default locale
842   */
843  public Locale getDefaultLocale()
844  {
845    return defaultLocale;
846  }
847}