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