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