/* * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.swing; import javax.swing.plaf.ComponentUI; import javax.swing.border.*; import javax.swing.event.SwingPropertyChangeSupport; import java.lang.reflect.*; import java.util.HashMap; import java.util.Map; import java.util.Enumeration; import java.util.Hashtable; import java.util.ResourceBundle; import java.util.ResourceBundle.Control; import java.util.Locale; import java.util.Vector; import java.util.MissingResourceException; import java.awt.Font; import java.awt.Color; import java.awt.Insets; import java.awt.Dimension; import java.lang.reflect.Method; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeEvent; import java.security.AccessController; import java.security.AccessControlContext; import java.security.PrivilegedAction; import sun.reflect.misc.MethodUtil; import sun.reflect.misc.ReflectUtil; import sun.util.CoreResourceBundleControl; /** * A table of defaults for Swing components. Applications can set/get * default values via the <code>UIManager</code>. * <p> * <strong>Warning:</strong> * Serialized objects of this class will not be compatible with * future Swing releases. The current serialization support is * appropriate for short term storage or RMI between applications running * the same version of Swing. As of 1.4, support for long term storage * of all JavaBeans<sup><font size="-2">TM</font></sup> * has been added to the <code>java.beans</code> package. * Please see {@link java.beans.XMLEncoder}. * * @see UIManager * @author Hans Muller */ public class UIDefaults extends Hashtable<Object,Object> { private static final Object PENDING = "Pending"; private SwingPropertyChangeSupport changeSupport; private Vector<String> resourceBundles; private Locale defaultLocale = Locale.getDefault(); /** * Maps from a Locale to a cached Map of the ResourceBundle. This is done * so as to avoid an exception being thrown when a value is asked for. * Access to this should be done while holding a lock on the * UIDefaults, eg synchronized(this). */ private Map<Locale, Map<String, Object>> resourceCache; /** * Creates an empty defaults table. */ public UIDefaults() { this(700, .75f); } /** * Creates an empty defaults table with the specified initial capacity and * load factor. * * @param initialCapacity the initial capacity of the defaults table * @param loadFactor the load factor of the defaults table * @see java.util.Hashtable * @since 1.6 */ public UIDefaults(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); resourceCache = new HashMap<Locale, Map<String, Object>>(); } /** * Creates a defaults table initialized with the specified * key/value pairs. For example: * <pre> Object[] uiDefaults = { "Font", new Font("Dialog", Font.BOLD, 12), "Color", Color.red, "five", new Integer(5) } UIDefaults myDefaults = new UIDefaults(uiDefaults); * </pre> * @param keyValueList an array of objects containing the key/value * pairs */ public UIDefaults(Object[] keyValueList) { super(keyValueList.length / 2); for(int i = 0; i < keyValueList.length; i += 2) { super.put(keyValueList[i], keyValueList[i + 1]); } } /** * Returns the value for key. If the value is a * <code>UIDefaults.LazyValue</code> then the real * value is computed with <code>LazyValue.createValue()</code>, * the table entry is replaced, and the real value is returned. * If the value is an <code>UIDefaults.ActiveValue</code> * the table entry is not replaced - the value is computed * with <code>ActiveValue.createValue()</code> for each * <code>get()</code> call. * * If the key is not found in the table then it is searched for in the list * of resource bundles maintained by this object. The resource bundles are * searched most recently added first using the locale returned by * <code>getDefaultLocale</code>. <code>LazyValues</code> and * <code>ActiveValues</code> are not supported in the resource bundles. * * @param key the desired key * @return the value for <code>key</code> * @see LazyValue * @see ActiveValue * @see java.util.Hashtable#get * @see #getDefaultLocale * @see #addResourceBundle * @since 1.4 */ public Object get(Object key) { Object value = getFromHashtable( key ); return (value != null) ? value : getFromResourceBundle(key, null); } /** * Looks up up the given key in our Hashtable and resolves LazyValues * or ActiveValues. */ private Object getFromHashtable(Object key) { /* Quickly handle the common case, without grabbing * a lock. */ Object value = super.get(key); if ((value != PENDING) && !(value instanceof ActiveValue) && !(value instanceof LazyValue)) { return value; } /* If the LazyValue for key is being constructed by another * thread then wait and then return the new value, otherwise drop * the lock and construct the ActiveValue or the LazyValue. * We use the special value PENDING to mark LazyValues that * are being constructed. */ synchronized(this) { value = super.get(key); if (value == PENDING) { do { try { this.wait(); } catch (InterruptedException e) { } value = super.get(key); } while(value == PENDING); return value; } else if (value instanceof LazyValue) { super.put(key, PENDING); } else if (!(value instanceof ActiveValue)) { return value; } } /* At this point we know that the value of key was * a LazyValue or an ActiveValue. */ if (value instanceof LazyValue) { try { /* If an exception is thrown we'll just put the LazyValue * back in the table. */ value = ((LazyValue)value).createValue(this); } finally { synchronized(this) { if (value == null) { super.remove(key); } else { super.put(key, value); } this.notifyAll(); } } } else { value = ((ActiveValue)value).createValue(this); } return value; } /** * Returns the value for key associated with the given locale. * If the value is a <code>UIDefaults.LazyValue</code> then the real * value is computed with <code>LazyValue.createValue()</code>, * the table entry is replaced, and the real value is returned. * If the value is an <code>UIDefaults.ActiveValue</code> * the table entry is not replaced - the value is computed * with <code>ActiveValue.createValue()</code> for each * <code>get()</code> call. * * If the key is not found in the table then it is searched for in the list * of resource bundles maintained by this object. The resource bundles are * searched most recently added first using the given locale. * <code>LazyValues</code> and <code>ActiveValues</code> are not supported * in the resource bundles. * * @param key the desired key * @param l the desired <code>locale</code> * @return the value for <code>key</code> * @see LazyValue * @see ActiveValue * @see java.util.Hashtable#get * @see #addResourceBundle * @since 1.4 */ public Object get(Object key, Locale l) { Object value = getFromHashtable( key ); return (value != null) ? value : getFromResourceBundle(key, l); } /** * Looks up given key in our resource bundles. */ private Object getFromResourceBundle(Object key, Locale l) { if( resourceBundles == null || resourceBundles.isEmpty() || !(key instanceof String) ) { return null; } // A null locale means use the default locale. if( l == null ) { if( defaultLocale == null ) return null; else l = defaultLocale; } synchronized(this) { return getResourceCache(l).get(key); } } /** * Returns a Map of the known resources for the given locale. */ private Map<String, Object> getResourceCache(Locale l) { Map<String, Object> values = resourceCache.get(l); if (values == null) { values = new TextAndMnemonicHashMap(); for (int i=resourceBundles.size()-1; i >= 0; i--) { String bundleName = resourceBundles.get(i); try { Control c = CoreResourceBundleControl.getRBControlInstance(bundleName); ResourceBundle b; if (c != null) { b = ResourceBundle.getBundle(bundleName, l, c); } else { b = ResourceBundle.getBundle(bundleName, l); } Enumeration keys = b.getKeys(); while (keys.hasMoreElements()) { String key = (String)keys.nextElement(); if (values.get(key) == null) { Object value = b.getObject(key); values.put(key, value); } } } catch( MissingResourceException mre ) { // Keep looking } } resourceCache.put(l, values); } return values; } /** * Sets the value of <code>key</code> to <code>value</code> for all locales. * If <code>key</code> is a string and the new value isn't * equal to the old one, fire a <code>PropertyChangeEvent</code>. * If value is <code>null</code>, the key is removed from the table. * * @param key the unique <code>Object</code> who's value will be used * to retrieve the data value associated with it * @param value the new <code>Object</code> to store as data under * that key * @return the previous <code>Object</code> value, or <code>null</code> * @see #putDefaults * @see java.util.Hashtable#put */ public Object put(Object key, Object value) { Object oldValue = (value == null) ? super.remove(key) : super.put(key, value); if (key instanceof String) { firePropertyChange((String)key, oldValue, value); } return oldValue; } /** * Puts all of the key/value pairs in the database and * unconditionally generates one <code>PropertyChangeEvent</code>. * The events oldValue and newValue will be <code>null</code> and its * <code>propertyName</code> will be "UIDefaults". The key/value pairs are * added for all locales. * * @param keyValueList an array of key/value pairs * @see #put * @see java.util.Hashtable#put */ public void putDefaults(Object[] keyValueList) { for(int i = 0, max = keyValueList.length; i < max; i += 2) { Object value = keyValueList[i + 1]; if (value == null) { super.remove(keyValueList[i]); } else { super.put(keyValueList[i], value); } } firePropertyChange("UIDefaults", null, null); } /** * If the value of <code>key</code> is a <code>Font</code> return it, * otherwise return <code>null</code>. * @param key the desired key * @return if the value for <code>key</code> is a <code>Font</code>, * return the <code>Font</code> object; otherwise return * <code>null</code> */ public Font getFont(Object key) { Object value = get(key); return (value instanceof Font) ? (Font)value : null; } /** * If the value of <code>key</code> for the given <code>Locale</code> * is a <code>Font</code> return it, otherwise return <code>null</code>. * @param key the desired key * @param l the desired locale * @return if the value for <code>key</code> and <code>Locale</code> * is a <code>Font</code>, * return the <code>Font</code> object; otherwise return * <code>null</code> * @since 1.4 */ public Font getFont(Object key, Locale l) { Object value = get(key,l); return (value instanceof Font) ? (Font)value : null; } /** * If the value of <code>key</code> is a <code>Color</code> return it, * otherwise return <code>null</code>. * @param key the desired key * @return if the value for <code>key</code> is a <code>Color</code>, * return the <code>Color</code> object; otherwise return * <code>null</code> */ public Color getColor(Object key) { Object value = get(key); return (value instanceof Color) ? (Color)value : null; } /** * If the value of <code>key</code> for the given <code>Locale</code> * is a <code>Color</code> return it, otherwise return <code>null</code>. * @param key the desired key * @param l the desired locale * @return if the value for <code>key</code> and <code>Locale</code> * is a <code>Color</code>, * return the <code>Color</code> object; otherwise return * <code>null</code> * @since 1.4 */ public Color getColor(Object key, Locale l) { Object value = get(key,l); return (value instanceof Color) ? (Color)value : null; } /** * If the value of <code>key</code> is an <code>Icon</code> return it, * otherwise return <code>null</code>. * @param key the desired key * @return if the value for <code>key</code> is an <code>Icon</code>, * return the <code>Icon</code> object; otherwise return * <code>null</code> */ public Icon getIcon(Object key) { Object value = get(key); return (value instanceof Icon) ? (Icon)value : null; } /** * If the value of <code>key</code> for the given <code>Locale</code> * is an <code>Icon</code> return it, otherwise return <code>null</code>. * @param key the desired key * @param l the desired locale * @return if the value for <code>key</code> and <code>Locale</code> * is an <code>Icon</code>, * return the <code>Icon</code> object; otherwise return * <code>null</code> * @since 1.4 */ public Icon getIcon(Object key, Locale l) { Object value = get(key,l); return (value instanceof Icon) ? (Icon)value : null; } /** * If the value of <code>key</code> is a <code>Border</code> return it, * otherwise return <code>null</code>. * @param key the desired key * @return if the value for <code>key</code> is a <code>Border</code>, * return the <code>Border</code> object; otherwise return * <code>null</code> */ public Border getBorder(Object key) { Object value = get(key); return (value instanceof Border) ? (Border)value : null; } /** * If the value of <code>key</code> for the given <code>Locale</code> * is a <code>Border</code> return it, otherwise return <code>null</code>. * @param key the desired key * @param l the desired locale * @return if the value for <code>key</code> and <code>Locale</code> * is a <code>Border</code>, * return the <code>Border</code> object; otherwise return * <code>null</code> * @since 1.4 */ public Border getBorder(Object key, Locale l) { Object value = get(key,l); return (value instanceof Border) ? (Border)value : null; } /** * If the value of <code>key</code> is a <code>String</code> return it, * otherwise return <code>null</code>. * @param key the desired key * @return if the value for <code>key</code> is a <code>String</code>, * return the <code>String</code> object; otherwise return * <code>null</code> */ public String getString(Object key) { Object value = get(key); return (value instanceof String) ? (String)value : null; } /** * If the value of <code>key</code> for the given <code>Locale</code> * is a <code>String</code> return it, otherwise return <code>null</code>. * @param key the desired key * @param l the desired <code>Locale</code> * @return if the value for <code>key</code> for the given * <code>Locale</code> is a <code>String</code>, * return the <code>String</code> object; otherwise return * <code>null</code> * @since 1.4 */ public String getString(Object key, Locale l) { Object value = get(key,l); return (value instanceof String) ? (String)value : null; } /** * If the value of <code>key</code> is an <code>Integer</code> return its * integer value, otherwise return 0. * @param key the desired key * @return if the value for <code>key</code> is an <code>Integer</code>, * return its value, otherwise return 0 */ public int getInt(Object key) { Object value = get(key); return (value instanceof Integer) ? ((Integer)value).intValue() : 0; } /** * If the value of <code>key</code> for the given <code>Locale</code> * is an <code>Integer</code> return its integer value, otherwise return 0. * @param key the desired key * @param l the desired locale * @return if the value for <code>key</code> and <code>Locale</code> * is an <code>Integer</code>, * return its value, otherwise return 0 * @since 1.4 */ public int getInt(Object key, Locale l) { Object value = get(key,l); return (value instanceof Integer) ? ((Integer)value).intValue() : 0; } /** * If the value of <code>key</code> is boolean, return the * boolean value, otherwise return false. * * @param key an <code>Object</code> specifying the key for the desired boolean value * @return if the value of <code>key</code> is boolean, return the * boolean value, otherwise return false. * @since 1.4 */ public boolean getBoolean(Object key) { Object value = get(key); return (value instanceof Boolean) ? ((Boolean)value).booleanValue() : false; } /** * If the value of <code>key</code> for the given <code>Locale</code> * is boolean, return the boolean value, otherwise return false. * * @param key an <code>Object</code> specifying the key for the desired boolean value * @param l the desired locale * @return if the value for <code>key</code> and <code>Locale</code> * is boolean, return the * boolean value, otherwise return false. * @since 1.4 */ public boolean getBoolean(Object key, Locale l) { Object value = get(key,l); return (value instanceof Boolean) ? ((Boolean)value).booleanValue() : false; } /** * If the value of <code>key</code> is an <code>Insets</code> return it, * otherwise return <code>null</code>. * @param key the desired key * @return if the value for <code>key</code> is an <code>Insets</code>, * return the <code>Insets</code> object; otherwise return * <code>null</code> */ public Insets getInsets(Object key) { Object value = get(key); return (value instanceof Insets) ? (Insets)value : null; } /** * If the value of <code>key</code> for the given <code>Locale</code> * is an <code>Insets</code> return it, otherwise return <code>null</code>. * @param key the desired key * @param l the desired locale * @return if the value for <code>key</code> and <code>Locale</code> * is an <code>Insets</code>, * return the <code>Insets</code> object; otherwise return * <code>null</code> * @since 1.4 */ public Insets getInsets(Object key, Locale l) { Object value = get(key,l); return (value instanceof Insets) ? (Insets)value : null; } /** * If the value of <code>key</code> is a <code>Dimension</code> return it, * otherwise return <code>null</code>. * @param key the desired key * @return if the value for <code>key</code> is a <code>Dimension</code>, * return the <code>Dimension</code> object; otherwise return * <code>null</code> */ public Dimension getDimension(Object key) { Object value = get(key); return (value instanceof Dimension) ? (Dimension)value : null; } /** * If the value of <code>key</code> for the given <code>Locale</code> * is a <code>Dimension</code> return it, otherwise return <code>null</code>. * @param key the desired key * @param l the desired locale * @return if the value for <code>key</code> and <code>Locale</code> * is a <code>Dimension</code>, * return the <code>Dimension</code> object; otherwise return * <code>null</code> * @since 1.4 */ public Dimension getDimension(Object key, Locale l) { Object value = get(key,l); return (value instanceof Dimension) ? (Dimension)value : null; } /** * The value of <code>get(uidClassID)</code> must be the * <code>String</code> name of a * class that implements the corresponding <code>ComponentUI</code> * class. If the class hasn't been loaded before, this method looks * up the class with <code>uiClassLoader.loadClass()</code> if a non * <code>null</code> * class loader is provided, <code>classForName()</code> otherwise. * <p> * If a mapping for <code>uiClassID</code> exists or if the specified * class can't be found, return <code>null</code>. * <p> * This method is used by <code>getUI</code>, it's usually * not necessary to call it directly. * * @param uiClassID a string containing the class ID * @param uiClassLoader the object which will load the class * @return the value of <code>Class.forName(get(uidClassID))</code> * @see #getUI */ public Class<? extends ComponentUI> getUIClass(String uiClassID, ClassLoader uiClassLoader) { try { String className = (String)get(uiClassID); if (className != null) { ReflectUtil.checkPackageAccess(className); Class cls = (Class)get(className); if (cls == null) { if (uiClassLoader == null) { cls = SwingUtilities.loadSystemClass(className); } else { cls = uiClassLoader.loadClass(className); } if (cls != null) { // Save lookup for future use, as forName is slow. put(className, cls); } } return cls; } } catch (ClassNotFoundException e) { return null; } catch (ClassCastException e) { return null; } return null; } /** * Returns the L&F class that renders this component. * * @param uiClassID a string containing the class ID * @return the Class object returned by * <code>getUIClass(uiClassID, null)</code> */ public Class<? extends ComponentUI> getUIClass(String uiClassID) { return getUIClass(uiClassID, null); } /** * If <code>getUI()</code> fails for any reason, * it calls this method before returning <code>null</code>. * Subclasses may choose to do more or less here. * * @param msg message string to print * @see #getUI */ protected void getUIError(String msg) { System.err.println("UIDefaults.getUI() failed: " + msg); try { throw new Error(); } catch (Throwable e) { e.printStackTrace(); } } /** * Creates an <code>ComponentUI</code> implementation for the * specified component. In other words create the look * and feel specific delegate object for <code>target</code>. * This is done in two steps: * <ul> * <li> Look up the name of the <code>ComponentUI</code> implementation * class under the value returned by <code>target.getUIClassID()</code>. * <li> Use the implementation classes static <code>createUI()</code> * method to construct a look and feel delegate. * </ul> * @param target the <code>JComponent</code> which needs a UI * @return the <code>ComponentUI</code> object */ public ComponentUI getUI(JComponent target) { Object cl = get("ClassLoader"); ClassLoader uiClassLoader = (cl != null) ? (ClassLoader)cl : target.getClass().getClassLoader(); Class<? extends ComponentUI> uiClass = getUIClass(target.getUIClassID(), uiClassLoader); Object uiObject = null; if (uiClass == null) { getUIError("no ComponentUI class for: " + target); } else { try { Method m = (Method)get(uiClass); if (m == null) { m = uiClass.getMethod("createUI", new Class[]{JComponent.class}); put(uiClass, m); } uiObject = MethodUtil.invoke(m, null, new Object[]{target}); } catch (NoSuchMethodException e) { getUIError("static createUI() method not found in " + uiClass); } catch (Exception e) { getUIError("createUI() failed for " + target + " " + e); } } return (ComponentUI)uiObject; } /** * Adds a <code>PropertyChangeListener</code> to the listener list. * The listener is registered for all properties. * <p> * A <code>PropertyChangeEvent</code> will get fired whenever a default * is changed. * * @param listener the <code>PropertyChangeListener</code> to be added * @see java.beans.PropertyChangeSupport */ public synchronized void addPropertyChangeListener(PropertyChangeListener listener) { if (changeSupport == null) { changeSupport = new SwingPropertyChangeSupport(this); } changeSupport.addPropertyChangeListener(listener); } /** * Removes a <code>PropertyChangeListener</code> from the listener list. * This removes a <code>PropertyChangeListener</code> that was registered * for all properties. * * @param listener the <code>PropertyChangeListener</code> to be removed * @see java.beans.PropertyChangeSupport */ public synchronized void removePropertyChangeListener(PropertyChangeListener listener) { if (changeSupport != null) { changeSupport.removePropertyChangeListener(listener); } } /** * Returns an array of all the <code>PropertyChangeListener</code>s added * to this UIDefaults with addPropertyChangeListener(). * * @return all of the <code>PropertyChangeListener</code>s added or an empty * array if no listeners have been added * @since 1.4 */ public synchronized PropertyChangeListener[] getPropertyChangeListeners() { if (changeSupport == null) { return new PropertyChangeListener[0]; } return changeSupport.getPropertyChangeListeners(); } /** * Support for reporting bound property changes. If oldValue and * newValue are not equal and the <code>PropertyChangeEvent</code>x * listener list isn't empty, then fire a * <code>PropertyChange</code> event to each listener. * * @param propertyName the programmatic name of the property * that was changed * @param oldValue the old value of the property * @param newValue the new value of the property * @see java.beans.PropertyChangeSupport */ protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { if (changeSupport != null) { changeSupport.firePropertyChange(propertyName, oldValue, newValue); } } /** * Adds a resource bundle to the list of resource bundles that are * searched for localized values. Resource bundles are searched in the * reverse order they were added. In other words, the most recently added * bundle is searched first. * * @param bundleName the base name of the resource bundle to be added * @see java.util.ResourceBundle * @see #removeResourceBundle * @since 1.4 */ public synchronized void addResourceBundle( String bundleName ) { if( bundleName == null ) { return; } if( resourceBundles == null ) { resourceBundles = new Vector<String>(5); } if (!resourceBundles.contains(bundleName)) { resourceBundles.add( bundleName ); resourceCache.clear(); } } /** * Removes a resource bundle from the list of resource bundles that are * searched for localized defaults. * * @param bundleName the base name of the resource bundle to be removed * @see java.util.ResourceBundle * @see #addResourceBundle * @since 1.4 */ public synchronized void removeResourceBundle( String bundleName ) { if( resourceBundles != null ) { resourceBundles.remove( bundleName ); } resourceCache.clear(); } /** * Sets the default locale. The default locale is used in retrieving * localized values via <code>get</code> methods that do not take a * locale argument. As of release 1.4, Swing UI objects should retrieve * localized values using the locale of their component rather than the * default locale. The default locale exists to provide compatibility with * pre 1.4 behaviour. * * @param l the new default locale * @see #getDefaultLocale * @see #get(Object) * @see #get(Object,Locale) * @since 1.4 */ public void setDefaultLocale( Locale l ) { defaultLocale = l; } /** * Returns the default locale. The default locale is used in retrieving * localized values via <code>get</code> methods that do not take a * locale argument. As of release 1.4, Swing UI objects should retrieve * localized values using the locale of their component rather than the * default locale. The default locale exists to provide compatibility with * pre 1.4 behaviour. * * @return the default locale * @see #setDefaultLocale * @see #get(Object) * @see #get(Object,Locale) * @since 1.4 */ public Locale getDefaultLocale() { return defaultLocale; } /** * This class enables one to store an entry in the defaults * table that isn't constructed until the first time it's * looked up with one of the <code>getXXX(key)</code> methods. * Lazy values are useful for defaults that are expensive * to construct or are seldom retrieved. The first time * a <code>LazyValue</code> is retrieved its "real value" is computed * by calling <code>LazyValue.createValue()</code> and the real * value is used to replace the <code>LazyValue</code> in the * <code>UIDefaults</code> * table. Subsequent lookups for the same key return * the real value. Here's an example of a <code>LazyValue</code> * that constructs a <code>Border</code>: * <pre> * Object borderLazyValue = new UIDefaults.LazyValue() { * public Object createValue(UIDefaults table) { * return new BorderFactory.createLoweredBevelBorder(); * } * }; * * uiDefaultsTable.put("MyBorder", borderLazyValue); * </pre> * * @see UIDefaults#get */ public interface LazyValue { /** * Creates the actual value retrieved from the <code>UIDefaults</code> * table. When an object that implements this interface is * retrieved from the table, this method is used to create * the real value, which is then stored in the table and * returned to the calling method. * * @param table a <code>UIDefaults</code> table * @return the created <code>Object</code> */ Object createValue(UIDefaults table); } /** * This class enables one to store an entry in the defaults * table that's constructed each time it's looked up with one of * the <code>getXXX(key)</code> methods. Here's an example of * an <code>ActiveValue</code> that constructs a * <code>DefaultListCellRenderer</code>: * <pre> * Object cellRendererActiveValue = new UIDefaults.ActiveValue() { * public Object createValue(UIDefaults table) { * return new DefaultListCellRenderer(); * } * }; * * uiDefaultsTable.put("MyRenderer", cellRendererActiveValue); * </pre> * * @see UIDefaults#get */ public interface ActiveValue { /** * Creates the value retrieved from the <code>UIDefaults</code> table. * The object is created each time it is accessed. * * @param table a <code>UIDefaults</code> table * @return the created <code>Object</code> */ Object createValue(UIDefaults table); } /** * This class provides an implementation of <code>LazyValue</code> * which can be * used to delay loading of the Class for the instance to be created. * It also avoids creation of an anonymous inner class for the * <code>LazyValue</code> * subclass. Both of these improve performance at the time that a * a Look and Feel is loaded, at the cost of a slight performance * reduction the first time <code>createValue</code> is called * (since Reflection APIs are used). * @since 1.3 */ public static class ProxyLazyValue implements LazyValue { private AccessControlContext acc; private String className; private String methodName; private Object[] args; /** * Creates a <code>LazyValue</code> which will construct an instance * when asked. * * @param c a <code>String</code> specifying the classname * of the instance to be created on demand */ public ProxyLazyValue(String c) { this(c, (String)null); } /** * Creates a <code>LazyValue</code> which will construct an instance * when asked. * * @param c a <code>String</code> specifying the classname of * the class * containing a static method to be called for * instance creation * @param m a <code>String</code> specifying the static * method to be called on class c */ public ProxyLazyValue(String c, String m) { this(c, m, null); } /** * Creates a <code>LazyValue</code> which will construct an instance * when asked. * * @param c a <code>String</code> specifying the classname * of the instance to be created on demand * @param o an array of <code>Objects</code> to be passed as * paramaters to the constructor in class c */ public ProxyLazyValue(String c, Object[] o) { this(c, null, o); } /** * Creates a <code>LazyValue</code> which will construct an instance * when asked. * * @param c a <code>String</code> specifying the classname * of the class * containing a static method to be called for * instance creation. * @param m a <code>String</code> specifying the static method * to be called on class c * @param o an array of <code>Objects</code> to be passed as * paramaters to the static method in class c */ public ProxyLazyValue(String c, String m, Object[] o) { acc = AccessController.getContext(); className = c; methodName = m; if (o != null) { args = o.clone(); } } /** * Creates the value retrieved from the <code>UIDefaults</code> table. * The object is created each time it is accessed. * * @param table a <code>UIDefaults</code> table * @return the created <code>Object</code> */ public Object createValue(final UIDefaults table) { // In order to pick up the security policy in effect at the // time of creation we use a doPrivileged with the // AccessControlContext that was in place when this was created. if (acc == null && System.getSecurityManager() != null) { throw new SecurityException("null AccessControlContext"); } return AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { try { Class<?> c; Object cl; // See if we should use a separate ClassLoader if (table == null || !((cl = table.get("ClassLoader")) instanceof ClassLoader)) { cl = Thread.currentThread(). getContextClassLoader(); if (cl == null) { // Fallback to the system class loader. cl = ClassLoader.getSystemClassLoader(); } } ReflectUtil.checkPackageAccess(className); c = Class.forName(className, true, (ClassLoader)cl); checkAccess(c.getModifiers()); if (methodName != null) { Class[] types = getClassArray(args); Method m = c.getMethod(methodName, types); return MethodUtil.invoke(m, c, args); } else { Class[] types = getClassArray(args); Constructor constructor = c.getConstructor(types); checkAccess(constructor.getModifiers()); return constructor.newInstance(args); } } catch(Exception e) { // Ideally we would throw an exception, unfortunately // often times there are errors as an initial look and // feel is loaded before one can be switched. Perhaps a // flag should be added for debugging, so that if true // the exception would be thrown. } return null; } }, acc); } private void checkAccess(int modifiers) { if(System.getSecurityManager() != null && !Modifier.isPublic(modifiers)) { throw new SecurityException("Resource is not accessible"); } } /* * Coerce the array of class types provided into one which * looks the way the Reflection APIs expect. This is done * by substituting primitive types for their Object counterparts, * and superclasses for subclasses used to add the * <code>UIResource</code> tag. */ private Class[] getClassArray(Object[] args) { Class[] types = null; if (args!=null) { types = new Class[args.length]; for (int i = 0; i< args.length; i++) { /* PENDING(ges): At present only the primitive types used are handled correctly; this should eventually handle all primitive types */ if (args[i] instanceof java.lang.Integer) { types[i]=Integer.TYPE; } else if (args[i] instanceof java.lang.Boolean) { types[i]=Boolean.TYPE; } else if (args[i] instanceof javax.swing.plaf.ColorUIResource) { /* PENDING(ges) Currently the Reflection APIs do not search superclasses of parameters supplied for constructor/method lookup. Since we only have one case where this is needed, we substitute directly instead of adding a massive amount of mechanism for this. Eventually this will probably need to handle the general case as well. */ types[i]=java.awt.Color.class; } else { types[i]=args[i].getClass(); } } } return types; } private String printArgs(Object[] array) { String s = "{"; if (array !=null) { for (int i = 0 ; i < array.length-1; i++) { s = s.concat(array[i] + ","); } s = s.concat(array[array.length-1] + "}"); } else { s = s.concat("}"); } return s; } } /** * <code>LazyInputMap</code> will create a <code>InputMap</code> * in its <code>createValue</code> * method. The bindings are passed in in the constructor. * The bindings are an array with * the even number entries being string <code>KeyStrokes</code> * (eg "alt SPACE") and * the odd number entries being the value to use in the * <code>InputMap</code> (and the key in the <code>ActionMap</code>). * @since 1.3 */ public static class LazyInputMap implements LazyValue { /** Key bindings are registered under. */ private Object[] bindings; public LazyInputMap(Object[] bindings) { this.bindings = bindings; } /** * Creates an <code>InputMap</code> with the bindings that are * passed in. * * @param table a <code>UIDefaults</code> table * @return the <code>InputMap</code> */ public Object createValue(UIDefaults table) { if (bindings != null) { InputMap km = LookAndFeel.makeInputMap(bindings); return km; } return null; } } /** * <code>TextAndMnemonicHashMap</code> stores swing resource strings. Many of strings * can have a mnemonic. For example: * FileChooser.saveButton.textAndMnemonic=&Save * For this case method get returns "Save" for the key "FileChooser.saveButtonText" and * mnemonic "S" for the key "FileChooser.saveButtonMnemonic" * * There are several patterns for the text and mnemonic suffixes which are checked by the * <code>TextAndMnemonicHashMap</code> class. * Patterns which are converted to the xxx.textAndMnemonic key: * (xxxNameText, xxxNameMnemonic) * (xxxNameText, xxxMnemonic) * (xxx.nameText, xxx.mnemonic) * (xxxText, xxxMnemonic) * * These patterns can have a mnemonic index in format * (xxxDisplayedMnemonicIndex) * * Pattern which is converted to the xxx.titleAndMnemonic key: * (xxxTitle, xxxMnemonic) * */ private static class TextAndMnemonicHashMap extends HashMap<String, Object> { static final String AND_MNEMONIC = "AndMnemonic"; static final String TITLE_SUFFIX = ".titleAndMnemonic"; static final String TEXT_SUFFIX = ".textAndMnemonic"; @Override public Object get(Object key) { Object value = super.get(key); if (value == null) { boolean checkTitle = false; String stringKey = key.toString(); String compositeKey = null; if (stringKey.endsWith(AND_MNEMONIC)) { return null; } if (stringKey.endsWith(".mnemonic")) { compositeKey = composeKey(stringKey, 9, TEXT_SUFFIX); } else if (stringKey.endsWith("NameMnemonic")) { compositeKey = composeKey(stringKey, 12, TEXT_SUFFIX); } else if (stringKey.endsWith("Mnemonic")) { compositeKey = composeKey(stringKey, 8, TEXT_SUFFIX); checkTitle = true; } if (compositeKey != null) { value = super.get(compositeKey); if (value == null && checkTitle) { compositeKey = composeKey(stringKey, 8, TITLE_SUFFIX); value = super.get(compositeKey); } return value == null ? null : getMnemonicFromProperty(value.toString()); } if (stringKey.endsWith("NameText")) { compositeKey = composeKey(stringKey, 8, TEXT_SUFFIX); } else if (stringKey.endsWith(".nameText")) { compositeKey = composeKey(stringKey, 9, TEXT_SUFFIX); } else if (stringKey.endsWith("Text")) { compositeKey = composeKey(stringKey, 4, TEXT_SUFFIX); } else if (stringKey.endsWith("Title")) { compositeKey = composeKey(stringKey, 5, TITLE_SUFFIX); } if (compositeKey != null) { value = super.get(compositeKey); return value == null ? null : getTextFromProperty(value.toString()); } if (stringKey.endsWith("DisplayedMnemonicIndex")) { compositeKey = composeKey(stringKey, 22, TEXT_SUFFIX); value = super.get(compositeKey); if (value == null) { compositeKey = composeKey(stringKey, 22, TITLE_SUFFIX); value = super.get(compositeKey); } return value == null ? null : getIndexFromProperty(value.toString()); } } return value; } String composeKey(String key, int reduce, String sufix) { return key.substring(0, key.length() - reduce) + sufix; } String getTextFromProperty(String text) { return text.replace("&", ""); } String getMnemonicFromProperty(String text) { int index = text.indexOf('&'); if (0 <= index && index < text.length() - 1) { char c = text.charAt(index + 1); return Integer.toString((int) Character.toUpperCase(c)); } return null; } String getIndexFromProperty(String text) { int index = text.indexOf('&'); return (index == -1) ? null : Integer.toString(index); } } }