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