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&trade;
  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 &gt;= 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&trade;
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 &gt;= 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 }