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