001    /* UIDefaults.java -- database for all settings and interface bindings.
002       Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing;
040    
041    import java.awt.Color;
042    import java.awt.Dimension;
043    import java.awt.Font;
044    import java.awt.Insets;
045    import java.beans.PropertyChangeListener;
046    import java.beans.PropertyChangeSupport;
047    import java.lang.reflect.Method;
048    import java.util.Hashtable;
049    import java.util.LinkedList;
050    import java.util.ListIterator;
051    import java.util.Locale;
052    import java.util.MissingResourceException;
053    import java.util.ResourceBundle;
054    
055    import javax.swing.border.Border;
056    import javax.swing.plaf.ComponentUI;
057    import 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     */
066    public 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    }