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