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