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<>(); 718 search = new SimpleAttributeSet(); 719 attributesPool = Collections. 720 synchronizedMap(new WeakHashMap<SmallAttributeSet, 721 WeakReference<SmallAttributeSet>>()); 722 723 ObjectInputStream.GetField f = s.readFields(); 724 Style newStyles = (Style) f.get("styles", null); 725 if (newStyles == null) { 726 throw new InvalidObjectException("Null styles"); 727 } 728 styles = newStyles; 729 unusedSets = f.get("unusedSets", 0); 730 } 731 732 // --- variables --------------------------------------------------- 733 734 /** 735 * The name given to the default logical style attached 736 * to paragraphs. 737 */ 738 public static final String DEFAULT_STYLE = "default"; 739 740 private static Hashtable<Object, String> freezeKeyMap; 741 private static Hashtable<String, Object> thawKeyMap; 742 743 private Style styles; 744 private transient FontKey fontSearch = new FontKey(null, 0, 0); 745 private transient Hashtable<FontKey, Font> fontTable = new Hashtable<>(); 746 747 private transient Map<SmallAttributeSet, WeakReference<SmallAttributeSet>> attributesPool = Collections. 748 synchronizedMap(new WeakHashMap<SmallAttributeSet, WeakReference<SmallAttributeSet>>()); 749 private transient MutableAttributeSet search = new SimpleAttributeSet(); 750 751 /** 752 * Number of immutable sets that are not currently 753 * being used. This helps indicate when the sets need 754 * to be cleaned out of the hashtable they are stored 755 * in. 756 */ 757 private int unusedSets; 758 759 /** 760 * The threshold for no longer sharing the set of attributes 761 * in an immutable table. 762 */ 763 static final int THRESHOLD = 9; 764 765 /** 766 * This class holds a small number of attributes in an array. 767 * The storage format is key, value, key, value, etc. The size 768 * of the set is the length of the array divided by two. By 769 * default, this is the class that will be used to store attributes 770 * when held in the compact sharable form. 771 */ 772 public class SmallAttributeSet implements AttributeSet { 773 774 public SmallAttributeSet(Object[] attributes) { 775 this.attributes = attributes; 776 updateResolveParent(); 777 } 778 779 public SmallAttributeSet(AttributeSet attrs) { 780 int n = attrs.getAttributeCount(); 781 Object[] tbl = new Object[2 * n]; 782 Enumeration<?> names = attrs.getAttributeNames(); 783 int i = 0; 784 while (names.hasMoreElements()) { 785 tbl[i] = names.nextElement(); 786 tbl[i+1] = attrs.getAttribute(tbl[i]); 787 i += 2; 788 } 789 attributes = tbl; 790 updateResolveParent(); 791 } 792 793 private void updateResolveParent() { 794 resolveParent = null; 795 Object[] tbl = attributes; 796 for (int i = 0; i < tbl.length; i += 2) { 797 if (tbl[i] == StyleConstants.ResolveAttribute) { 798 resolveParent = (AttributeSet)tbl[i + 1]; 799 break; 800 } 801 } 802 } 803 804 Object getLocalAttribute(Object nm) { 805 if (nm == StyleConstants.ResolveAttribute) { 806 return resolveParent; 807 } 808 Object[] tbl = attributes; 809 for (int i = 0; i < tbl.length; i += 2) { 810 if (nm.equals(tbl[i])) { 811 return tbl[i+1]; 812 } 813 } 814 return null; 815 } 816 817 // --- Object methods ------------------------- 818 819 /** 820 * Returns a string showing the key/value pairs 821 */ 822 public String toString() { 823 String s = "{"; 824 Object[] tbl = attributes; 825 for (int i = 0; i < tbl.length; i += 2) { 826 if (tbl[i+1] instanceof AttributeSet) { 827 // don't recurse 828 s = s + tbl[i] + "=" + "AttributeSet" + ","; 829 } else { 830 s = s + tbl[i] + "=" + tbl[i+1] + ","; 831 } 832 } 833 s = s + "}"; 834 return s; 835 } 836 837 /** 838 * Returns a hashcode for this set of attributes. 839 * @return a hashcode value for this set of attributes. 840 */ 841 public int hashCode() { 842 int code = 0; 843 Object[] tbl = attributes; 844 for (int i = 1; i < tbl.length; i += 2) { 845 code ^= tbl[i].hashCode(); 846 } 847 return code; 848 } 849 850 /** 851 * Compares this object to the specified object. 852 * The result is <code>true</code> if the object is an equivalent 853 * set of attributes. 854 * @param obj the object to compare with. 855 * @return <code>true</code> if the objects are equal; 856 * <code>false</code> otherwise. 857 */ 858 public boolean equals(Object obj) { 859 if (obj instanceof AttributeSet) { 860 AttributeSet attrs = (AttributeSet) obj; 861 return ((getAttributeCount() == attrs.getAttributeCount()) && 862 containsAttributes(attrs)); 863 } 864 return false; 865 } 866 867 /** 868 * Clones a set of attributes. Since the set is immutable, a 869 * clone is basically the same set. 870 * 871 * @return the set of attributes 872 */ 873 public Object clone() { 874 return this; 875 } 876 877 // --- AttributeSet methods ---------------------------- 878 879 /** 880 * Gets the number of attributes that are defined. 881 * 882 * @return the number of attributes 883 * @see AttributeSet#getAttributeCount 884 */ 885 public int getAttributeCount() { 886 return attributes.length / 2; 887 } 888 889 /** 890 * Checks whether a given attribute is defined. 891 * 892 * @param key the attribute key 893 * @return true if the attribute is defined 894 * @see AttributeSet#isDefined 895 */ 896 public boolean isDefined(Object key) { 897 Object[] a = attributes; 898 int n = a.length; 899 for (int i = 0; i < n; i += 2) { 900 if (key.equals(a[i])) { 901 return true; 902 } 903 } 904 return false; 905 } 906 907 /** 908 * Checks whether two attribute sets are equal. 909 * 910 * @param attr the attribute set to check against 911 * @return true if the same 912 * @see AttributeSet#isEqual 913 */ 914 public boolean isEqual(AttributeSet attr) { 915 if (attr instanceof SmallAttributeSet) { 916 return attr == this; 917 } 918 return ((getAttributeCount() == attr.getAttributeCount()) && 919 containsAttributes(attr)); 920 } 921 922 /** 923 * Copies a set of attributes. 924 * 925 * @return the copy 926 * @see AttributeSet#copyAttributes 927 */ 928 public AttributeSet copyAttributes() { 929 return this; 930 } 931 932 /** 933 * Gets the value of an attribute. 934 * 935 * @param key the attribute name 936 * @return the attribute value 937 * @see AttributeSet#getAttribute 938 */ 939 public Object getAttribute(Object key) { 940 Object value = getLocalAttribute(key); 941 if (value == null) { 942 AttributeSet parent = getResolveParent(); 943 if (parent != null) 944 value = parent.getAttribute(key); 945 } 946 return value; 947 } 948 949 /** 950 * Gets the names of all attributes. 951 * 952 * @return the attribute names 953 * @see AttributeSet#getAttributeNames 954 */ 955 public Enumeration<?> getAttributeNames() { 956 return new KeyEnumeration(attributes); 957 } 958 959 /** 960 * Checks whether a given attribute name/value is defined. 961 * 962 * @param name the attribute name 963 * @param value the attribute value 964 * @return true if the name/value is defined 965 * @see AttributeSet#containsAttribute 966 */ 967 public boolean containsAttribute(Object name, Object value) { 968 return value.equals(getAttribute(name)); 969 } 970 971 /** 972 * Checks whether the attribute set contains all of 973 * the given attributes. 974 * 975 * @param attrs the attributes to check 976 * @return true if the element contains all the attributes 977 * @see AttributeSet#containsAttributes 978 */ 979 public boolean containsAttributes(AttributeSet attrs) { 980 boolean result = true; 981 982 Enumeration<?> names = attrs.getAttributeNames(); 983 while (result && names.hasMoreElements()) { 984 Object name = names.nextElement(); 985 result = attrs.getAttribute(name).equals(getAttribute(name)); 986 } 987 988 return result; 989 } 990 991 /** 992 * If not overriden, the resolving parent defaults to 993 * the parent element. 994 * 995 * @return the attributes from the parent 996 * @see AttributeSet#getResolveParent 997 */ 998 public AttributeSet getResolveParent() { 999 return resolveParent; 1000 } 1001 1002 // --- variables ----------------------------------------- 1003 1004 Object[] attributes; 1005 // This is also stored in attributes 1006 AttributeSet resolveParent; 1007 } 1008 1009 /** 1010 * An enumeration of the keys in a SmallAttributeSet. 1011 */ 1012 class KeyEnumeration implements Enumeration<Object> { 1013 1014 KeyEnumeration(Object[] attr) { 1015 this.attr = attr; 1016 i = 0; 1017 } 1018 1019 /** 1020 * Tests if this enumeration contains more elements. 1021 * 1022 * @return <code>true</code> if this enumeration contains more elements; 1023 * <code>false</code> otherwise. 1024 * @since 1.0 1025 */ 1026 public boolean hasMoreElements() { 1027 return i < attr.length; 1028 } 1029 1030 /** 1031 * Returns the next element of this enumeration. 1032 * 1033 * @return the next element of this enumeration. 1034 * @exception NoSuchElementException if no more elements exist. 1035 * @since 1.0 1036 */ 1037 public Object nextElement() { 1038 if (i < attr.length) { 1039 Object o = attr[i]; 1040 i += 2; 1041 return o; 1042 } 1043 throw new NoSuchElementException(); 1044 } 1045 1046 Object[] attr; 1047 int i; 1048 } 1049 1050 /** 1051 * Sorts the key strings so that they can be very quickly compared 1052 * in the attribute set searches. 1053 */ 1054 class KeyBuilder { 1055 1056 public void initialize(AttributeSet a) { 1057 if (a instanceof SmallAttributeSet) { 1058 initialize(((SmallAttributeSet)a).attributes); 1059 } else { 1060 keys.removeAllElements(); 1061 data.removeAllElements(); 1062 Enumeration<?> names = a.getAttributeNames(); 1063 while (names.hasMoreElements()) { 1064 Object name = names.nextElement(); 1065 addAttribute(name, a.getAttribute(name)); 1066 } 1067 } 1068 } 1069 1070 /** 1071 * Initialize with a set of already sorted 1072 * keys (data from an existing SmallAttributeSet). 1073 */ 1074 private void initialize(Object[] sorted) { 1075 keys.removeAllElements(); 1076 data.removeAllElements(); 1077 int n = sorted.length; 1078 for (int i = 0; i < n; i += 2) { 1079 keys.addElement(sorted[i]); 1080 data.addElement(sorted[i+1]); 1081 } 1082 } 1083 1084 /** 1085 * Creates a table of sorted key/value entries 1086 * suitable for creation of an instance of 1087 * SmallAttributeSet. 1088 */ 1089 public Object[] createTable() { 1090 int n = keys.size(); 1091 Object[] tbl = new Object[2 * n]; 1092 for (int i = 0; i < n; i ++) { 1093 int offs = 2 * i; 1094 tbl[offs] = keys.elementAt(i); 1095 tbl[offs + 1] = data.elementAt(i); 1096 } 1097 return tbl; 1098 } 1099 1100 /** 1101 * The number of key/value pairs contained 1102 * in the current key being forged. 1103 */ 1104 int getCount() { 1105 return keys.size(); 1106 } 1107 1108 /** 1109 * Adds a key/value to the set. 1110 */ 1111 public void addAttribute(Object key, Object value) { 1112 keys.addElement(key); 1113 data.addElement(value); 1114 } 1115 1116 /** 1117 * Adds a set of key/value pairs to the set. 1118 */ 1119 public void addAttributes(AttributeSet attr) { 1120 if (attr instanceof SmallAttributeSet) { 1121 // avoid searching the keys, they are already interned. 1122 Object[] tbl = ((SmallAttributeSet)attr).attributes; 1123 int n = tbl.length; 1124 for (int i = 0; i < n; i += 2) { 1125 addAttribute(tbl[i], tbl[i+1]); 1126 } 1127 } else { 1128 Enumeration<?> names = attr.getAttributeNames(); 1129 while (names.hasMoreElements()) { 1130 Object name = names.nextElement(); 1131 addAttribute(name, attr.getAttribute(name)); 1132 } 1133 } 1134 } 1135 1136 /** 1137 * Removes the given name from the set. 1138 */ 1139 public void removeAttribute(Object key) { 1140 int n = keys.size(); 1141 for (int i = 0; i < n; i++) { 1142 if (keys.elementAt(i).equals(key)) { 1143 keys.removeElementAt(i); 1144 data.removeElementAt(i); 1145 return; 1146 } 1147 } 1148 } 1149 1150 /** 1151 * Removes the set of keys from the set. 1152 */ 1153 public void removeAttributes(Enumeration<?> names) { 1154 while (names.hasMoreElements()) { 1155 Object name = names.nextElement(); 1156 removeAttribute(name); 1157 } 1158 } 1159 1160 /** 1161 * Removes the set of matching attributes from the set. 1162 */ 1163 public void removeAttributes(AttributeSet attr) { 1164 Enumeration<?> names = attr.getAttributeNames(); 1165 while (names.hasMoreElements()) { 1166 Object name = names.nextElement(); 1167 Object value = attr.getAttribute(name); 1168 removeSearchAttribute(name, value); 1169 } 1170 } 1171 1172 private void removeSearchAttribute(Object ikey, Object value) { 1173 int n = keys.size(); 1174 for (int i = 0; i < n; i++) { 1175 if (keys.elementAt(i).equals(ikey)) { 1176 if (data.elementAt(i).equals(value)) { 1177 keys.removeElementAt(i); 1178 data.removeElementAt(i); 1179 } 1180 return; 1181 } 1182 } 1183 } 1184 1185 private Vector<Object> keys = new Vector<Object>(); 1186 private Vector<Object> data = new Vector<Object>(); 1187 } 1188 1189 /** 1190 * key for a font table 1191 */ 1192 static class FontKey { 1193 1194 private String family; 1195 private int style; 1196 private int size; 1197 1198 /** 1199 * Constructs a font key. 1200 */ 1201 public FontKey(String family, int style, int size) { 1202 setValue(family, style, size); 1203 } 1204 1205 public void setValue(String family, int style, int size) { 1206 this.family = (family != null) ? family.intern() : null; 1207 this.style = style; 1208 this.size = size; 1209 } 1210 1211 /** 1212 * Returns a hashcode for this font. 1213 * @return a hashcode value for this font. 1214 */ 1215 public int hashCode() { 1216 int fhash = (family != null) ? family.hashCode() : 0; 1217 return fhash ^ style ^ size; 1218 } 1219 1220 /** 1221 * Compares this object to the specified object. 1222 * The result is <code>true</code> if and only if the argument is not 1223 * <code>null</code> and is a <code>Font</code> object with the same 1224 * name, style, and point size as this font. 1225 * @param obj the object to compare this font with. 1226 * @return <code>true</code> if the objects are equal; 1227 * <code>false</code> otherwise. 1228 */ 1229 public boolean equals(Object obj) { 1230 if (obj instanceof FontKey) { 1231 FontKey font = (FontKey)obj; 1232 return (size == font.size) && (style == font.style) && (family == font.family); 1233 } 1234 return false; 1235 } 1236 1237 } 1238 1239 /** 1240 * A collection of attributes, typically used to represent 1241 * character and paragraph styles. This is an implementation 1242 * of MutableAttributeSet that can be observed if desired. 1243 * These styles will take advantage of immutability while 1244 * the sets are small enough, and may be substantially more 1245 * efficient than something like SimpleAttributeSet. 1246 * <p> 1247 * <strong>Warning:</strong> 1248 * Serialized objects of this class will not be compatible with 1249 * future Swing releases. The current serialization support is 1250 * appropriate for short term storage or RMI between applications running 1251 * the same version of Swing. As of 1.4, support for long term storage 1252 * of all JavaBeans™ 1253 * has been added to the <code>java.beans</code> package. 1254 * Please see {@link java.beans.XMLEncoder}. 1255 */ 1256 @SuppressWarnings("serial") // Same-version serialization only 1257 public class NamedStyle implements Style, Serializable { 1258 1259 /** 1260 * Creates a new named style. 1261 * 1262 * @param name the style name, null for unnamed 1263 * @param parent the parent style, null if none 1264 * @since 1.4 1265 */ 1266 public NamedStyle(String name, Style parent) { 1267 attributes = getEmptySet(); 1268 if (name != null) { 1269 setName(name); 1270 } 1271 if (parent != null) { 1272 setResolveParent(parent); 1273 } 1274 } 1275 1276 /** 1277 * Creates a new named style. 1278 * 1279 * @param parent the parent style, null if none 1280 * @since 1.4 1281 */ 1282 public NamedStyle(Style parent) { 1283 this(null, parent); 1284 } 1285 1286 /** 1287 * Creates a new named style, with a null name and parent. 1288 */ 1289 public NamedStyle() { 1290 attributes = getEmptySet(); 1291 } 1292 1293 /** 1294 * Converts the style to a string. 1295 * 1296 * @return the string 1297 */ 1298 public String toString() { 1299 return "NamedStyle:" + getName() + " " + attributes; 1300 } 1301 1302 /** 1303 * Fetches the name of the style. A style is not required to be named, 1304 * so null is returned if there is no name associated with the style. 1305 * 1306 * @return the name 1307 */ 1308 public String getName() { 1309 if (isDefined(StyleConstants.NameAttribute)) { 1310 return getAttribute(StyleConstants.NameAttribute).toString(); 1311 } 1312 return null; 1313 } 1314 1315 /** 1316 * Changes the name of the style. Does nothing with a null name. 1317 * 1318 * @param name the new name 1319 */ 1320 public void setName(String name) { 1321 if (name != null) { 1322 this.addAttribute(StyleConstants.NameAttribute, name); 1323 } 1324 } 1325 1326 /** 1327 * Adds a change listener. 1328 * 1329 * @param l the change listener 1330 */ 1331 public void addChangeListener(ChangeListener l) { 1332 listenerList.add(ChangeListener.class, l); 1333 } 1334 1335 /** 1336 * Removes a change listener. 1337 * 1338 * @param l the change listener 1339 */ 1340 public void removeChangeListener(ChangeListener l) { 1341 listenerList.remove(ChangeListener.class, l); 1342 } 1343 1344 1345 /** 1346 * Returns an array of all the <code>ChangeListener</code>s added 1347 * to this NamedStyle with addChangeListener(). 1348 * 1349 * @return all of the <code>ChangeListener</code>s added or an empty 1350 * array if no listeners have been added 1351 * @since 1.4 1352 */ 1353 public ChangeListener[] getChangeListeners() { 1354 return listenerList.getListeners(ChangeListener.class); 1355 } 1356 1357 1358 /** 1359 * Notifies all listeners that have registered interest for 1360 * notification on this event type. The event instance 1361 * is lazily created using the parameters passed into 1362 * the fire method. 1363 * 1364 * @see EventListenerList 1365 */ 1366 protected void fireStateChanged() { 1367 // Guaranteed to return a non-null array 1368 Object[] listeners = listenerList.getListenerList(); 1369 // Process the listeners last to first, notifying 1370 // those that are interested in this event 1371 for (int i = listeners.length-2; i>=0; i-=2) { 1372 if (listeners[i]==ChangeListener.class) { 1373 // Lazily create the event: 1374 if (changeEvent == null) 1375 changeEvent = new ChangeEvent(this); 1376 ((ChangeListener)listeners[i+1]).stateChanged(changeEvent); 1377 } 1378 } 1379 } 1380 1381 /** 1382 * Return an array of all the listeners of the given type that 1383 * were added to this model. 1384 * 1385 * @return all of the objects receiving <em>listenerType</em> notifications 1386 * from this model 1387 * 1388 * @since 1.3 1389 */ 1390 public <T extends EventListener> T[] getListeners(Class<T> listenerType) { 1391 return listenerList.getListeners(listenerType); 1392 } 1393 1394 // --- AttributeSet ---------------------------- 1395 // delegated to the immutable field "attributes" 1396 1397 /** 1398 * Gets the number of attributes that are defined. 1399 * 1400 * @return the number of attributes >= 0 1401 * @see AttributeSet#getAttributeCount 1402 */ 1403 public int getAttributeCount() { 1404 return attributes.getAttributeCount(); 1405 } 1406 1407 /** 1408 * Checks whether a given attribute is defined. 1409 * 1410 * @param attrName the non-null attribute name 1411 * @return true if the attribute is defined 1412 * @see AttributeSet#isDefined 1413 */ 1414 public boolean isDefined(Object attrName) { 1415 return attributes.isDefined(attrName); 1416 } 1417 1418 /** 1419 * Checks whether two attribute sets are equal. 1420 * 1421 * @param attr the attribute set to check against 1422 * @return true if the same 1423 * @see AttributeSet#isEqual 1424 */ 1425 public boolean isEqual(AttributeSet attr) { 1426 return attributes.isEqual(attr); 1427 } 1428 1429 /** 1430 * Copies a set of attributes. 1431 * 1432 * @return the copy 1433 * @see AttributeSet#copyAttributes 1434 */ 1435 public AttributeSet copyAttributes() { 1436 NamedStyle a = new NamedStyle(); 1437 a.attributes = attributes.copyAttributes(); 1438 return a; 1439 } 1440 1441 /** 1442 * Gets the value of an attribute. 1443 * 1444 * @param attrName the non-null attribute name 1445 * @return the attribute value 1446 * @see AttributeSet#getAttribute 1447 */ 1448 public Object getAttribute(Object attrName) { 1449 return attributes.getAttribute(attrName); 1450 } 1451 1452 /** 1453 * Gets the names of all attributes. 1454 * 1455 * @return the attribute names as an enumeration 1456 * @see AttributeSet#getAttributeNames 1457 */ 1458 public Enumeration<?> getAttributeNames() { 1459 return attributes.getAttributeNames(); 1460 } 1461 1462 /** 1463 * Checks whether a given attribute name/value is defined. 1464 * 1465 * @param name the non-null attribute name 1466 * @param value the attribute value 1467 * @return true if the name/value is defined 1468 * @see AttributeSet#containsAttribute 1469 */ 1470 public boolean containsAttribute(Object name, Object value) { 1471 return attributes.containsAttribute(name, value); 1472 } 1473 1474 1475 /** 1476 * Checks whether the element contains all the attributes. 1477 * 1478 * @param attrs the attributes to check 1479 * @return true if the element contains all the attributes 1480 * @see AttributeSet#containsAttributes 1481 */ 1482 public boolean containsAttributes(AttributeSet attrs) { 1483 return attributes.containsAttributes(attrs); 1484 } 1485 1486 /** 1487 * Gets attributes from the parent. 1488 * If not overriden, the resolving parent defaults to 1489 * the parent element. 1490 * 1491 * @return the attributes from the parent 1492 * @see AttributeSet#getResolveParent 1493 */ 1494 public AttributeSet getResolveParent() { 1495 return attributes.getResolveParent(); 1496 } 1497 1498 // --- MutableAttributeSet ---------------------------------- 1499 // should fetch a new immutable record for the field 1500 // "attributes". 1501 1502 /** 1503 * Adds an attribute. 1504 * 1505 * @param name the non-null attribute name 1506 * @param value the attribute value 1507 * @see MutableAttributeSet#addAttribute 1508 */ 1509 public void addAttribute(Object name, Object value) { 1510 StyleContext context = StyleContext.this; 1511 attributes = context.addAttribute(attributes, name, value); 1512 fireStateChanged(); 1513 } 1514 1515 /** 1516 * Adds a set of attributes to the element. 1517 * 1518 * @param attr the attributes to add 1519 * @see MutableAttributeSet#addAttribute 1520 */ 1521 public void addAttributes(AttributeSet attr) { 1522 StyleContext context = StyleContext.this; 1523 attributes = context.addAttributes(attributes, attr); 1524 fireStateChanged(); 1525 } 1526 1527 /** 1528 * Removes an attribute from the set. 1529 * 1530 * @param name the non-null attribute name 1531 * @see MutableAttributeSet#removeAttribute 1532 */ 1533 public void removeAttribute(Object name) { 1534 StyleContext context = StyleContext.this; 1535 attributes = context.removeAttribute(attributes, name); 1536 fireStateChanged(); 1537 } 1538 1539 /** 1540 * Removes a set of attributes for the element. 1541 * 1542 * @param names the attribute names 1543 * @see MutableAttributeSet#removeAttributes 1544 */ 1545 public void removeAttributes(Enumeration<?> names) { 1546 StyleContext context = StyleContext.this; 1547 attributes = context.removeAttributes(attributes, names); 1548 fireStateChanged(); 1549 } 1550 1551 /** 1552 * Removes a set of attributes for the element. 1553 * 1554 * @param attrs the attributes 1555 * @see MutableAttributeSet#removeAttributes 1556 */ 1557 public void removeAttributes(AttributeSet attrs) { 1558 StyleContext context = StyleContext.this; 1559 if (attrs == this) { 1560 attributes = context.getEmptySet(); 1561 } else { 1562 attributes = context.removeAttributes(attributes, attrs); 1563 } 1564 fireStateChanged(); 1565 } 1566 1567 /** 1568 * Sets the resolving parent. 1569 * 1570 * @param parent the parent, null if none 1571 * @see MutableAttributeSet#setResolveParent 1572 */ 1573 public void setResolveParent(AttributeSet parent) { 1574 if (parent != null) { 1575 addAttribute(StyleConstants.ResolveAttribute, parent); 1576 } else { 1577 removeAttribute(StyleConstants.ResolveAttribute); 1578 } 1579 } 1580 1581 // --- serialization --------------------------------------------- 1582 1583 private void writeObject(ObjectOutputStream s) throws IOException { 1584 s.defaultWriteObject(); 1585 writeAttributeSet(s, attributes); 1586 } 1587 1588 private void readObject(ObjectInputStream s) 1589 throws ClassNotFoundException, IOException 1590 { 1591 s.defaultReadObject(); 1592 attributes = SimpleAttributeSet.EMPTY; 1593 readAttributeSet(s, this); 1594 } 1595 1596 // --- member variables ----------------------------------------------- 1597 1598 /** 1599 * The change listeners for the model. 1600 */ 1601 protected EventListenerList listenerList = new EventListenerList(); 1602 1603 /** 1604 * Only one ChangeEvent is needed per model instance since the 1605 * event's only (read-only) state is the source property. The source 1606 * of events generated here is always "this". 1607 */ 1608 protected transient ChangeEvent changeEvent = null; 1609 1610 /** 1611 * Inner AttributeSet implementation, which may be an 1612 * immutable unique set being shared. 1613 */ 1614 private transient AttributeSet attributes; 1615 1616 } 1617 1618 static { 1619 // initialize the static key registry with the StyleConstants keys 1620 try { 1621 int n = StyleConstants.keys.length; 1622 for (int i = 0; i < n; i++) { 1623 StyleContext.registerStaticAttributeKey(StyleConstants.keys[i]); 1624 } 1625 } catch (Throwable e) { 1626 e.printStackTrace(); 1627 } 1628 } 1629 1630 1631 }