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