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