1 /* 2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing; 27 28 29 import javax.swing.plaf.ComponentUI; 30 import javax.swing.border.*; 31 import javax.swing.event.SwingPropertyChangeSupport; 32 33 import java.lang.reflect.*; 34 import java.util.HashMap; 35 import java.util.Map; 36 import java.util.Enumeration; 37 import java.util.Hashtable; 38 import java.util.ResourceBundle; 39 import java.util.ResourceBundle.Control; 40 import java.util.Locale; 41 import java.util.Vector; 42 import java.util.MissingResourceException; 43 import java.awt.Font; 44 import java.awt.Color; 45 import java.awt.Insets; 46 import java.awt.Dimension; 47 import java.lang.reflect.Method; 48 import java.beans.PropertyChangeListener; 49 import java.beans.PropertyChangeEvent; 50 import java.security.AccessController; 51 import java.security.AccessControlContext; 52 import java.security.PrivilegedAction; 53 54 import sun.reflect.misc.MethodUtil; 55 import sun.reflect.misc.ReflectUtil; 56 import sun.swing.SwingUtilities2; 57 import sun.util.CoreResourceBundleControl; 58 59 /** 60 * A table of defaults for Swing components. Applications can set/get 61 * default values via the <code>UIManager</code>. 62 * <p> 63 * <strong>Warning:</strong> 64 * Serialized objects of this class will not be compatible with 65 * future Swing releases. The current serialization support is 66 * appropriate for short term storage or RMI between applications running 67 * the same version of Swing. As of 1.4, support for long term storage 68 * of all JavaBeans™ 69 * has been added to the <code>java.beans</code> package. 70 * Please see {@link java.beans.XMLEncoder}. 71 * 72 * @see UIManager 73 * @author Hans Muller 74 */ 75 @SuppressWarnings("serial") // Same-version serialization only 76 public class UIDefaults extends Hashtable<Object,Object> 77 { 78 private static final Object PENDING = "Pending"; 79 80 private SwingPropertyChangeSupport changeSupport; 81 82 private Vector<String> resourceBundles; 83 84 private Locale defaultLocale = Locale.getDefault(); 85 86 /** 87 * Maps from a Locale to a cached Map of the ResourceBundle. This is done 88 * so as to avoid an exception being thrown when a value is asked for. 89 * Access to this should be done while holding a lock on the 90 * UIDefaults, eg synchronized(this). 91 */ 92 private Map<Locale, Map<String, Object>> resourceCache; 93 94 /** 95 * Creates an empty defaults table. 96 */ 97 public UIDefaults() { 98 this(700, .75f); 99 } 100 101 /** 102 * Creates an empty defaults table with the specified initial capacity and 103 * load factor. 104 * 105 * @param initialCapacity the initial capacity of the defaults table 106 * @param loadFactor the load factor of the defaults table 107 * @see java.util.Hashtable 108 * @since 1.6 109 */ 110 public UIDefaults(int initialCapacity, float loadFactor) { 111 super(initialCapacity, loadFactor); 112 resourceCache = new HashMap<Locale, Map<String, Object>>(); 113 } 114 115 116 /** 117 * Creates a defaults table initialized with the specified 118 * key/value pairs. For example: 119 * <pre> 120 Object[] uiDefaults = { 121 "Font", new Font("Dialog", Font.BOLD, 12), 122 "Color", Color.red, 123 "five", new Integer(5) 124 } 125 UIDefaults myDefaults = new UIDefaults(uiDefaults); 126 * </pre> 127 * @param keyValueList an array of objects containing the key/value 128 * pairs 129 */ 130 public UIDefaults(Object[] keyValueList) { 131 super(keyValueList.length / 2); 132 for(int i = 0; i < keyValueList.length; i += 2) { 133 super.put(keyValueList[i], keyValueList[i + 1]); 134 } 135 } 136 137 /** 138 * Returns the value for key. If the value is a 139 * <code>UIDefaults.LazyValue</code> then the real 140 * value is computed with <code>LazyValue.createValue()</code>, 141 * the table entry is replaced, and the real value is returned. 142 * If the value is an <code>UIDefaults.ActiveValue</code> 143 * the table entry is not replaced - the value is computed 144 * with <code>ActiveValue.createValue()</code> for each 145 * <code>get()</code> call. 146 * 147 * If the key is not found in the table then it is searched for in the list 148 * of resource bundles maintained by this object. The resource bundles are 149 * searched most recently added first using the locale returned by 150 * <code>getDefaultLocale</code>. <code>LazyValues</code> and 151 * <code>ActiveValues</code> are not supported in the resource bundles. 152 153 * 154 * @param key the desired key 155 * @return the value for <code>key</code> 156 * @see LazyValue 157 * @see ActiveValue 158 * @see java.util.Hashtable#get 159 * @see #getDefaultLocale 160 * @see #addResourceBundle 161 * @since 1.4 162 */ 163 public Object get(Object key) { 164 Object value = getFromHashtable( key ); 165 return (value != null) ? value : getFromResourceBundle(key, null); 166 } 167 168 /** 169 * Looks up up the given key in our Hashtable and resolves LazyValues 170 * or ActiveValues. 171 */ 172 private Object getFromHashtable(Object key) { 173 /* Quickly handle the common case, without grabbing 174 * a lock. 175 */ 176 Object value = super.get(key); 177 if ((value != PENDING) && 178 !(value instanceof ActiveValue) && 179 !(value instanceof LazyValue)) { 180 return value; 181 } 182 183 /* If the LazyValue for key is being constructed by another 184 * thread then wait and then return the new value, otherwise drop 185 * the lock and construct the ActiveValue or the LazyValue. 186 * We use the special value PENDING to mark LazyValues that 187 * are being constructed. 188 */ 189 synchronized(this) { 190 value = super.get(key); 191 if (value == PENDING) { 192 do { 193 try { 194 this.wait(); 195 } 196 catch (InterruptedException e) { 197 } 198 value = super.get(key); 199 } 200 while(value == PENDING); 201 return value; 202 } 203 else if (value instanceof LazyValue) { 204 super.put(key, PENDING); 205 } 206 else if (!(value instanceof ActiveValue)) { 207 return value; 208 } 209 } 210 211 /* At this point we know that the value of key was 212 * a LazyValue or an ActiveValue. 213 */ 214 if (value instanceof LazyValue) { 215 try { 216 /* If an exception is thrown we'll just put the LazyValue 217 * back in the table. 218 */ 219 value = ((LazyValue)value).createValue(this); 220 } 221 finally { 222 synchronized(this) { 223 if (value == null) { 224 super.remove(key); 225 } 226 else { 227 super.put(key, value); 228 } 229 this.notifyAll(); 230 } 231 } 232 } 233 else { 234 value = ((ActiveValue)value).createValue(this); 235 } 236 237 return value; 238 } 239 240 241 /** 242 * Returns the value for key associated with the given locale. 243 * If the value is a <code>UIDefaults.LazyValue</code> then the real 244 * value is computed with <code>LazyValue.createValue()</code>, 245 * the table entry is replaced, and the real value is returned. 246 * If the value is an <code>UIDefaults.ActiveValue</code> 247 * the table entry is not replaced - the value is computed 248 * with <code>ActiveValue.createValue()</code> for each 249 * <code>get()</code> call. 250 * 251 * If the key is not found in the table then it is searched for in the list 252 * of resource bundles maintained by this object. The resource bundles are 253 * searched most recently added first using the given locale. 254 * <code>LazyValues</code> and <code>ActiveValues</code> are not supported 255 * in the resource bundles. 256 * 257 * @param key the desired key 258 * @param l the desired <code>locale</code> 259 * @return the value for <code>key</code> 260 * @see LazyValue 261 * @see ActiveValue 262 * @see java.util.Hashtable#get 263 * @see #addResourceBundle 264 * @since 1.4 265 */ 266 public Object get(Object key, Locale l) { 267 Object value = getFromHashtable( key ); 268 return (value != null) ? value : getFromResourceBundle(key, l); 269 } 270 271 /** 272 * Looks up given key in our resource bundles. 273 */ 274 private Object getFromResourceBundle(Object key, Locale l) { 275 276 if( resourceBundles == null || 277 resourceBundles.isEmpty() || 278 !(key instanceof String) ) { 279 return null; 280 } 281 282 // A null locale means use the default locale. 283 if( l == null ) { 284 if( defaultLocale == null ) 285 return null; 286 else 287 l = defaultLocale; 288 } 289 290 synchronized(this) { 291 return getResourceCache(l).get(key); 292 } 293 } 294 295 /** 296 * Returns a Map of the known resources for the given locale. 297 */ 298 private Map<String, Object> getResourceCache(Locale l) { 299 Map<String, Object> values = resourceCache.get(l); 300 301 if (values == null) { 302 values = new TextAndMnemonicHashMap(); 303 for (int i=resourceBundles.size()-1; i >= 0; i--) { 304 String bundleName = resourceBundles.get(i); 305 try { 306 Control c = CoreResourceBundleControl.getRBControlInstance(bundleName); 307 ResourceBundle b; 308 if (c != null) { 309 b = ResourceBundle.getBundle(bundleName, l, c); 310 } else { 311 b = ResourceBundle.getBundle(bundleName, l); 312 } 313 Enumeration keys = b.getKeys(); 314 315 while (keys.hasMoreElements()) { 316 String key = (String)keys.nextElement(); 317 318 if (values.get(key) == null) { 319 Object value = b.getObject(key); 320 321 values.put(key, value); 322 } 323 } 324 } catch( MissingResourceException mre ) { 325 // Keep looking 326 } 327 } 328 resourceCache.put(l, values); 329 } 330 return values; 331 } 332 333 /** 334 * Sets the value of <code>key</code> to <code>value</code> for all locales. 335 * If <code>key</code> is a string and the new value isn't 336 * equal to the old one, fire a <code>PropertyChangeEvent</code>. 337 * If value is <code>null</code>, the key is removed from the table. 338 * 339 * @param key the unique <code>Object</code> who's value will be used 340 * to retrieve the data value associated with it 341 * @param value the new <code>Object</code> to store as data under 342 * that key 343 * @return the previous <code>Object</code> value, or <code>null</code> 344 * @see #putDefaults 345 * @see java.util.Hashtable#put 346 */ 347 public Object put(Object key, Object value) { 348 Object oldValue = (value == null) ? super.remove(key) : super.put(key, value); 349 if (key instanceof String) { 350 firePropertyChange((String)key, oldValue, value); 351 } 352 return oldValue; 353 } 354 355 356 /** 357 * Puts all of the key/value pairs in the database and 358 * unconditionally generates one <code>PropertyChangeEvent</code>. 359 * The events oldValue and newValue will be <code>null</code> and its 360 * <code>propertyName</code> will be "UIDefaults". The key/value pairs are 361 * added for all locales. 362 * 363 * @param keyValueList an array of key/value pairs 364 * @see #put 365 * @see java.util.Hashtable#put 366 */ 367 public void putDefaults(Object[] keyValueList) { 368 for(int i = 0, max = keyValueList.length; i < max; i += 2) { 369 Object value = keyValueList[i + 1]; 370 if (value == null) { 371 super.remove(keyValueList[i]); 372 } 373 else { 374 super.put(keyValueList[i], value); 375 } 376 } 377 firePropertyChange("UIDefaults", null, null); 378 } 379 380 381 /** 382 * If the value of <code>key</code> is a <code>Font</code> return it, 383 * otherwise return <code>null</code>. 384 * @param key the desired key 385 * @return if the value for <code>key</code> is a <code>Font</code>, 386 * return the <code>Font</code> object; otherwise return 387 * <code>null</code> 388 */ 389 public Font getFont(Object key) { 390 Object value = get(key); 391 return (value instanceof Font) ? (Font)value : null; 392 } 393 394 395 /** 396 * If the value of <code>key</code> for the given <code>Locale</code> 397 * is a <code>Font</code> return it, otherwise return <code>null</code>. 398 * @param key the desired key 399 * @param l the desired locale 400 * @return if the value for <code>key</code> and <code>Locale</code> 401 * is a <code>Font</code>, 402 * return the <code>Font</code> object; otherwise return 403 * <code>null</code> 404 * @since 1.4 405 */ 406 public Font getFont(Object key, Locale l) { 407 Object value = get(key,l); 408 return (value instanceof Font) ? (Font)value : null; 409 } 410 411 /** 412 * If the value of <code>key</code> is a <code>Color</code> return it, 413 * otherwise return <code>null</code>. 414 * @param key the desired key 415 * @return if the value for <code>key</code> is a <code>Color</code>, 416 * return the <code>Color</code> object; otherwise return 417 * <code>null</code> 418 */ 419 public Color getColor(Object key) { 420 Object value = get(key); 421 return (value instanceof Color) ? (Color)value : null; 422 } 423 424 425 /** 426 * If the value of <code>key</code> for the given <code>Locale</code> 427 * is a <code>Color</code> return it, otherwise return <code>null</code>. 428 * @param key the desired key 429 * @param l the desired locale 430 * @return if the value for <code>key</code> and <code>Locale</code> 431 * is a <code>Color</code>, 432 * return the <code>Color</code> object; otherwise return 433 * <code>null</code> 434 * @since 1.4 435 */ 436 public Color getColor(Object key, Locale l) { 437 Object value = get(key,l); 438 return (value instanceof Color) ? (Color)value : null; 439 } 440 441 442 /** 443 * If the value of <code>key</code> is an <code>Icon</code> return it, 444 * otherwise return <code>null</code>. 445 * @param key the desired key 446 * @return if the value for <code>key</code> is an <code>Icon</code>, 447 * return the <code>Icon</code> object; otherwise return 448 * <code>null</code> 449 */ 450 public Icon getIcon(Object key) { 451 Object value = get(key); 452 return (value instanceof Icon) ? (Icon)value : null; 453 } 454 455 456 /** 457 * If the value of <code>key</code> for the given <code>Locale</code> 458 * is an <code>Icon</code> return it, otherwise return <code>null</code>. 459 * @param key the desired key 460 * @param l the desired locale 461 * @return if the value for <code>key</code> and <code>Locale</code> 462 * is an <code>Icon</code>, 463 * return the <code>Icon</code> object; otherwise return 464 * <code>null</code> 465 * @since 1.4 466 */ 467 public Icon getIcon(Object key, Locale l) { 468 Object value = get(key,l); 469 return (value instanceof Icon) ? (Icon)value : null; 470 } 471 472 473 /** 474 * If the value of <code>key</code> is a <code>Border</code> return it, 475 * otherwise return <code>null</code>. 476 * @param key the desired key 477 * @return if the value for <code>key</code> is a <code>Border</code>, 478 * return the <code>Border</code> object; otherwise return 479 * <code>null</code> 480 */ 481 public Border getBorder(Object key) { 482 Object value = get(key); 483 return (value instanceof Border) ? (Border)value : null; 484 } 485 486 487 /** 488 * If the value of <code>key</code> for the given <code>Locale</code> 489 * is a <code>Border</code> return it, otherwise return <code>null</code>. 490 * @param key the desired key 491 * @param l the desired locale 492 * @return if the value for <code>key</code> and <code>Locale</code> 493 * is a <code>Border</code>, 494 * return the <code>Border</code> object; otherwise return 495 * <code>null</code> 496 * @since 1.4 497 */ 498 public Border getBorder(Object key, Locale l) { 499 Object value = get(key,l); 500 return (value instanceof Border) ? (Border)value : null; 501 } 502 503 504 /** 505 * If the value of <code>key</code> is a <code>String</code> return it, 506 * otherwise return <code>null</code>. 507 * @param key the desired key 508 * @return if the value for <code>key</code> is a <code>String</code>, 509 * return the <code>String</code> object; otherwise return 510 * <code>null</code> 511 */ 512 public String getString(Object key) { 513 Object value = get(key); 514 return (value instanceof String) ? (String)value : null; 515 } 516 517 /** 518 * If the value of <code>key</code> for the given <code>Locale</code> 519 * is a <code>String</code> return it, otherwise return <code>null</code>. 520 * @param key the desired key 521 * @param l the desired <code>Locale</code> 522 * @return if the value for <code>key</code> for the given 523 * <code>Locale</code> is a <code>String</code>, 524 * return the <code>String</code> object; otherwise return 525 * <code>null</code> 526 * @since 1.4 527 */ 528 public String getString(Object key, Locale l) { 529 Object value = get(key,l); 530 return (value instanceof String) ? (String)value : null; 531 } 532 533 /** 534 * If the value of <code>key</code> is an <code>Integer</code> return its 535 * integer value, otherwise return 0. 536 * @param key the desired key 537 * @return if the value for <code>key</code> is an <code>Integer</code>, 538 * return its value, otherwise return 0 539 */ 540 public int getInt(Object key) { 541 Object value = get(key); 542 return (value instanceof Integer) ? ((Integer)value).intValue() : 0; 543 } 544 545 546 /** 547 * If the value of <code>key</code> for the given <code>Locale</code> 548 * is an <code>Integer</code> return its integer value, otherwise return 0. 549 * @param key the desired key 550 * @param l the desired locale 551 * @return if the value for <code>key</code> and <code>Locale</code> 552 * is an <code>Integer</code>, 553 * return its value, otherwise return 0 554 * @since 1.4 555 */ 556 public int getInt(Object key, Locale l) { 557 Object value = get(key,l); 558 return (value instanceof Integer) ? ((Integer)value).intValue() : 0; 559 } 560 561 562 /** 563 * If the value of <code>key</code> is boolean, return the 564 * boolean value, otherwise return false. 565 * 566 * @param key an <code>Object</code> specifying the key for the desired boolean value 567 * @return if the value of <code>key</code> is boolean, return the 568 * boolean value, otherwise return false. 569 * @since 1.4 570 */ 571 public boolean getBoolean(Object key) { 572 Object value = get(key); 573 return (value instanceof Boolean) ? ((Boolean)value).booleanValue() : false; 574 } 575 576 577 /** 578 * If the value of <code>key</code> for the given <code>Locale</code> 579 * is boolean, return the boolean value, otherwise return false. 580 * 581 * @param key an <code>Object</code> specifying the key for the desired boolean value 582 * @param l the desired locale 583 * @return if the value for <code>key</code> and <code>Locale</code> 584 * is boolean, return the 585 * boolean value, otherwise return false. 586 * @since 1.4 587 */ 588 public boolean getBoolean(Object key, Locale l) { 589 Object value = get(key,l); 590 return (value instanceof Boolean) ? ((Boolean)value).booleanValue() : false; 591 } 592 593 594 /** 595 * If the value of <code>key</code> is an <code>Insets</code> return it, 596 * otherwise return <code>null</code>. 597 * @param key the desired key 598 * @return if the value for <code>key</code> is an <code>Insets</code>, 599 * return the <code>Insets</code> object; otherwise return 600 * <code>null</code> 601 */ 602 public Insets getInsets(Object key) { 603 Object value = get(key); 604 return (value instanceof Insets) ? (Insets)value : null; 605 } 606 607 608 /** 609 * If the value of <code>key</code> for the given <code>Locale</code> 610 * is an <code>Insets</code> return it, otherwise return <code>null</code>. 611 * @param key the desired key 612 * @param l the desired locale 613 * @return if the value for <code>key</code> and <code>Locale</code> 614 * is an <code>Insets</code>, 615 * return the <code>Insets</code> object; otherwise return 616 * <code>null</code> 617 * @since 1.4 618 */ 619 public Insets getInsets(Object key, Locale l) { 620 Object value = get(key,l); 621 return (value instanceof Insets) ? (Insets)value : null; 622 } 623 624 625 /** 626 * If the value of <code>key</code> is a <code>Dimension</code> return it, 627 * otherwise return <code>null</code>. 628 * @param key the desired key 629 * @return if the value for <code>key</code> is a <code>Dimension</code>, 630 * return the <code>Dimension</code> object; otherwise return 631 * <code>null</code> 632 */ 633 public Dimension getDimension(Object key) { 634 Object value = get(key); 635 return (value instanceof Dimension) ? (Dimension)value : null; 636 } 637 638 639 /** 640 * If the value of <code>key</code> for the given <code>Locale</code> 641 * is a <code>Dimension</code> return it, otherwise return <code>null</code>. 642 * @param key the desired key 643 * @param l the desired locale 644 * @return if the value for <code>key</code> and <code>Locale</code> 645 * is a <code>Dimension</code>, 646 * return the <code>Dimension</code> object; otherwise return 647 * <code>null</code> 648 * @since 1.4 649 */ 650 public Dimension getDimension(Object key, Locale l) { 651 Object value = get(key,l); 652 return (value instanceof Dimension) ? (Dimension)value : null; 653 } 654 655 656 /** 657 * The value of <code>get(uidClassID)</code> must be the 658 * <code>String</code> name of a 659 * class that implements the corresponding <code>ComponentUI</code> 660 * class. If the class hasn't been loaded before, this method looks 661 * up the class with <code>uiClassLoader.loadClass()</code> if a non 662 * <code>null</code> 663 * class loader is provided, <code>classForName()</code> otherwise. 664 * <p> 665 * If a mapping for <code>uiClassID</code> exists or if the specified 666 * class can't be found, return <code>null</code>. 667 * <p> 668 * This method is used by <code>getUI</code>, it's usually 669 * not necessary to call it directly. 670 * 671 * @param uiClassID a string containing the class ID 672 * @param uiClassLoader the object which will load the class 673 * @return the value of <code>Class.forName(get(uidClassID))</code> 674 * @see #getUI 675 */ 676 public Class<? extends ComponentUI> 677 getUIClass(String uiClassID, ClassLoader uiClassLoader) 678 { 679 try { 680 String className = (String)get(uiClassID); 681 if (className != null) { 682 ReflectUtil.checkPackageAccess(className); 683 684 Class cls = (Class)get(className); 685 if (cls == null) { 686 if (uiClassLoader == null) { 687 cls = SwingUtilities.loadSystemClass(className); 688 } 689 else { 690 cls = uiClassLoader.loadClass(className); 691 } 692 if (cls != null) { 693 // Save lookup for future use, as forName is slow. 694 put(className, cls); 695 } 696 } 697 return cls; 698 } 699 } 700 catch (ClassNotFoundException e) { 701 return null; 702 } 703 catch (ClassCastException e) { 704 return null; 705 } 706 return null; 707 } 708 709 710 /** 711 * Returns the L&F class that renders this component. 712 * 713 * @param uiClassID a string containing the class ID 714 * @return the Class object returned by 715 * <code>getUIClass(uiClassID, null)</code> 716 */ 717 public Class<? extends ComponentUI> getUIClass(String uiClassID) { 718 return getUIClass(uiClassID, null); 719 } 720 721 722 /** 723 * If <code>getUI()</code> fails for any reason, 724 * it calls this method before returning <code>null</code>. 725 * Subclasses may choose to do more or less here. 726 * 727 * @param msg message string to print 728 * @see #getUI 729 */ 730 protected void getUIError(String msg) { 731 System.err.println("UIDefaults.getUI() failed: " + msg); 732 try { 733 throw new Error(); 734 } 735 catch (Throwable e) { 736 e.printStackTrace(); 737 } 738 } 739 740 /** 741 * Creates an <code>ComponentUI</code> implementation for the 742 * specified component. In other words create the look 743 * and feel specific delegate object for <code>target</code>. 744 * This is done in two steps: 745 * <ul> 746 * <li> Look up the name of the <code>ComponentUI</code> implementation 747 * class under the value returned by <code>target.getUIClassID()</code>. 748 * <li> Use the implementation classes static <code>createUI()</code> 749 * method to construct a look and feel delegate. 750 * </ul> 751 * @param target the <code>JComponent</code> which needs a UI 752 * @return the <code>ComponentUI</code> object 753 */ 754 public ComponentUI getUI(JComponent target) { 755 756 Object cl = get("ClassLoader"); 757 ClassLoader uiClassLoader = 758 (cl != null) ? (ClassLoader)cl : target.getClass().getClassLoader(); 759 Class<? extends ComponentUI> uiClass = getUIClass(target.getUIClassID(), uiClassLoader); 760 Object uiObject = null; 761 762 if (uiClass == null) { 763 getUIError("no ComponentUI class for: " + target); 764 } 765 else { 766 try { 767 Method m = (Method)get(uiClass); 768 if (m == null) { 769 m = uiClass.getMethod("createUI", new Class[]{JComponent.class}); 770 put(uiClass, m); 771 } 772 uiObject = MethodUtil.invoke(m, null, new Object[]{target}); 773 } 774 catch (NoSuchMethodException e) { 775 getUIError("static createUI() method not found in " + uiClass); 776 } 777 catch (Exception e) { 778 getUIError("createUI() failed for " + target + " " + e); 779 } 780 } 781 782 return (ComponentUI)uiObject; 783 } 784 785 /** 786 * Adds a <code>PropertyChangeListener</code> to the listener list. 787 * The listener is registered for all properties. 788 * <p> 789 * A <code>PropertyChangeEvent</code> will get fired whenever a default 790 * is changed. 791 * 792 * @param listener the <code>PropertyChangeListener</code> to be added 793 * @see java.beans.PropertyChangeSupport 794 */ 795 public synchronized void addPropertyChangeListener(PropertyChangeListener listener) { 796 if (changeSupport == null) { 797 changeSupport = new SwingPropertyChangeSupport(this); 798 } 799 changeSupport.addPropertyChangeListener(listener); 800 } 801 802 803 /** 804 * Removes a <code>PropertyChangeListener</code> from the listener list. 805 * This removes a <code>PropertyChangeListener</code> that was registered 806 * for all properties. 807 * 808 * @param listener the <code>PropertyChangeListener</code> to be removed 809 * @see java.beans.PropertyChangeSupport 810 */ 811 public synchronized void removePropertyChangeListener(PropertyChangeListener listener) { 812 if (changeSupport != null) { 813 changeSupport.removePropertyChangeListener(listener); 814 } 815 } 816 817 818 /** 819 * Returns an array of all the <code>PropertyChangeListener</code>s added 820 * to this UIDefaults with addPropertyChangeListener(). 821 * 822 * @return all of the <code>PropertyChangeListener</code>s added or an empty 823 * array if no listeners have been added 824 * @since 1.4 825 */ 826 public synchronized PropertyChangeListener[] getPropertyChangeListeners() { 827 if (changeSupport == null) { 828 return new PropertyChangeListener[0]; 829 } 830 return changeSupport.getPropertyChangeListeners(); 831 } 832 833 834 /** 835 * Support for reporting bound property changes. If oldValue and 836 * newValue are not equal and the <code>PropertyChangeEvent</code>x 837 * listener list isn't empty, then fire a 838 * <code>PropertyChange</code> event to each listener. 839 * 840 * @param propertyName the programmatic name of the property 841 * that was changed 842 * @param oldValue the old value of the property 843 * @param newValue the new value of the property 844 * @see java.beans.PropertyChangeSupport 845 */ 846 protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { 847 if (changeSupport != null) { 848 changeSupport.firePropertyChange(propertyName, oldValue, newValue); 849 } 850 } 851 852 853 /** 854 * Adds a resource bundle to the list of resource bundles that are 855 * searched for localized values. Resource bundles are searched in the 856 * reverse order they were added. In other words, the most recently added 857 * bundle is searched first. 858 * 859 * @param bundleName the base name of the resource bundle to be added 860 * @see java.util.ResourceBundle 861 * @see #removeResourceBundle 862 * @since 1.4 863 */ 864 public synchronized void addResourceBundle( String bundleName ) { 865 if( bundleName == null ) { 866 return; 867 } 868 if( resourceBundles == null ) { 869 resourceBundles = new Vector<String>(5); 870 } 871 if (!resourceBundles.contains(bundleName)) { 872 resourceBundles.add( bundleName ); 873 resourceCache.clear(); 874 } 875 } 876 877 878 /** 879 * Removes a resource bundle from the list of resource bundles that are 880 * searched for localized defaults. 881 * 882 * @param bundleName the base name of the resource bundle to be removed 883 * @see java.util.ResourceBundle 884 * @see #addResourceBundle 885 * @since 1.4 886 */ 887 public synchronized void removeResourceBundle( String bundleName ) { 888 if( resourceBundles != null ) { 889 resourceBundles.remove( bundleName ); 890 } 891 resourceCache.clear(); 892 } 893 894 /** 895 * Sets the default locale. The default locale is used in retrieving 896 * localized values via <code>get</code> methods that do not take a 897 * locale argument. As of release 1.4, Swing UI objects should retrieve 898 * localized values using the locale of their component rather than the 899 * default locale. The default locale exists to provide compatibility with 900 * pre 1.4 behaviour. 901 * 902 * @param l the new default locale 903 * @see #getDefaultLocale 904 * @see #get(Object) 905 * @see #get(Object,Locale) 906 * @since 1.4 907 */ 908 public void setDefaultLocale( Locale l ) { 909 defaultLocale = l; 910 } 911 912 /** 913 * Returns the default locale. The default locale is used in retrieving 914 * localized values via <code>get</code> methods that do not take a 915 * locale argument. As of release 1.4, Swing UI objects should retrieve 916 * localized values using the locale of their component rather than the 917 * default locale. The default locale exists to provide compatibility with 918 * pre 1.4 behaviour. 919 * 920 * @return the default locale 921 * @see #setDefaultLocale 922 * @see #get(Object) 923 * @see #get(Object,Locale) 924 * @since 1.4 925 */ 926 public Locale getDefaultLocale() { 927 return defaultLocale; 928 } 929 930 /** 931 * This class enables one to store an entry in the defaults 932 * table that isn't constructed until the first time it's 933 * looked up with one of the <code>getXXX(key)</code> methods. 934 * Lazy values are useful for defaults that are expensive 935 * to construct or are seldom retrieved. The first time 936 * a <code>LazyValue</code> is retrieved its "real value" is computed 937 * by calling <code>LazyValue.createValue()</code> and the real 938 * value is used to replace the <code>LazyValue</code> in the 939 * <code>UIDefaults</code> 940 * table. Subsequent lookups for the same key return 941 * the real value. Here's an example of a <code>LazyValue</code> 942 * that constructs a <code>Border</code>: 943 * <pre> 944 * Object borderLazyValue = new UIDefaults.LazyValue() { 945 * public Object createValue(UIDefaults table) { 946 * return new BorderFactory.createLoweredBevelBorder(); 947 * } 948 * }; 949 * 950 * uiDefaultsTable.put("MyBorder", borderLazyValue); 951 * </pre> 952 * 953 * @see UIDefaults#get 954 */ 955 public interface LazyValue { 956 /** 957 * Creates the actual value retrieved from the <code>UIDefaults</code> 958 * table. When an object that implements this interface is 959 * retrieved from the table, this method is used to create 960 * the real value, which is then stored in the table and 961 * returned to the calling method. 962 * 963 * @param table a <code>UIDefaults</code> table 964 * @return the created <code>Object</code> 965 */ 966 Object createValue(UIDefaults table); 967 } 968 969 970 /** 971 * This class enables one to store an entry in the defaults 972 * table that's constructed each time it's looked up with one of 973 * the <code>getXXX(key)</code> methods. Here's an example of 974 * an <code>ActiveValue</code> that constructs a 975 * <code>DefaultListCellRenderer</code>: 976 * <pre> 977 * Object cellRendererActiveValue = new UIDefaults.ActiveValue() { 978 * public Object createValue(UIDefaults table) { 979 * return new DefaultListCellRenderer(); 980 * } 981 * }; 982 * 983 * uiDefaultsTable.put("MyRenderer", cellRendererActiveValue); 984 * </pre> 985 * 986 * @see UIDefaults#get 987 */ 988 public interface ActiveValue { 989 /** 990 * Creates the value retrieved from the <code>UIDefaults</code> table. 991 * The object is created each time it is accessed. 992 * 993 * @param table a <code>UIDefaults</code> table 994 * @return the created <code>Object</code> 995 */ 996 Object createValue(UIDefaults table); 997 } 998 999 /** 1000 * This class provides an implementation of <code>LazyValue</code> 1001 * which can be 1002 * used to delay loading of the Class for the instance to be created. 1003 * It also avoids creation of an anonymous inner class for the 1004 * <code>LazyValue</code> 1005 * subclass. Both of these improve performance at the time that a 1006 * a Look and Feel is loaded, at the cost of a slight performance 1007 * reduction the first time <code>createValue</code> is called 1008 * (since Reflection APIs are used). 1009 * @since 1.3 1010 */ 1011 public static class ProxyLazyValue implements LazyValue { 1012 private AccessControlContext acc; 1013 private String className; 1014 private String methodName; 1015 private Object[] args; 1016 1017 /** 1018 * Creates a <code>LazyValue</code> which will construct an instance 1019 * when asked. 1020 * 1021 * @param c a <code>String</code> specifying the classname 1022 * of the instance to be created on demand 1023 */ 1024 public ProxyLazyValue(String c) { 1025 this(c, (String)null); 1026 } 1027 /** 1028 * Creates a <code>LazyValue</code> which will construct an instance 1029 * when asked. 1030 * 1031 * @param c a <code>String</code> specifying the classname of 1032 * the class 1033 * containing a static method to be called for 1034 * instance creation 1035 * @param m a <code>String</code> specifying the static 1036 * method to be called on class c 1037 */ 1038 public ProxyLazyValue(String c, String m) { 1039 this(c, m, null); 1040 } 1041 /** 1042 * Creates a <code>LazyValue</code> which will construct an instance 1043 * when asked. 1044 * 1045 * @param c a <code>String</code> specifying the classname 1046 * of the instance to be created on demand 1047 * @param o an array of <code>Objects</code> to be passed as 1048 * paramaters to the constructor in class c 1049 */ 1050 public ProxyLazyValue(String c, Object[] o) { 1051 this(c, null, o); 1052 } 1053 /** 1054 * Creates a <code>LazyValue</code> which will construct an instance 1055 * when asked. 1056 * 1057 * @param c a <code>String</code> specifying the classname 1058 * of the class 1059 * containing a static method to be called for 1060 * instance creation. 1061 * @param m a <code>String</code> specifying the static method 1062 * to be called on class c 1063 * @param o an array of <code>Objects</code> to be passed as 1064 * paramaters to the static method in class c 1065 */ 1066 public ProxyLazyValue(String c, String m, Object[] o) { 1067 acc = AccessController.getContext(); 1068 className = c; 1069 methodName = m; 1070 if (o != null) { 1071 args = o.clone(); 1072 } 1073 } 1074 1075 /** 1076 * Creates the value retrieved from the <code>UIDefaults</code> table. 1077 * The object is created each time it is accessed. 1078 * 1079 * @param table a <code>UIDefaults</code> table 1080 * @return the created <code>Object</code> 1081 */ 1082 public Object createValue(final UIDefaults table) { 1083 // In order to pick up the security policy in effect at the 1084 // time of creation we use a doPrivileged with the 1085 // AccessControlContext that was in place when this was created. 1086 if (acc == null && System.getSecurityManager() != null) { 1087 throw new SecurityException("null AccessControlContext"); 1088 } 1089 return AccessController.doPrivileged(new PrivilegedAction<Object>() { 1090 public Object run() { 1091 try { 1092 Class<?> c; 1093 Object cl; 1094 // See if we should use a separate ClassLoader 1095 if (table == null || !((cl = table.get("ClassLoader")) 1096 instanceof ClassLoader)) { 1097 cl = Thread.currentThread(). 1098 getContextClassLoader(); 1099 if (cl == null) { 1100 // Fallback to the system class loader. 1101 cl = ClassLoader.getSystemClassLoader(); 1102 } 1103 } 1104 ReflectUtil.checkPackageAccess(className); 1105 c = Class.forName(className, true, (ClassLoader)cl); 1106 SwingUtilities2.checkAccess(c.getModifiers()); 1107 if (methodName != null) { 1108 Class[] types = getClassArray(args); 1109 Method m = c.getMethod(methodName, types); 1110 return MethodUtil.invoke(m, c, args); 1111 } else { 1112 Class[] types = getClassArray(args); 1113 Constructor constructor = c.getConstructor(types); 1114 SwingUtilities2.checkAccess(constructor.getModifiers()); 1115 return constructor.newInstance(args); 1116 } 1117 } catch(Exception e) { 1118 // Ideally we would throw an exception, unfortunately 1119 // often times there are errors as an initial look and 1120 // feel is loaded before one can be switched. Perhaps a 1121 // flag should be added for debugging, so that if true 1122 // the exception would be thrown. 1123 } 1124 return null; 1125 } 1126 }, acc); 1127 } 1128 1129 /* 1130 * Coerce the array of class types provided into one which 1131 * looks the way the Reflection APIs expect. This is done 1132 * by substituting primitive types for their Object counterparts, 1133 * and superclasses for subclasses used to add the 1134 * <code>UIResource</code> tag. 1135 */ 1136 private Class[] getClassArray(Object[] args) { 1137 Class[] types = null; 1138 if (args!=null) { 1139 types = new Class[args.length]; 1140 for (int i = 0; i< args.length; i++) { 1141 /* PENDING(ges): At present only the primitive types 1142 used are handled correctly; this should eventually 1143 handle all primitive types */ 1144 if (args[i] instanceof java.lang.Integer) { 1145 types[i]=Integer.TYPE; 1146 } else if (args[i] instanceof java.lang.Boolean) { 1147 types[i]=Boolean.TYPE; 1148 } else if (args[i] instanceof javax.swing.plaf.ColorUIResource) { 1149 /* PENDING(ges) Currently the Reflection APIs do not 1150 search superclasses of parameters supplied for 1151 constructor/method lookup. Since we only have 1152 one case where this is needed, we substitute 1153 directly instead of adding a massive amount 1154 of mechanism for this. Eventually this will 1155 probably need to handle the general case as well. 1156 */ 1157 types[i]=java.awt.Color.class; 1158 } else { 1159 types[i]=args[i].getClass(); 1160 } 1161 } 1162 } 1163 return types; 1164 } 1165 1166 private String printArgs(Object[] array) { 1167 String s = "{"; 1168 if (array !=null) { 1169 for (int i = 0 ; i < array.length-1; i++) { 1170 s = s.concat(array[i] + ","); 1171 } 1172 s = s.concat(array[array.length-1] + "}"); 1173 } else { 1174 s = s.concat("}"); 1175 } 1176 return s; 1177 } 1178 } 1179 1180 1181 /** 1182 * <code>LazyInputMap</code> will create a <code>InputMap</code> 1183 * in its <code>createValue</code> 1184 * method. The bindings are passed in in the constructor. 1185 * The bindings are an array with 1186 * the even number entries being string <code>KeyStrokes</code> 1187 * (eg "alt SPACE") and 1188 * the odd number entries being the value to use in the 1189 * <code>InputMap</code> (and the key in the <code>ActionMap</code>). 1190 * @since 1.3 1191 */ 1192 public static class LazyInputMap implements LazyValue { 1193 /** Key bindings are registered under. */ 1194 private Object[] bindings; 1195 1196 public LazyInputMap(Object[] bindings) { 1197 this.bindings = bindings; 1198 } 1199 1200 /** 1201 * Creates an <code>InputMap</code> with the bindings that are 1202 * passed in. 1203 * 1204 * @param table a <code>UIDefaults</code> table 1205 * @return the <code>InputMap</code> 1206 */ 1207 public Object createValue(UIDefaults table) { 1208 if (bindings != null) { 1209 InputMap km = LookAndFeel.makeInputMap(bindings); 1210 return km; 1211 } 1212 return null; 1213 } 1214 } 1215 1216 /** 1217 * <code>TextAndMnemonicHashMap</code> stores swing resource strings. Many of strings 1218 * can have a mnemonic. For example: 1219 * FileChooser.saveButton.textAndMnemonic=&Save 1220 * For this case method get returns "Save" for the key "FileChooser.saveButtonText" and 1221 * mnemonic "S" for the key "FileChooser.saveButtonMnemonic" 1222 * 1223 * There are several patterns for the text and mnemonic suffixes which are checked by the 1224 * <code>TextAndMnemonicHashMap</code> class. 1225 * Patterns which are converted to the xxx.textAndMnemonic key: 1226 * (xxxNameText, xxxNameMnemonic) 1227 * (xxxNameText, xxxMnemonic) 1228 * (xxx.nameText, xxx.mnemonic) 1229 * (xxxText, xxxMnemonic) 1230 * 1231 * These patterns can have a mnemonic index in format 1232 * (xxxDisplayedMnemonicIndex) 1233 * 1234 * Pattern which is converted to the xxx.titleAndMnemonic key: 1235 * (xxxTitle, xxxMnemonic) 1236 * 1237 */ 1238 private static class TextAndMnemonicHashMap extends HashMap<String, Object> { 1239 1240 static final String AND_MNEMONIC = "AndMnemonic"; 1241 static final String TITLE_SUFFIX = ".titleAndMnemonic"; 1242 static final String TEXT_SUFFIX = ".textAndMnemonic"; 1243 1244 @Override 1245 public Object get(Object key) { 1246 1247 Object value = super.get(key); 1248 1249 if (value == null) { 1250 1251 boolean checkTitle = false; 1252 1253 String stringKey = key.toString(); 1254 String compositeKey = null; 1255 1256 if (stringKey.endsWith(AND_MNEMONIC)) { 1257 return null; 1258 } 1259 1260 if (stringKey.endsWith(".mnemonic")) { 1261 compositeKey = composeKey(stringKey, 9, TEXT_SUFFIX); 1262 } else if (stringKey.endsWith("NameMnemonic")) { 1263 compositeKey = composeKey(stringKey, 12, TEXT_SUFFIX); 1264 } else if (stringKey.endsWith("Mnemonic")) { 1265 compositeKey = composeKey(stringKey, 8, TEXT_SUFFIX); 1266 checkTitle = true; 1267 } 1268 1269 if (compositeKey != null) { 1270 value = super.get(compositeKey); 1271 if (value == null && checkTitle) { 1272 compositeKey = composeKey(stringKey, 8, TITLE_SUFFIX); 1273 value = super.get(compositeKey); 1274 } 1275 1276 return value == null ? null : getMnemonicFromProperty(value.toString()); 1277 } 1278 1279 if (stringKey.endsWith("NameText")) { 1280 compositeKey = composeKey(stringKey, 8, TEXT_SUFFIX); 1281 } else if (stringKey.endsWith(".nameText")) { 1282 compositeKey = composeKey(stringKey, 9, TEXT_SUFFIX); 1283 } else if (stringKey.endsWith("Text")) { 1284 compositeKey = composeKey(stringKey, 4, TEXT_SUFFIX); 1285 } else if (stringKey.endsWith("Title")) { 1286 compositeKey = composeKey(stringKey, 5, TITLE_SUFFIX); 1287 } 1288 1289 if (compositeKey != null) { 1290 value = super.get(compositeKey); 1291 return value == null ? null : getTextFromProperty(value.toString()); 1292 } 1293 1294 if (stringKey.endsWith("DisplayedMnemonicIndex")) { 1295 compositeKey = composeKey(stringKey, 22, TEXT_SUFFIX); 1296 value = super.get(compositeKey); 1297 if (value == null) { 1298 compositeKey = composeKey(stringKey, 22, TITLE_SUFFIX); 1299 value = super.get(compositeKey); 1300 } 1301 return value == null ? null : getIndexFromProperty(value.toString()); 1302 } 1303 } 1304 1305 return value; 1306 } 1307 1308 String composeKey(String key, int reduce, String sufix) { 1309 return key.substring(0, key.length() - reduce) + sufix; 1310 } 1311 1312 String getTextFromProperty(String text) { 1313 return text.replace("&", ""); 1314 } 1315 1316 String getMnemonicFromProperty(String text) { 1317 int index = text.indexOf('&'); 1318 if (0 <= index && index < text.length() - 1) { 1319 char c = text.charAt(index + 1); 1320 return Integer.toString((int) Character.toUpperCase(c)); 1321 } 1322 return null; 1323 } 1324 1325 String getIndexFromProperty(String text) { 1326 int index = text.indexOf('&'); 1327 return (index == -1) ? null : Integer.toString(index); 1328 } 1329 } 1330 1331 }