1 /* 2 * Copyright (c) 1997, 2013, 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 package javax.swing.text; 26 27 import java.awt.*; 28 import java.util.*; 29 import java.io.*; 30 31 import javax.swing.SwingUtilities; 32 import javax.swing.event.ChangeListener; 33 import javax.swing.event.EventListenerList; 34 import javax.swing.event.ChangeEvent; 35 import java.lang.ref.WeakReference; 36 import java.util.WeakHashMap; 37 38 import sun.font.FontUtilities; 39 40 /** 41 * A pool of styles and their associated resources. This class determines 42 * the lifetime of a group of resources by being a container that holds 43 * caches for various resources such as font and color that get reused 44 * by the various style definitions. This can be shared by multiple 45 * documents if desired to maximize the sharing of related resources. 46 * <p> 47 * This class also provides efficient support for small sets of attributes 48 * and compresses them by sharing across uses and taking advantage of 49 * their immutable nature. Since many styles are replicated, the potential 50 * for sharing is significant, and copies can be extremely cheap. 51 * Larger sets reduce the possibility of sharing, and therefore revert 52 * automatically to a less space-efficient implementation. 53 * <p> 54 * <strong>Warning:</strong> 55 * Serialized objects of this class will not be compatible with 56 * future Swing releases. The current serialization support is 57 * appropriate for short term storage or RMI between applications running 58 * the same version of Swing. As of 1.4, support for long term storage 59 * of all JavaBeans™ 60 * has been added to the <code>java.beans</code> package. 61 * Please see {@link java.beans.XMLEncoder}. 62 * 63 * @author Timothy Prinzing 64 */ 65 public class StyleContext implements Serializable, AbstractDocument.AttributeContext { 66 67 /** 68 * Returns default AttributeContext shared by all documents that 69 * don't bother to define/supply their own context. 70 * 71 * @return the context 72 */ 73 public static final StyleContext getDefaultStyleContext() { 74 if (defaultContext == null) { 75 defaultContext = new StyleContext(); 76 } 77 return defaultContext; 78 } 79 80 private static StyleContext defaultContext; 81 82 /** 83 * Creates a new StyleContext object. 84 */ 85 public StyleContext() { 86 styles = new NamedStyle(null); 87 addStyle(DEFAULT_STYLE, null); 88 } 89 90 /** 91 * Adds a new style into the style hierarchy. Style attributes 92 * resolve from bottom up so an attribute specified in a child 93 * will override an attribute specified in the parent. 94 * 95 * @param nm the name of the style (must be unique within the 96 * collection of named styles in the document). The name may 97 * be null if the style is unnamed, but the caller is responsible 98 * for managing the reference returned as an unnamed style can't 99 * be fetched by name. An unnamed style may be useful for things 100 * like character attribute overrides such as found in a style 101 * run. 102 * @param parent the parent style. This may be null if unspecified 103 * attributes need not be resolved in some other style. 104 * @return the created style 105 */ 106 public Style addStyle(String nm, Style parent) { 107 Style style = new NamedStyle(nm, parent); 108 if (nm != null) { 109 // add a named style, a class of attributes 110 styles.addAttribute(nm, style); 111 } 112 return style; 113 } 114 115 /** 116 * Removes a named style previously added to the document. 117 * 118 * @param nm the name of the style to remove 119 */ 120 public void removeStyle(String nm) { 121 styles.removeAttribute(nm); 122 } 123 124 /** 125 * Fetches a named style previously added to the document 126 * 127 * @param nm the name of the style 128 * @return the style 129 */ 130 public Style getStyle(String nm) { 131 return (Style) styles.getAttribute(nm); 132 } 133 134 /** 135 * Fetches the names of the styles defined. 136 * 137 * @return the list of names as an enumeration 138 */ 139 public Enumeration<?> getStyleNames() { 140 return styles.getAttributeNames(); 141 } 142 143 /** 144 * Adds a listener to track when styles are added 145 * or removed. 146 * 147 * @param l the change listener 148 */ 149 public void addChangeListener(ChangeListener l) { 150 styles.addChangeListener(l); 151 } 152 153 /** 154 * Removes a listener that was tracking styles being 155 * added or removed. 156 * 157 * @param l the change listener 158 */ 159 public void removeChangeListener(ChangeListener l) { 160 styles.removeChangeListener(l); 161 } 162 163 /** 164 * Returns an array of all the <code>ChangeListener</code>s added 165 * to this StyleContext with addChangeListener(). 166 * 167 * @return all of the <code>ChangeListener</code>s added or an empty 168 * array if no listeners have been added 169 * @since 1.4 170 */ 171 public ChangeListener[] getChangeListeners() { 172 return ((NamedStyle)styles).getChangeListeners(); 173 } 174 175 /** 176 * Gets the font from an attribute set. This is 177 * implemented to try and fetch a cached font 178 * for the given AttributeSet, and if that fails 179 * the font features are resolved and the 180 * font is fetched from the low-level font cache. 181 * 182 * @param attr the attribute set 183 * @return the font 184 */ 185 public Font getFont(AttributeSet attr) { 186 // PENDING(prinz) add cache behavior 187 int style = Font.PLAIN; 188 if (StyleConstants.isBold(attr)) { 189 style |= Font.BOLD; 190 } 191 if (StyleConstants.isItalic(attr)) { 192 style |= Font.ITALIC; 193 } 194 String family = StyleConstants.getFontFamily(attr); 195 int size = StyleConstants.getFontSize(attr); 196 197 /** 198 * if either superscript or subscript is 199 * is set, we need to reduce the font size 200 * by 2. 201 */ 202 if (StyleConstants.isSuperscript(attr) || 203 StyleConstants.isSubscript(attr)) { 204 size -= 2; 205 } 206 207 return getFont(family, style, size); 208 } 209 210 /** 211 * Takes a set of attributes and turn it into a foreground color 212 * specification. This might be used to specify things 213 * like brighter, more hue, etc. By default it simply returns 214 * the value specified by the StyleConstants.Foreground attribute. 215 * 216 * @param attr the set of attributes 217 * @return the color 218 */ 219 public Color getForeground(AttributeSet attr) { 220 return StyleConstants.getForeground(attr); 221 } 222 223 /** 224 * Takes a set of attributes and turn it into a background color 225 * specification. This might be used to specify things 226 * like brighter, more hue, etc. By default it simply returns 227 * the value specified by the StyleConstants.Background attribute. 228 * 229 * @param attr the set of attributes 230 * @return the color 231 */ 232 public Color getBackground(AttributeSet attr) { 233 return StyleConstants.getBackground(attr); 234 } 235 236 /** 237 * Gets a new font. This returns a Font from a cache 238 * if a cached font exists. If not, a Font is added to 239 * the cache. This is basically a low-level cache for 240 * 1.1 font features. 241 * 242 * @param family the font family (such as "Monospaced") 243 * @param style the style of the font (such as Font.PLAIN) 244 * @param size the point size >= 1 245 * @return the new font 246 */ 247 public Font getFont(String family, int style, int size) { 248 fontSearch.setValue(family, style, size); 249 Font f = fontTable.get(fontSearch); 250 if (f == null) { 251 // haven't seen this one yet. 252 Style defaultStyle = 253 getStyle(StyleContext.DEFAULT_STYLE); 254 if (defaultStyle != null) { 255 final String FONT_ATTRIBUTE_KEY = "FONT_ATTRIBUTE_KEY"; 256 Font defaultFont = 257 (Font) defaultStyle.getAttribute(FONT_ATTRIBUTE_KEY); 258 if (defaultFont != null 259 && defaultFont.getFamily().equalsIgnoreCase(family)) { 260 f = defaultFont.deriveFont(style, size); 261 } 262 } 263 if (f == null) { 264 f = new Font(family, style, size); 265 } 266 if (! FontUtilities.fontSupportsDefaultEncoding(f)) { 267 f = FontUtilities.getCompositeFontUIResource(f); 268 } 269 FontKey key = new FontKey(family, style, size); 270 fontTable.put(key, f); 271 } 272 return f; 273 } 274 275 /** 276 * Returns font metrics for a font. 277 * 278 * @param f the font 279 * @return the metrics 280 */ 281 public FontMetrics getFontMetrics(Font f) { 282 // The Toolkit implementations cache, so we just forward 283 // to the default toolkit. 284 return Toolkit.getDefaultToolkit().getFontMetrics(f); 285 } 286 287 // --- AttributeContext methods -------------------- 288 289 /** 290 * Adds an attribute to the given set, and returns 291 * the new representative set. 292 * <p> 293 * This method is thread safe, although most Swing methods 294 * are not. Please see 295 * <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency 296 * in Swing</A> for more information. 297 * 298 * @param old the old attribute set 299 * @param name the non-null attribute name 300 * @param value the attribute value 301 * @return the updated attribute set 302 * @see MutableAttributeSet#addAttribute 303 */ 304 public synchronized AttributeSet addAttribute(AttributeSet old, Object name, Object value) { 305 if ((old.getAttributeCount() + 1) <= getCompressionThreshold()) { 306 // build a search key and find/create an immutable and unique 307 // set. 308 search.removeAttributes(search); 309 search.addAttributes(old); 310 search.addAttribute(name, value); 311 reclaim(old); 312 return getImmutableUniqueSet(); 313 } 314 MutableAttributeSet ma = getMutableAttributeSet(old); 315 ma.addAttribute(name, value); 316 return ma; 317 } 318 319 /** 320 * Adds a set of attributes to the element. 321 * <p> 322 * This method is thread safe, although most Swing methods 323 * are not. Please see 324 * <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency 325 * in Swing</A> for more information. 326 * 327 * @param old the old attribute set 328 * @param attr the attributes to add 329 * @return the updated attribute set 330 * @see MutableAttributeSet#addAttribute 331 */ 332 public synchronized AttributeSet addAttributes(AttributeSet old, AttributeSet attr) { 333 if ((old.getAttributeCount() + attr.getAttributeCount()) <= getCompressionThreshold()) { 334 // build a search key and find/create an immutable and unique 335 // set. 336 search.removeAttributes(search); 337 search.addAttributes(old); 338 search.addAttributes(attr); 339 reclaim(old); 340 return getImmutableUniqueSet(); 341 } 342 MutableAttributeSet ma = getMutableAttributeSet(old); 343 ma.addAttributes(attr); 344 return ma; 345 } 346 347 /** 348 * Removes an attribute from the set. 349 * <p> 350 * This method is thread safe, although most Swing methods 351 * are not. Please see 352 * <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency 353 * in Swing</A> for more information. 354 * 355 * @param old the old set of attributes 356 * @param name the non-null attribute name 357 * @return the updated attribute set 358 * @see MutableAttributeSet#removeAttribute 359 */ 360 public synchronized AttributeSet removeAttribute(AttributeSet old, Object name) { 361 if ((old.getAttributeCount() - 1) <= getCompressionThreshold()) { 362 // build a search key and find/create an immutable and unique 363 // set. 364 search.removeAttributes(search); 365 search.addAttributes(old); 366 search.removeAttribute(name); 367 reclaim(old); 368 return getImmutableUniqueSet(); 369 } 370 MutableAttributeSet ma = getMutableAttributeSet(old); 371 ma.removeAttribute(name); 372 return ma; 373 } 374 375 /** 376 * Removes a set of attributes for the element. 377 * <p> 378 * This method is thread safe, although most Swing methods 379 * are not. Please see 380 * <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency 381 * in Swing</A> for more information. 382 * 383 * @param old the old attribute set 384 * @param names the attribute names 385 * @return the updated attribute set 386 * @see MutableAttributeSet#removeAttributes 387 */ 388 public synchronized AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) { 389 if (old.getAttributeCount() <= getCompressionThreshold()) { 390 // build a search key and find/create an immutable and unique 391 // set. 392 search.removeAttributes(search); 393 search.addAttributes(old); 394 search.removeAttributes(names); 395 reclaim(old); 396 return getImmutableUniqueSet(); 397 } 398 MutableAttributeSet ma = getMutableAttributeSet(old); 399 ma.removeAttributes(names); 400 return ma; 401 } 402 403 /** 404 * Removes a set of attributes for the element. 405 * <p> 406 * This method is thread safe, although most Swing methods 407 * are not. Please see 408 * <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency 409 * in Swing</A> for more information. 410 * 411 * @param old the old attribute set 412 * @param attrs the attributes 413 * @return the updated attribute set 414 * @see MutableAttributeSet#removeAttributes 415 */ 416 public synchronized AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs) { 417 if (old.getAttributeCount() <= getCompressionThreshold()) { 418 // build a search key and find/create an immutable and unique 419 // set. 420 search.removeAttributes(search); 421 search.addAttributes(old); 422 search.removeAttributes(attrs); 423 reclaim(old); 424 return getImmutableUniqueSet(); 425 } 426 MutableAttributeSet ma = getMutableAttributeSet(old); 427 ma.removeAttributes(attrs); 428 return ma; 429 } 430 431 /** 432 * Fetches an empty AttributeSet. 433 * 434 * @return the set 435 */ 436 public AttributeSet getEmptySet() { 437 return SimpleAttributeSet.EMPTY; 438 } 439 440 /** 441 * Returns a set no longer needed by the MutableAttributeSet implementation. 442 * This is useful for operation under 1.1 where there are no weak 443 * references. This would typically be called by the finalize method 444 * of the MutableAttributeSet implementation. 445 * <p> 446 * This method is thread safe, although most Swing methods 447 * are not. Please see 448 * <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency 449 * in Swing</A> for more information. 450 * 451 * @param a the set to reclaim 452 */ 453 public void reclaim(AttributeSet a) { 454 if (SwingUtilities.isEventDispatchThread()) { 455 attributesPool.size(); // force WeakHashMap to expunge stale entries 456 } 457 // if current thread is not event dispatching thread 458 // do not bother with expunging stale entries. 459 } 460 461 // --- local methods ----------------------------------------------- 462 463 /** 464 * Returns the maximum number of key/value pairs to try and 465 * compress into unique/immutable sets. Any sets above this 466 * limit will use hashtables and be a MutableAttributeSet. 467 * 468 * @return the threshold 469 */ 470 protected int getCompressionThreshold() { 471 return THRESHOLD; 472 } 473 474 /** 475 * Create a compact set of attributes that might be shared. 476 * This is a hook for subclasses that want to alter the 477 * behavior of SmallAttributeSet. This can be reimplemented 478 * to return an AttributeSet that provides some sort of 479 * attribute conversion. 480 * 481 * @param a The set of attributes to be represented in the 482 * the compact form. 483 */ 484 protected SmallAttributeSet createSmallAttributeSet(AttributeSet a) { 485 return new SmallAttributeSet(a); 486 } 487 488 /** 489 * Create a large set of attributes that should trade off 490 * space for time. This set will not be shared. This is 491 * a hook for subclasses that want to alter the behavior 492 * of the larger attribute storage format (which is 493 * SimpleAttributeSet by default). This can be reimplemented 494 * to return a MutableAttributeSet that provides some sort of 495 * attribute conversion. 496 * 497 * @param a The set of attributes to be represented in the 498 * the larger form. 499 */ 500 protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) { 501 return new SimpleAttributeSet(a); 502 } 503 504 /** 505 * Clean the unused immutable sets out of the hashtable. 506 */ 507 synchronized void removeUnusedSets() { 508 attributesPool.size(); // force WeakHashMap to expunge stale entries 509 } 510 511 /** 512 * Search for an existing attribute set using the current search 513 * parameters. If a matching set is found, return it. If a match 514 * is not found, we create a new set and add it to the pool. 515 */ 516 AttributeSet getImmutableUniqueSet() { 517 // PENDING(prinz) should consider finding a alternative to 518 // generating extra garbage on search key. 519 SmallAttributeSet key = createSmallAttributeSet(search); 520 WeakReference<SmallAttributeSet> reference = attributesPool.get(key); 521 SmallAttributeSet a; 522 if (reference == null || (a = reference.get()) == null) { 523 a = key; 524 attributesPool.put(a, new WeakReference<SmallAttributeSet>(a)); 525 } 526 return a; 527 } 528 529 /** 530 * Creates a mutable attribute set to hand out because the current 531 * needs are too big to try and use a shared version. 532 */ 533 MutableAttributeSet getMutableAttributeSet(AttributeSet a) { 534 if (a instanceof MutableAttributeSet && 535 a != SimpleAttributeSet.EMPTY) { 536 return (MutableAttributeSet) a; 537 } 538 return createLargeAttributeSet(a); 539 } 540 541 /** 542 * Converts a StyleContext to a String. 543 * 544 * @return the string 545 */ 546 public String toString() { 547 removeUnusedSets(); 548 String s = ""; 549 for (SmallAttributeSet set : attributesPool.keySet()) { 550 s = s + set + "\n"; 551 } 552 return s; 553 } 554 555 // --- serialization --------------------------------------------- 556 557 /** 558 * Context-specific handling of writing out attributes 559 */ 560 public void writeAttributes(ObjectOutputStream out, 561 AttributeSet a) throws IOException { 562 writeAttributeSet(out, a); 563 } 564 565 /** 566 * Context-specific handling of reading in attributes 567 */ 568 public void readAttributes(ObjectInputStream in, 569 MutableAttributeSet a) throws ClassNotFoundException, IOException { 570 readAttributeSet(in, a); 571 } 572 573 /** 574 * Writes a set of attributes to the given object stream 575 * for the purpose of serialization. This will take 576 * special care to deal with static attribute keys that 577 * have been registered wit the 578 * <code>registerStaticAttributeKey</code> method. 579 * Any attribute key not registered as a static key 580 * will be serialized directly. All values are expected 581 * to be serializable. 582 * 583 * @param out the output stream 584 * @param a the attribute set 585 * @exception IOException on any I/O error 586 */ 587 public static void writeAttributeSet(ObjectOutputStream out, 588 AttributeSet a) throws IOException { 589 int n = a.getAttributeCount(); 590 out.writeInt(n); 591 Enumeration keys = a.getAttributeNames(); 592 while (keys.hasMoreElements()) { 593 Object key = keys.nextElement(); 594 if (key instanceof Serializable) { 595 out.writeObject(key); 596 } else { 597 Object ioFmt = freezeKeyMap.get(key); 598 if (ioFmt == null) { 599 throw new NotSerializableException(key.getClass(). 600 getName() + " is not serializable as a key in an AttributeSet"); 601 } 602 out.writeObject(ioFmt); 603 } 604 Object value = a.getAttribute(key); 605 Object ioFmt = freezeKeyMap.get(value); 606 if (value instanceof Serializable) { 607 out.writeObject((ioFmt != null) ? ioFmt : value); 608 } else { 609 if (ioFmt == null) { 610 throw new NotSerializableException(value.getClass(). 611 getName() + " is not serializable as a value in an AttributeSet"); 612 } 613 out.writeObject(ioFmt); 614 } 615 } 616 } 617 618 /** 619 * Reads a set of attributes from the given object input 620 * stream that have been previously written out with 621 * <code>writeAttributeSet</code>. This will try to restore 622 * keys that were static objects to the static objects in 623 * the current virtual machine considering only those keys 624 * that have been registered with the 625 * <code>registerStaticAttributeKey</code> method. 626 * The attributes retrieved from the stream will be placed 627 * into the given mutable set. 628 * 629 * @param in the object stream to read the attribute data from. 630 * @param a the attribute set to place the attribute 631 * definitions in. 632 * @exception ClassNotFoundException passed upward if encountered 633 * when reading the object stream. 634 * @exception IOException passed upward if encountered when 635 * reading the object stream. 636 */ 637 public static void readAttributeSet(ObjectInputStream in, 638 MutableAttributeSet a) throws ClassNotFoundException, IOException { 639 640 int n = in.readInt(); 641 for (int i = 0; i < n; i++) { 642 Object key = in.readObject(); 643 Object value = in.readObject(); 644 if (thawKeyMap != null) { 645 Object staticKey = thawKeyMap.get(key); 646 if (staticKey != null) { 647 key = staticKey; 648 } 649 Object staticValue = thawKeyMap.get(value); 650 if (staticValue != null) { 651 value = staticValue; 652 } 653 } 654 a.addAttribute(key, value); 655 } 656 } 657 658 /** 659 * Registers an object as a static object that is being 660 * used as a key in attribute sets. This allows the key 661 * to be treated specially for serialization. 662 * <p> 663 * For operation under a 1.1 virtual machine, this 664 * uses the value returned by <code>toString</code> 665 * concatenated to the classname. The value returned 666 * by toString should not have the class reference 667 * in it (ie it should be reimplemented from the 668 * definition in Object) in order to be the same when 669 * recomputed later. 670 * 671 * @param key the non-null object key 672 */ 673 public static void registerStaticAttributeKey(Object key) { 674 String ioFmt = key.getClass().getName() + "." + key.toString(); 675 if (freezeKeyMap == null) { 676 freezeKeyMap = new Hashtable<Object, String>(); 677 thawKeyMap = new Hashtable<String, Object>(); 678 } 679 freezeKeyMap.put(key, ioFmt); 680 thawKeyMap.put(ioFmt, key); 681 } 682 683 /** 684 * Returns the object previously registered with 685 * <code>registerStaticAttributeKey</code>. 686 */ 687 public static Object getStaticAttribute(Object key) { 688 if (thawKeyMap == null || key == null) { 689 return null; 690 } 691 return thawKeyMap.get(key); 692 } 693 694 /** 695 * Returns the String that <code>key</code> will be registered with 696 * @see #getStaticAttribute 697 * @see #registerStaticAttributeKey 698 */ 699 public static Object getStaticAttributeKey(Object key) { 700 return key.getClass().getName() + "." + key.toString(); 701 } 702 703 private void writeObject(java.io.ObjectOutputStream s) 704 throws IOException 705 { 706 // clean out unused sets before saving 707 removeUnusedSets(); 708 709 s.defaultWriteObject(); 710 } 711 712 private void readObject(ObjectInputStream s) 713 throws ClassNotFoundException, IOException 714 { 715 fontSearch = new FontKey(null, 0, 0); 716 fontTable = new Hashtable<FontKey, Font>(); 717 search = new SimpleAttributeSet(); 718 attributesPool = Collections. 719 synchronizedMap(new WeakHashMap<SmallAttributeSet, WeakReference<SmallAttributeSet>>()); 720 s.defaultReadObject(); 721 } 722 723 // --- variables --------------------------------------------------- 724 725 /** 726 * The name given to the default logical style attached 727 * to paragraphs. 728 */ 729 public static final String DEFAULT_STYLE = "default"; 730 731 private static Hashtable<Object, String> freezeKeyMap; 732 private static Hashtable<String, Object> thawKeyMap; 733 734 private Style styles; 735 private transient FontKey fontSearch = new FontKey(null, 0, 0); 736 private transient Hashtable<FontKey, Font> fontTable = new Hashtable<FontKey, Font>(); 737 738 private transient Map<SmallAttributeSet, WeakReference<SmallAttributeSet>> attributesPool = Collections. 739 synchronizedMap(new WeakHashMap<SmallAttributeSet, WeakReference<SmallAttributeSet>>()); 740 private transient MutableAttributeSet search = new SimpleAttributeSet(); 741 742 /** 743 * Number of immutable sets that are not currently 744 * being used. This helps indicate when the sets need 745 * to be cleaned out of the hashtable they are stored 746 * in. 747 */ 748 private int unusedSets; 749 750 /** 751 * The threshold for no longer sharing the set of attributes 752 * in an immutable table. 753 */ 754 static final int THRESHOLD = 9; 755 756 /** 757 * This class holds a small number of attributes in an array. 758 * The storage format is key, value, key, value, etc. The size 759 * of the set is the length of the array divided by two. By 760 * default, this is the class that will be used to store attributes 761 * when held in the compact sharable form. 762 */ 763 public class SmallAttributeSet implements AttributeSet { 764 765 public SmallAttributeSet(Object[] attributes) { 766 this.attributes = attributes; 767 updateResolveParent(); 768 } 769 770 public SmallAttributeSet(AttributeSet attrs) { 771 int n = attrs.getAttributeCount(); 772 Object[] tbl = new Object[2 * n]; 773 Enumeration names = attrs.getAttributeNames(); 774 int i = 0; 775 while (names.hasMoreElements()) { 776 tbl[i] = names.nextElement(); 777 tbl[i+1] = attrs.getAttribute(tbl[i]); 778 i += 2; 779 } 780 attributes = tbl; 781 updateResolveParent(); 782 } 783 784 private void updateResolveParent() { 785 resolveParent = null; 786 Object[] tbl = attributes; 787 for (int i = 0; i < tbl.length; i += 2) { 788 if (tbl[i] == StyleConstants.ResolveAttribute) { 789 resolveParent = (AttributeSet)tbl[i + 1]; 790 break; 791 } 792 } 793 } 794 795 Object getLocalAttribute(Object nm) { 796 if (nm == StyleConstants.ResolveAttribute) { 797 return resolveParent; 798 } 799 Object[] tbl = attributes; 800 for (int i = 0; i < tbl.length; i += 2) { 801 if (nm.equals(tbl[i])) { 802 return tbl[i+1]; 803 } 804 } 805 return null; 806 } 807 808 // --- Object methods ------------------------- 809 810 /** 811 * Returns a string showing the key/value pairs 812 */ 813 public String toString() { 814 String s = "{"; 815 Object[] tbl = attributes; 816 for (int i = 0; i < tbl.length; i += 2) { 817 if (tbl[i+1] instanceof AttributeSet) { 818 // don't recurse 819 s = s + tbl[i] + "=" + "AttributeSet" + ","; 820 } else { 821 s = s + tbl[i] + "=" + tbl[i+1] + ","; 822 } 823 } 824 s = s + "}"; 825 return s; 826 } 827 828 /** 829 * Returns a hashcode for this set of attributes. 830 * @return a hashcode value for this set of attributes. 831 */ 832 public int hashCode() { 833 int code = 0; 834 Object[] tbl = attributes; 835 for (int i = 1; i < tbl.length; i += 2) { 836 code ^= tbl[i].hashCode(); 837 } 838 return code; 839 } 840 841 /** 842 * Compares this object to the specified object. 843 * The result is <code>true</code> if the object is an equivalent 844 * set of attributes. 845 * @param obj the object to compare with. 846 * @return <code>true</code> if the objects are equal; 847 * <code>false</code> otherwise. 848 */ 849 public boolean equals(Object obj) { 850 if (obj instanceof AttributeSet) { 851 AttributeSet attrs = (AttributeSet) obj; 852 return ((getAttributeCount() == attrs.getAttributeCount()) && 853 containsAttributes(attrs)); 854 } 855 return false; 856 } 857 858 /** 859 * Clones a set of attributes. Since the set is immutable, a 860 * clone is basically the same set. 861 * 862 * @return the set of attributes 863 */ 864 public Object clone() { 865 return this; 866 } 867 868 // --- AttributeSet methods ---------------------------- 869 870 /** 871 * Gets the number of attributes that are defined. 872 * 873 * @return the number of attributes 874 * @see AttributeSet#getAttributeCount 875 */ 876 public int getAttributeCount() { 877 return attributes.length / 2; 878 } 879 880 /** 881 * Checks whether a given attribute is defined. 882 * 883 * @param key the attribute key 884 * @return true if the attribute is defined 885 * @see AttributeSet#isDefined 886 */ 887 public boolean isDefined(Object key) { 888 Object[] a = attributes; 889 int n = a.length; 890 for (int i = 0; i < n; i += 2) { 891 if (key.equals(a[i])) { 892 return true; 893 } 894 } 895 return false; 896 } 897 898 /** 899 * Checks whether two attribute sets are equal. 900 * 901 * @param attr the attribute set to check against 902 * @return true if the same 903 * @see AttributeSet#isEqual 904 */ 905 public boolean isEqual(AttributeSet attr) { 906 if (attr instanceof SmallAttributeSet) { 907 return attr == this; 908 } 909 return ((getAttributeCount() == attr.getAttributeCount()) && 910 containsAttributes(attr)); 911 } 912 913 /** 914 * Copies a set of attributes. 915 * 916 * @return the copy 917 * @see AttributeSet#copyAttributes 918 */ 919 public AttributeSet copyAttributes() { 920 return this; 921 } 922 923 /** 924 * Gets the value of an attribute. 925 * 926 * @param key the attribute name 927 * @return the attribute value 928 * @see AttributeSet#getAttribute 929 */ 930 public Object getAttribute(Object key) { 931 Object value = getLocalAttribute(key); 932 if (value == null) { 933 AttributeSet parent = getResolveParent(); 934 if (parent != null) 935 value = parent.getAttribute(key); 936 } 937 return value; 938 } 939 940 /** 941 * Gets the names of all attributes. 942 * 943 * @return the attribute names 944 * @see AttributeSet#getAttributeNames 945 */ 946 public Enumeration<?> getAttributeNames() { 947 return new KeyEnumeration(attributes); 948 } 949 950 /** 951 * Checks whether a given attribute name/value is defined. 952 * 953 * @param name the attribute name 954 * @param value the attribute value 955 * @return true if the name/value is defined 956 * @see AttributeSet#containsAttribute 957 */ 958 public boolean containsAttribute(Object name, Object value) { 959 return value.equals(getAttribute(name)); 960 } 961 962 /** 963 * Checks whether the attribute set contains all of 964 * the given attributes. 965 * 966 * @param attrs the attributes to check 967 * @return true if the element contains all the attributes 968 * @see AttributeSet#containsAttributes 969 */ 970 public boolean containsAttributes(AttributeSet attrs) { 971 boolean result = true; 972 973 Enumeration names = attrs.getAttributeNames(); 974 while (result && names.hasMoreElements()) { 975 Object name = names.nextElement(); 976 result = attrs.getAttribute(name).equals(getAttribute(name)); 977 } 978 979 return result; 980 } 981 982 /** 983 * If not overriden, the resolving parent defaults to 984 * the parent element. 985 * 986 * @return the attributes from the parent 987 * @see AttributeSet#getResolveParent 988 */ 989 public AttributeSet getResolveParent() { 990 return resolveParent; 991 } 992 993 // --- variables ----------------------------------------- 994 995 Object[] attributes; 996 // This is also stored in attributes 997 AttributeSet resolveParent; 998 } 999 1000 /** 1001 * An enumeration of the keys in a SmallAttributeSet. 1002 */ 1003 class KeyEnumeration implements Enumeration<Object> { 1004 1005 KeyEnumeration(Object[] attr) { 1006 this.attr = attr; 1007 i = 0; 1008 } 1009 1010 /** 1011 * Tests if this enumeration contains more elements. 1012 * 1013 * @return <code>true</code> if this enumeration contains more elements; 1014 * <code>false</code> otherwise. 1015 * @since JDK1.0 1016 */ 1017 public boolean hasMoreElements() { 1018 return i < attr.length; 1019 } 1020 1021 /** 1022 * Returns the next element of this enumeration. 1023 * 1024 * @return the next element of this enumeration. 1025 * @exception NoSuchElementException if no more elements exist. 1026 * @since JDK1.0 1027 */ 1028 public Object nextElement() { 1029 if (i < attr.length) { 1030 Object o = attr[i]; 1031 i += 2; 1032 return o; 1033 } 1034 throw new NoSuchElementException(); 1035 } 1036 1037 Object[] attr; 1038 int i; 1039 } 1040 1041 /** 1042 * Sorts the key strings so that they can be very quickly compared 1043 * in the attribute set searches. 1044 */ 1045 class KeyBuilder { 1046 1047 public void initialize(AttributeSet a) { 1048 if (a instanceof SmallAttributeSet) { 1049 initialize(((SmallAttributeSet)a).attributes); 1050 } else { 1051 keys.removeAllElements(); 1052 data.removeAllElements(); 1053 Enumeration names = a.getAttributeNames(); 1054 while (names.hasMoreElements()) { 1055 Object name = names.nextElement(); 1056 addAttribute(name, a.getAttribute(name)); 1057 } 1058 } 1059 } 1060 1061 /** 1062 * Initialize with a set of already sorted 1063 * keys (data from an existing SmallAttributeSet). 1064 */ 1065 private void initialize(Object[] sorted) { 1066 keys.removeAllElements(); 1067 data.removeAllElements(); 1068 int n = sorted.length; 1069 for (int i = 0; i < n; i += 2) { 1070 keys.addElement(sorted[i]); 1071 data.addElement(sorted[i+1]); 1072 } 1073 } 1074 1075 /** 1076 * Creates a table of sorted key/value entries 1077 * suitable for creation of an instance of 1078 * SmallAttributeSet. 1079 */ 1080 public Object[] createTable() { 1081 int n = keys.size(); 1082 Object[] tbl = new Object[2 * n]; 1083 for (int i = 0; i < n; i ++) { 1084 int offs = 2 * i; 1085 tbl[offs] = keys.elementAt(i); 1086 tbl[offs + 1] = data.elementAt(i); 1087 } 1088 return tbl; 1089 } 1090 1091 /** 1092 * The number of key/value pairs contained 1093 * in the current key being forged. 1094 */ 1095 int getCount() { 1096 return keys.size(); 1097 } 1098 1099 /** 1100 * Adds a key/value to the set. 1101 */ 1102 public void addAttribute(Object key, Object value) { 1103 keys.addElement(key); 1104 data.addElement(value); 1105 } 1106 1107 /** 1108 * Adds a set of key/value pairs to the set. 1109 */ 1110 public void addAttributes(AttributeSet attr) { 1111 if (attr instanceof SmallAttributeSet) { 1112 // avoid searching the keys, they are already interned. 1113 Object[] tbl = ((SmallAttributeSet)attr).attributes; 1114 int n = tbl.length; 1115 for (int i = 0; i < n; i += 2) { 1116 addAttribute(tbl[i], tbl[i+1]); 1117 } 1118 } else { 1119 Enumeration names = attr.getAttributeNames(); 1120 while (names.hasMoreElements()) { 1121 Object name = names.nextElement(); 1122 addAttribute(name, attr.getAttribute(name)); 1123 } 1124 } 1125 } 1126 1127 /** 1128 * Removes the given name from the set. 1129 */ 1130 public void removeAttribute(Object key) { 1131 int n = keys.size(); 1132 for (int i = 0; i < n; i++) { 1133 if (keys.elementAt(i).equals(key)) { 1134 keys.removeElementAt(i); 1135 data.removeElementAt(i); 1136 return; 1137 } 1138 } 1139 } 1140 1141 /** 1142 * Removes the set of keys from the set. 1143 */ 1144 public void removeAttributes(Enumeration names) { 1145 while (names.hasMoreElements()) { 1146 Object name = names.nextElement(); 1147 removeAttribute(name); 1148 } 1149 } 1150 1151 /** 1152 * Removes the set of matching attributes from the set. 1153 */ 1154 public void removeAttributes(AttributeSet attr) { 1155 Enumeration names = attr.getAttributeNames(); 1156 while (names.hasMoreElements()) { 1157 Object name = names.nextElement(); 1158 Object value = attr.getAttribute(name); 1159 removeSearchAttribute(name, value); 1160 } 1161 } 1162 1163 private void removeSearchAttribute(Object ikey, Object value) { 1164 int n = keys.size(); 1165 for (int i = 0; i < n; i++) { 1166 if (keys.elementAt(i).equals(ikey)) { 1167 if (data.elementAt(i).equals(value)) { 1168 keys.removeElementAt(i); 1169 data.removeElementAt(i); 1170 } 1171 return; 1172 } 1173 } 1174 } 1175 1176 private Vector<Object> keys = new Vector<Object>(); 1177 private Vector<Object> data = new Vector<Object>(); 1178 } 1179 1180 /** 1181 * key for a font table 1182 */ 1183 static class FontKey { 1184 1185 private String family; 1186 private int style; 1187 private int size; 1188 1189 /** 1190 * Constructs a font key. 1191 */ 1192 public FontKey(String family, int style, int size) { 1193 setValue(family, style, size); 1194 } 1195 1196 public void setValue(String family, int style, int size) { 1197 this.family = (family != null) ? family.intern() : null; 1198 this.style = style; 1199 this.size = size; 1200 } 1201 1202 /** 1203 * Returns a hashcode for this font. 1204 * @return a hashcode value for this font. 1205 */ 1206 public int hashCode() { 1207 int fhash = (family != null) ? family.hashCode() : 0; 1208 return fhash ^ style ^ size; 1209 } 1210 1211 /** 1212 * Compares this object to the specified object. 1213 * The result is <code>true</code> if and only if the argument is not 1214 * <code>null</code> and is a <code>Font</code> object with the same 1215 * name, style, and point size as this font. 1216 * @param obj the object to compare this font with. 1217 * @return <code>true</code> if the objects are equal; 1218 * <code>false</code> otherwise. 1219 */ 1220 public boolean equals(Object obj) { 1221 if (obj instanceof FontKey) { 1222 FontKey font = (FontKey)obj; 1223 return (size == font.size) && (style == font.style) && (family == font.family); 1224 } 1225 return false; 1226 } 1227 1228 } 1229 1230 /** 1231 * A collection of attributes, typically used to represent 1232 * character and paragraph styles. This is an implementation 1233 * of MutableAttributeSet that can be observed if desired. 1234 * These styles will take advantage of immutability while 1235 * the sets are small enough, and may be substantially more 1236 * efficient than something like SimpleAttributeSet. 1237 * <p> 1238 * <strong>Warning:</strong> 1239 * Serialized objects of this class will not be compatible with 1240 * future Swing releases. The current serialization support is 1241 * appropriate for short term storage or RMI between applications running 1242 * the same version of Swing. As of 1.4, support for long term storage 1243 * of all JavaBeans™ 1244 * has been added to the <code>java.beans</code> package. 1245 * Please see {@link java.beans.XMLEncoder}. 1246 */ 1247 public class NamedStyle implements Style, Serializable { 1248 1249 /** 1250 * Creates a new named style. 1251 * 1252 * @param name the style name, null for unnamed 1253 * @param parent the parent style, null if none 1254 * @since 1.4 1255 */ 1256 public NamedStyle(String name, Style parent) { 1257 attributes = getEmptySet(); 1258 if (name != null) { 1259 setName(name); 1260 } 1261 if (parent != null) { 1262 setResolveParent(parent); 1263 } 1264 } 1265 1266 /** 1267 * Creates a new named style. 1268 * 1269 * @param parent the parent style, null if none 1270 * @since 1.4 1271 */ 1272 public NamedStyle(Style parent) { 1273 this(null, parent); 1274 } 1275 1276 /** 1277 * Creates a new named style, with a null name and parent. 1278 */ 1279 public NamedStyle() { 1280 attributes = getEmptySet(); 1281 } 1282 1283 /** 1284 * Converts the style to a string. 1285 * 1286 * @return the string 1287 */ 1288 public String toString() { 1289 return "NamedStyle:" + getName() + " " + attributes; 1290 } 1291 1292 /** 1293 * Fetches the name of the style. A style is not required to be named, 1294 * so null is returned if there is no name associated with the style. 1295 * 1296 * @return the name 1297 */ 1298 public String getName() { 1299 if (isDefined(StyleConstants.NameAttribute)) { 1300 return getAttribute(StyleConstants.NameAttribute).toString(); 1301 } 1302 return null; 1303 } 1304 1305 /** 1306 * Changes the name of the style. Does nothing with a null name. 1307 * 1308 * @param name the new name 1309 */ 1310 public void setName(String name) { 1311 if (name != null) { 1312 this.addAttribute(StyleConstants.NameAttribute, name); 1313 } 1314 } 1315 1316 /** 1317 * Adds a change listener. 1318 * 1319 * @param l the change listener 1320 */ 1321 public void addChangeListener(ChangeListener l) { 1322 listenerList.add(ChangeListener.class, l); 1323 } 1324 1325 /** 1326 * Removes a change listener. 1327 * 1328 * @param l the change listener 1329 */ 1330 public void removeChangeListener(ChangeListener l) { 1331 listenerList.remove(ChangeListener.class, l); 1332 } 1333 1334 1335 /** 1336 * Returns an array of all the <code>ChangeListener</code>s added 1337 * to this NamedStyle with addChangeListener(). 1338 * 1339 * @return all of the <code>ChangeListener</code>s added or an empty 1340 * array if no listeners have been added 1341 * @since 1.4 1342 */ 1343 public ChangeListener[] getChangeListeners() { 1344 return listenerList.getListeners(ChangeListener.class); 1345 } 1346 1347 1348 /** 1349 * Notifies all listeners that have registered interest for 1350 * notification on this event type. The event instance 1351 * is lazily created using the parameters passed into 1352 * the fire method. 1353 * 1354 * @see EventListenerList 1355 */ 1356 protected void fireStateChanged() { 1357 // Guaranteed to return a non-null array 1358 Object[] listeners = listenerList.getListenerList(); 1359 // Process the listeners last to first, notifying 1360 // those that are interested in this event 1361 for (int i = listeners.length-2; i>=0; i-=2) { 1362 if (listeners[i]==ChangeListener.class) { 1363 // Lazily create the event: 1364 if (changeEvent == null) 1365 changeEvent = new ChangeEvent(this); 1366 ((ChangeListener)listeners[i+1]).stateChanged(changeEvent); 1367 } 1368 } 1369 } 1370 1371 /** 1372 * Return an array of all the listeners of the given type that 1373 * were added to this model. 1374 * 1375 * @return all of the objects receiving <em>listenerType</em> notifications 1376 * from this model 1377 * 1378 * @since 1.3 1379 */ 1380 public <T extends EventListener> T[] getListeners(Class<T> listenerType) { 1381 return listenerList.getListeners(listenerType); 1382 } 1383 1384 // --- AttributeSet ---------------------------- 1385 // delegated to the immutable field "attributes" 1386 1387 /** 1388 * Gets the number of attributes that are defined. 1389 * 1390 * @return the number of attributes >= 0 1391 * @see AttributeSet#getAttributeCount 1392 */ 1393 public int getAttributeCount() { 1394 return attributes.getAttributeCount(); 1395 } 1396 1397 /** 1398 * Checks whether a given attribute is defined. 1399 * 1400 * @param attrName the non-null attribute name 1401 * @return true if the attribute is defined 1402 * @see AttributeSet#isDefined 1403 */ 1404 public boolean isDefined(Object attrName) { 1405 return attributes.isDefined(attrName); 1406 } 1407 1408 /** 1409 * Checks whether two attribute sets are equal. 1410 * 1411 * @param attr the attribute set to check against 1412 * @return true if the same 1413 * @see AttributeSet#isEqual 1414 */ 1415 public boolean isEqual(AttributeSet attr) { 1416 return attributes.isEqual(attr); 1417 } 1418 1419 /** 1420 * Copies a set of attributes. 1421 * 1422 * @return the copy 1423 * @see AttributeSet#copyAttributes 1424 */ 1425 public AttributeSet copyAttributes() { 1426 NamedStyle a = new NamedStyle(); 1427 a.attributes = attributes.copyAttributes(); 1428 return a; 1429 } 1430 1431 /** 1432 * Gets the value of an attribute. 1433 * 1434 * @param attrName the non-null attribute name 1435 * @return the attribute value 1436 * @see AttributeSet#getAttribute 1437 */ 1438 public Object getAttribute(Object attrName) { 1439 return attributes.getAttribute(attrName); 1440 } 1441 1442 /** 1443 * Gets the names of all attributes. 1444 * 1445 * @return the attribute names as an enumeration 1446 * @see AttributeSet#getAttributeNames 1447 */ 1448 public Enumeration<?> getAttributeNames() { 1449 return attributes.getAttributeNames(); 1450 } 1451 1452 /** 1453 * Checks whether a given attribute name/value is defined. 1454 * 1455 * @param name the non-null attribute name 1456 * @param value the attribute value 1457 * @return true if the name/value is defined 1458 * @see AttributeSet#containsAttribute 1459 */ 1460 public boolean containsAttribute(Object name, Object value) { 1461 return attributes.containsAttribute(name, value); 1462 } 1463 1464 1465 /** 1466 * Checks whether the element contains all the attributes. 1467 * 1468 * @param attrs the attributes to check 1469 * @return true if the element contains all the attributes 1470 * @see AttributeSet#containsAttributes 1471 */ 1472 public boolean containsAttributes(AttributeSet attrs) { 1473 return attributes.containsAttributes(attrs); 1474 } 1475 1476 /** 1477 * Gets attributes from the parent. 1478 * If not overriden, the resolving parent defaults to 1479 * the parent element. 1480 * 1481 * @return the attributes from the parent 1482 * @see AttributeSet#getResolveParent 1483 */ 1484 public AttributeSet getResolveParent() { 1485 return attributes.getResolveParent(); 1486 } 1487 1488 // --- MutableAttributeSet ---------------------------------- 1489 // should fetch a new immutable record for the field 1490 // "attributes". 1491 1492 /** 1493 * Adds an attribute. 1494 * 1495 * @param name the non-null attribute name 1496 * @param value the attribute value 1497 * @see MutableAttributeSet#addAttribute 1498 */ 1499 public void addAttribute(Object name, Object value) { 1500 StyleContext context = StyleContext.this; 1501 attributes = context.addAttribute(attributes, name, value); 1502 fireStateChanged(); 1503 } 1504 1505 /** 1506 * Adds a set of attributes to the element. 1507 * 1508 * @param attr the attributes to add 1509 * @see MutableAttributeSet#addAttribute 1510 */ 1511 public void addAttributes(AttributeSet attr) { 1512 StyleContext context = StyleContext.this; 1513 attributes = context.addAttributes(attributes, attr); 1514 fireStateChanged(); 1515 } 1516 1517 /** 1518 * Removes an attribute from the set. 1519 * 1520 * @param name the non-null attribute name 1521 * @see MutableAttributeSet#removeAttribute 1522 */ 1523 public void removeAttribute(Object name) { 1524 StyleContext context = StyleContext.this; 1525 attributes = context.removeAttribute(attributes, name); 1526 fireStateChanged(); 1527 } 1528 1529 /** 1530 * Removes a set of attributes for the element. 1531 * 1532 * @param names the attribute names 1533 * @see MutableAttributeSet#removeAttributes 1534 */ 1535 public void removeAttributes(Enumeration<?> names) { 1536 StyleContext context = StyleContext.this; 1537 attributes = context.removeAttributes(attributes, names); 1538 fireStateChanged(); 1539 } 1540 1541 /** 1542 * Removes a set of attributes for the element. 1543 * 1544 * @param attrs the attributes 1545 * @see MutableAttributeSet#removeAttributes 1546 */ 1547 public void removeAttributes(AttributeSet attrs) { 1548 StyleContext context = StyleContext.this; 1549 if (attrs == this) { 1550 attributes = context.getEmptySet(); 1551 } else { 1552 attributes = context.removeAttributes(attributes, attrs); 1553 } 1554 fireStateChanged(); 1555 } 1556 1557 /** 1558 * Sets the resolving parent. 1559 * 1560 * @param parent the parent, null if none 1561 * @see MutableAttributeSet#setResolveParent 1562 */ 1563 public void setResolveParent(AttributeSet parent) { 1564 if (parent != null) { 1565 addAttribute(StyleConstants.ResolveAttribute, parent); 1566 } else { 1567 removeAttribute(StyleConstants.ResolveAttribute); 1568 } 1569 } 1570 1571 // --- serialization --------------------------------------------- 1572 1573 private void writeObject(ObjectOutputStream s) throws IOException { 1574 s.defaultWriteObject(); 1575 writeAttributeSet(s, attributes); 1576 } 1577 1578 private void readObject(ObjectInputStream s) 1579 throws ClassNotFoundException, IOException 1580 { 1581 s.defaultReadObject(); 1582 attributes = SimpleAttributeSet.EMPTY; 1583 readAttributeSet(s, this); 1584 } 1585 1586 // --- member variables ----------------------------------------------- 1587 1588 /** 1589 * The change listeners for the model. 1590 */ 1591 protected EventListenerList listenerList = new EventListenerList(); 1592 1593 /** 1594 * Only one ChangeEvent is needed per model instance since the 1595 * event's only (read-only) state is the source property. The source 1596 * of events generated here is always "this". 1597 */ 1598 protected transient ChangeEvent changeEvent = null; 1599 1600 /** 1601 * Inner AttributeSet implementation, which may be an 1602 * immutable unique set being shared. 1603 */ 1604 private transient AttributeSet attributes; 1605 1606 } 1607 1608 static { 1609 // initialize the static key registry with the StyleConstants keys 1610 try { 1611 int n = StyleConstants.keys.length; 1612 for (int i = 0; i < n; i++) { 1613 StyleContext.registerStaticAttributeKey(StyleConstants.keys[i]); 1614 } 1615 } catch (Throwable e) { 1616 e.printStackTrace(); 1617 } 1618 } 1619 1620 1621 }