1. /*
  2. * @(#)UIDefaults.java 1.32 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing;
  8. import javax.swing.plaf.ComponentUI;
  9. import javax.swing.border.Border;
  10. import javax.swing.event.SwingPropertyChangeSupport;
  11. import java.util.Hashtable;
  12. import java.awt.Font;
  13. import java.awt.Color;
  14. import java.awt.Insets;
  15. import java.awt.Dimension;
  16. import java.lang.reflect.Method;
  17. import java.beans.PropertyChangeListener;
  18. import java.beans.PropertyChangeEvent;
  19. /**
  20. * A table of defaults for Swing components. Applications can set/get
  21. * default values via the UIManager.
  22. * <p>
  23. * <strong>Warning:</strong>
  24. * Serialized objects of this class will not be compatible with
  25. * future Swing releases. The current serialization support is appropriate
  26. * for short term storage or RMI between applications running the same
  27. * version of Swing. A future release of Swing will provide support for
  28. * long term persistence.
  29. *
  30. * @see UIManager
  31. * @version 1.32 11/29/01
  32. * @author Hans Muller
  33. */
  34. public class UIDefaults extends Hashtable
  35. {
  36. private static final Object PENDING = new String("Pending");
  37. private SwingPropertyChangeSupport changeSupport;
  38. /**
  39. * Create an empty defaults table.
  40. */
  41. public UIDefaults() {
  42. super();
  43. }
  44. /**
  45. * Create a defaults table initialized with the specified
  46. * key/value pairs. For example:
  47. * <pre>
  48. Object[] uiDefaults = {
  49. "Font", new Font("Dialog", Font.BOLD, 12),
  50. "Color", Color.red,
  51. "five", new Integer(5)
  52. }
  53. UIDefaults myDefaults = new UIDefaults(uiDefaults);
  54. * </pre>
  55. */
  56. public UIDefaults(Object[] keyValueList) {
  57. super(keyValueList.length / 2);
  58. for(int i = 0; i < keyValueList.length; i += 2) {
  59. super.put(keyValueList[i], keyValueList[i + 1]);
  60. }
  61. }
  62. /**
  63. * Returns the value for key. If the value is a
  64. * <code>UIDefaults.LazyValue</code> then the real
  65. * value is computed with <code>LazyValue.createValue()</code>,
  66. * the table entry is replaced, and the real value is returned.
  67. * If the value is an <code>UIDefaults.ActiveValue</code>
  68. * the table entry is not replaced - the value is computed
  69. * with ActiveValue.createValue() for each get() call.
  70. *
  71. * @see LazyValue
  72. * @see ActiveValue
  73. * @see java.util.Hashtable#get
  74. */
  75. public Object get(Object key)
  76. {
  77. /* Quickly handle the common case, without grabbing
  78. * a lock.
  79. */
  80. Object value = super.get(key);
  81. if ((value != PENDING) &&
  82. !(value instanceof ActiveValue) &&
  83. !(value instanceof LazyValue)) {
  84. return value;
  85. }
  86. /* If the LazyValue for key is being constructed by another
  87. * thread then wait and then return the new value, otherwise drop
  88. * the lock and construct the ActiveValue or the LazyValue.
  89. * We use the special value PENDING to mark LazyValues that
  90. * are being constructed.
  91. */
  92. synchronized(this) {
  93. value = super.get(key);
  94. if (value == PENDING) {
  95. do {
  96. try {
  97. this.wait();
  98. }
  99. catch (InterruptedException e) {
  100. }
  101. value = super.get(key);
  102. }
  103. while(value == PENDING);
  104. return value;
  105. }
  106. else if (value instanceof LazyValue) {
  107. super.put(key, PENDING);
  108. }
  109. else if (!(value instanceof ActiveValue)) {
  110. return value;
  111. }
  112. }
  113. /* At this point we know that the value of key was
  114. * a LazyValue or an ActiveValue.
  115. */
  116. if (value instanceof LazyValue) {
  117. try {
  118. /* If an exception is thrown we'll just put the LazyValue
  119. * back in the table.
  120. */
  121. value = ((LazyValue)value).createValue(this);
  122. }
  123. finally {
  124. synchronized(this) {
  125. if (value == null) {
  126. super.remove(key);
  127. }
  128. else {
  129. super.put(key, value);
  130. }
  131. this.notify();
  132. }
  133. }
  134. }
  135. else {
  136. value = ((ActiveValue)value).createValue(this);
  137. }
  138. return value;
  139. }
  140. /**
  141. * Set the value of <code>key</code> to <code>value</code>.
  142. * If <code>key</code> is a string and the new value isn't
  143. * equal to the old one, fire a PropertyChangeEvent. If value
  144. * is null, the key is removed from the table.
  145. *
  146. * @param key the unique Object who's value will be used to
  147. * retreive the data value associated with it
  148. * @param value the new Object to store as data under that key
  149. * @return the previous Object value, or null
  150. * @see #putDefaults
  151. * @see java.util.Hashtable#put
  152. */
  153. public Object put(Object key, Object value) {
  154. Object oldValue = (value == null) ? super.remove(key) : super.put(key, value);
  155. if (key instanceof String) {
  156. firePropertyChange((String)key, oldValue, value);
  157. }
  158. return oldValue;
  159. }
  160. /**
  161. * Put all of the key/value pairs in the database and
  162. * unconditionally generate one PropertyChangeEvent.
  163. * The events oldValue and newValue will be null and its
  164. * propertyName will be "UIDefaults".
  165. *
  166. * @see #put
  167. * @see java.util.Hashtable#put
  168. */
  169. public void putDefaults(Object[] keyValueList) {
  170. for(int i = 0; i < keyValueList.length; i += 2) {
  171. Object value = keyValueList[i + 1];
  172. if (value == null) {
  173. super.remove(keyValueList[i]);
  174. }
  175. else {
  176. super.put(keyValueList[i], value);
  177. }
  178. }
  179. firePropertyChange("UIDefaults", null, null);
  180. }
  181. /**
  182. * If the value of <code>key</code> is a Font return it, otherwise
  183. * return null.
  184. */
  185. public Font getFont(Object key) {
  186. Object value = get(key);
  187. return (value instanceof Font) ? (Font)value : null;
  188. }
  189. /**
  190. * If the value of <code>key</code> is a Color return it, otherwise
  191. * return null.
  192. */
  193. public Color getColor(Object key) {
  194. Object value = get(key);
  195. return (value instanceof Color) ? (Color)value : null;
  196. }
  197. /**
  198. * If the value of <code>key</code> is an Icon return it, otherwise
  199. * return null.
  200. */
  201. public Icon getIcon(Object key) {
  202. Object value = get(key);
  203. return (value instanceof Icon) ? (Icon)value : null;
  204. }
  205. /**
  206. * If the value of <code>key</code> is a Border return it, otherwise
  207. * return null.
  208. */
  209. public Border getBorder(Object key) {
  210. Object value = get(key);
  211. return (value instanceof Border) ? (Border)value : null;
  212. }
  213. /**
  214. * If the value of <code>key</code> is a String return it, otherwise
  215. * return null.
  216. */
  217. public String getString(Object key) {
  218. Object value = get(key);
  219. return (value instanceof String) ? (String)value : null;
  220. }
  221. /**
  222. * If the value of <code>key</code> is a Integer return its
  223. * integer value, otherwise return 0.
  224. */
  225. public int getInt(Object key) {
  226. Object value = get(key);
  227. return (value instanceof Integer) ? ((Integer)value).intValue() : 0;
  228. }
  229. /**
  230. * If the value of <code>key</code> is a Insets return it, otherwise
  231. * return null.
  232. */
  233. public Insets getInsets(Object key) {
  234. Object value = get(key);
  235. return (value instanceof Insets) ? (Insets)value : null;
  236. }
  237. /**
  238. * If the value of <code>key</code> is a Dimension return it, otherwise
  239. * return null.
  240. */
  241. public Dimension getDimension(Object key) {
  242. Object value = get(key);
  243. return (value instanceof Dimension) ? (Dimension)value : null;
  244. }
  245. /**
  246. * The value of get(uidClassID) must be the String name of a
  247. * class that implements the corresponding ComponentUI
  248. * class. If the class hasn't been loaded before, this method looks
  249. * up the class with <code>uiClassLoader.loadClass()</code> if a non null
  250. * class loader is provided, <code>classForName()</code> otherwise.
  251. * <p>
  252. * If a mapping for uiClassID exists or if the specified
  253. * class can't be found, return null.
  254. * <p>
  255. * This method is used by <code>getUI</code>, it's usually
  256. * not neccessary to call it directly.
  257. *
  258. * @return The value of <code>Class.forName(get(uidClassID))</code>.
  259. * @see #getUI
  260. */
  261. public Class getUIClass(String uiClassID, ClassLoader uiClassLoader)
  262. {
  263. try {
  264. String className = (String)get(uiClassID);
  265. Class cls = (Class)get(className);
  266. if (cls == null) {
  267. if (uiClassLoader == null) {
  268. cls = SwingUtilities.loadSystemClass(className);
  269. }
  270. else {
  271. cls = uiClassLoader.loadClass(className);
  272. }
  273. if (cls != null) {
  274. // Save lookup for future use, as forName is slow.
  275. put(className, cls);
  276. }
  277. }
  278. return cls;
  279. }
  280. catch (ClassNotFoundException e) {
  281. return null;
  282. }
  283. catch (ClassCastException e) {
  284. return null;
  285. }
  286. }
  287. /**
  288. * Returns the L&F class that renders this component.
  289. *
  290. * @return the Class object returned by getUIClass(uiClassID, null)
  291. */
  292. public Class getUIClass(String uiClassID) {
  293. return getUIClass(uiClassID, null);
  294. }
  295. /**
  296. * If getUI() fails for any reason, it calls this method before
  297. * returning null. Subclasses may choose to do more or
  298. * less here.
  299. *
  300. * @param msg Message string to print.
  301. * @see #getUI
  302. */
  303. protected void getUIError(String msg) {
  304. System.err.println("UIDefaults.getUI() failed: " + msg);
  305. try {
  306. throw new Error();
  307. }
  308. catch (Throwable e) {
  309. e.printStackTrace();
  310. }
  311. }
  312. /**
  313. * Create an ComponentUI implementation for the
  314. * specified component. In other words create the look
  315. * and feel specific delegate object for <code>target</code>.
  316. * This is done in two steps:
  317. * <ul>
  318. * <li> Lookup the name of the ComponentUI implementation
  319. * class under the value returned by target.getUIClassID().
  320. * <li> Use the implementation classes static <code>createUI()</code>
  321. * method to construct a look and feel delegate.
  322. * </ul>
  323. */
  324. public ComponentUI getUI(JComponent target)
  325. {
  326. Object cl = get("ClassLoader");
  327. ClassLoader uiClassLoader =
  328. (cl != null) ? (ClassLoader)cl : target.getClass().getClassLoader();
  329. Class uiClass = getUIClass(target.getUIClassID(), uiClassLoader);
  330. Object uiObject = null;
  331. if (uiClass == null) {
  332. getUIError("no ComponentUI class for: " + target);
  333. }
  334. else {
  335. try {
  336. Method m = (Method)get(uiClass);
  337. if (m == null) {
  338. Class acClass = javax.swing.JComponent.class;
  339. m = uiClass.getMethod("createUI", new Class[]{acClass});
  340. put(uiClass, m);
  341. }
  342. uiObject = m.invoke(null, new Object[]{target});
  343. }
  344. catch (NoSuchMethodException e) {
  345. getUIError("static createUI() method not found in " + uiClass);
  346. }
  347. catch (Exception e) {
  348. getUIError("createUI() failed for " + target + " " + e);
  349. }
  350. }
  351. return (ComponentUI)uiObject;
  352. }
  353. /**
  354. * Add a PropertyChangeListener to the listener list.
  355. * The listener is registered for all properties.
  356. * <p>
  357. * A PropertyChangeEvent will get fired whenever a default
  358. * is changed.
  359. *
  360. * @param listener The PropertyChangeListener to be added
  361. * @see java.beans.PropertyChangeSupport
  362. */
  363. public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
  364. if (changeSupport == null) {
  365. changeSupport = new SwingPropertyChangeSupport(this);
  366. }
  367. changeSupport.addPropertyChangeListener(listener);
  368. }
  369. /**
  370. * Remove a PropertyChangeListener from the listener list.
  371. * This removes a PropertyChangeListener that was registered
  372. * for all properties.
  373. *
  374. * @param listener The PropertyChangeListener to be removed
  375. * @see java.beans.PropertyChangeSupport
  376. */
  377. public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
  378. if (changeSupport != null) {
  379. changeSupport.removePropertyChangeListener(listener);
  380. }
  381. }
  382. /**
  383. * Support for reporting bound property changes. If oldValue and
  384. * newValue are not equal and the PropertyChangeEvent listener list
  385. * isn't empty, then fire a PropertyChange event to each listener.
  386. *
  387. * @param propertyName The programmatic name of the property that was changed.
  388. * @param oldValue The old value of the property.
  389. * @param newValue The new value of the property.
  390. * @see java.beans.PropertyChangeSupport
  391. */
  392. protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
  393. if (changeSupport != null) {
  394. changeSupport.firePropertyChange(propertyName, oldValue, newValue);
  395. }
  396. }
  397. /**
  398. * This class enables one to store an entry in the defaults
  399. * table that isn't constructed until the first time it's
  400. * looked up with one of the <code>getXXX(key)</code> methods.
  401. * Lazy values are useful for defaults that are expensive
  402. * to construct or are seldom retrieved. The first time
  403. * a LazyValue is retrieved its "real value" is computed
  404. * by calling <code>LazyValue.createValue()</code> and the real
  405. * value is used to replace the LazyValue in the UIDefaults
  406. * table. Subsequent lookups for the same key return
  407. * the real value. Here's an example of a LazyValue that
  408. * constructs a Border:
  409. * <pre>
  410. * Object borderLazyValue = new UIDefaults.LazyValue() {
  411. * public Object createValue(UIDefaults table) {
  412. * return new BorderFactory.createLoweredBevelBorder();
  413. * }
  414. * };
  415. *
  416. * uiDefaultsTable.put("MyBorder", borderLazyValue);
  417. * </pre>
  418. *
  419. * @see UIDefaults#get
  420. */
  421. public interface LazyValue {
  422. /**
  423. * Creates the actual value retrieved from the UIDefaults
  424. * table. When an object that implements this interface is
  425. * retrieved from the table, this method is used to create
  426. * the real value, which is then stored in the table and
  427. * returned to the calling method.
  428. *
  429. * @param table a UIDefaults table
  430. * @return the created Object
  431. */
  432. Object createValue(UIDefaults table);
  433. }
  434. /**
  435. * This class enables one to store an entry in the defaults
  436. * table that's constructed each time it's looked up with one of
  437. * the <code>getXXX(key)</code> methods. Here's an example of
  438. * an ActiveValue that constructs a DefaultListCellRenderer
  439. * <pre>
  440. * Object cellRendererActiveValue = new UIDefaults.ActiveValue() {
  441. * public Object createValue(UIDefaults table) {
  442. * return new DefaultListCellRenderer();
  443. * }
  444. * };
  445. *
  446. * uiDefaultsTable.put("MyRenderer", cellRendererActiveValue);
  447. * </pre>
  448. *
  449. * @see UIDefaults#get
  450. */
  451. public interface ActiveValue {
  452. /**
  453. * Creates the value retrieved from the UIDefaults table.
  454. * The object is created each time it is accessed.
  455. *
  456. * @param table a UIDefaults table
  457. * @return the created Object
  458. */
  459. Object createValue(UIDefaults table);
  460. }
  461. }