001    /* StyleContext.java --
002       Copyright (C) 2004 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.text;
040    
041    import java.awt.Color;
042    import java.awt.Font;
043    import java.awt.FontMetrics;
044    import java.awt.Toolkit;
045    import java.io.IOException;
046    import java.io.NotSerializableException;
047    import java.io.ObjectInputStream;
048    import java.io.ObjectOutputStream;
049    import java.io.Serializable;
050    import java.lang.ref.WeakReference;
051    import java.util.Collections;
052    import java.util.Enumeration;
053    import java.util.EventListener;
054    import java.util.Hashtable;
055    import java.util.Iterator;
056    import java.util.Map;
057    import java.util.WeakHashMap;
058    
059    import javax.swing.event.ChangeEvent;
060    import javax.swing.event.ChangeListener;
061    import javax.swing.event.EventListenerList;
062    
063    public class StyleContext 
064      implements Serializable, AbstractDocument.AttributeContext
065    {
066      /** The serialization UID (compatible with JDK1.5). */
067      private static final long serialVersionUID = 8042858831190784241L;
068    
069      public class NamedStyle
070        implements Serializable, Style
071      {
072        /** The serialization UID (compatible with JDK1.5). */
073        private static final long serialVersionUID = -6690628971806226374L;
074    
075        protected transient ChangeEvent changeEvent;
076        protected EventListenerList listenerList;
077          
078        private transient AttributeSet attributes;
079    
080        public NamedStyle()
081        {
082          this(null, null);
083        }
084    
085        public NamedStyle(Style parent)
086        {
087          this(null, parent);
088        }
089    
090        public NamedStyle(String name, Style parent)
091        {
092          attributes = getEmptySet();
093          listenerList = new EventListenerList();
094          if (name != null)
095            setName(name);
096          if (parent != null)
097            setResolveParent(parent);
098        }
099    
100        public String getName()
101        {
102          String name = null;
103          if (isDefined(StyleConstants.NameAttribute))
104            name = getAttribute(StyleConstants.NameAttribute).toString();
105          return name;
106        }
107    
108        public void setName(String n)
109        {
110          if (n != null)
111            addAttribute(StyleConstants.NameAttribute, n);
112        }
113    
114        public void addChangeListener(ChangeListener l)
115        {
116          listenerList.add(ChangeListener.class, l);
117        }
118          
119        public void removeChangeListener(ChangeListener l)
120        {
121          listenerList.remove(ChangeListener.class, l);
122        }
123          
124        public <T extends EventListener> T[] getListeners(Class<T> listenerType)
125        {
126          return listenerList.getListeners(listenerType);
127        }
128    
129        public ChangeListener[] getChangeListeners()
130        {
131          return (ChangeListener[]) getListeners(ChangeListener.class);
132        }
133    
134        protected  void fireStateChanged()
135        {
136          ChangeListener[] listeners = getChangeListeners();
137          for (int i = 0; i < listeners.length; ++i)
138            {
139              // Lazily create event.
140              if (changeEvent == null)
141                changeEvent = new ChangeEvent(this);
142              listeners[i].stateChanged(changeEvent);
143            }
144        }
145    
146        public void addAttribute(Object name, Object value)
147        {
148          attributes = StyleContext.this.addAttribute(attributes, name, value);
149          fireStateChanged();
150        }
151    
152        public void addAttributes(AttributeSet attr)
153        {
154          attributes = StyleContext.this.addAttributes(attributes, attr);
155          fireStateChanged();
156        }
157    
158        public boolean containsAttribute(Object name, Object value)
159        {
160          return attributes.containsAttribute(name, value);
161        }
162          
163        public boolean containsAttributes(AttributeSet attrs)
164        {
165          return attributes.containsAttributes(attrs);
166        }
167    
168        public AttributeSet copyAttributes()
169        {
170          // The RI returns a NamedStyle as copy, so do we.
171          NamedStyle copy = new NamedStyle();
172          copy.attributes = attributes.copyAttributes();
173          return copy;
174        }
175                
176        public Object getAttribute(Object attrName)
177        {
178          return attributes.getAttribute(attrName);
179        }
180    
181        public int getAttributeCount()
182        {
183          return attributes.getAttributeCount();
184        }
185    
186        public Enumeration<?> getAttributeNames()
187        {
188          return attributes.getAttributeNames();
189        }
190          
191        public boolean isDefined(Object attrName)
192        {
193          return attributes.isDefined(attrName);        
194        }
195    
196        public boolean isEqual(AttributeSet attr)
197        {
198          return attributes.isEqual(attr);
199        }
200    
201        public void removeAttribute(Object name)
202        {
203          attributes = StyleContext.this.removeAttribute(attributes, name);
204          fireStateChanged();
205        }
206    
207        public void removeAttributes(AttributeSet attrs)
208        {
209          attributes = StyleContext.this.removeAttributes(attributes, attrs);
210          fireStateChanged();
211        }
212    
213        public void removeAttributes(Enumeration<?> names)
214        {
215          attributes = StyleContext.this.removeAttributes(attributes, names);
216          fireStateChanged();
217        }
218    
219    
220        public AttributeSet getResolveParent()
221        {
222          return attributes.getResolveParent();        
223        }
224    
225        public void setResolveParent(AttributeSet parent)
226        {
227          if (parent != null)
228            addAttribute(StyleConstants.ResolveAttribute, parent);
229          else
230            removeAttribute(StyleConstants.ResolveAttribute);
231        }
232          
233        public String toString()
234        {
235          return "NamedStyle:" + getName() + " " + attributes;
236        }
237    
238        private void writeObject(ObjectOutputStream s)
239          throws IOException
240        {
241          s.defaultWriteObject();
242          writeAttributeSet(s, attributes);
243        }
244    
245        private void readObject(ObjectInputStream s)
246          throws ClassNotFoundException, IOException
247        {
248          s.defaultReadObject();
249          attributes = SimpleAttributeSet.EMPTY;
250          readAttributeSet(s, this);
251        }
252      }
253      
254      public class SmallAttributeSet
255        implements AttributeSet
256      {
257        final Object [] attrs;
258        private AttributeSet resolveParent;
259        public SmallAttributeSet(AttributeSet a)
260        {
261          int n = a.getAttributeCount();
262          int i = 0;
263          attrs = new Object[n * 2];
264          Enumeration e = a.getAttributeNames();
265          while (e.hasMoreElements())
266            {
267              Object name = e.nextElement();
268              Object value = a.getAttribute(name);
269              if (name == ResolveAttribute)
270                resolveParent = (AttributeSet) value;
271              attrs[i++] = name;
272              attrs[i++] = value;
273            }
274        }
275    
276        public SmallAttributeSet(Object [] a)
277        {
278          attrs = a;
279          for (int i = 0; i < attrs.length; i += 2)
280            {
281              if (attrs[i] == ResolveAttribute)
282                resolveParent = (AttributeSet) attrs[i + 1];
283            }
284        }
285    
286        public Object clone()
287        {
288          return this;
289        }
290    
291        public boolean containsAttribute(Object name, Object value)
292        {
293          return value.equals(getAttribute(name));
294        }
295    
296        public boolean containsAttributes(AttributeSet a)
297        {
298          boolean res = true;
299          Enumeration e = a.getAttributeNames();
300          while (e.hasMoreElements() && res)
301            {
302              Object name = e.nextElement();
303              res = a.getAttribute(name).equals(getAttribute(name));
304            }
305          return res;
306        }
307    
308        public AttributeSet copyAttributes()
309        {
310          return this;
311        }
312    
313        public boolean equals(Object obj)
314        {
315          boolean eq = false;
316          if (obj instanceof AttributeSet)
317            {
318              AttributeSet atts = (AttributeSet) obj;
319              eq = getAttributeCount() == atts.getAttributeCount()
320                   && containsAttributes(atts);
321            }
322          return eq;
323        }
324     
325        public Object getAttribute(Object key)
326        {
327          Object att = null;
328          if (key == StyleConstants.ResolveAttribute)
329            att = resolveParent;
330    
331          for (int i = 0; i < attrs.length && att == null; i += 2)
332            {
333              if (attrs[i].equals(key))
334                att = attrs[i + 1];
335            }
336    
337          // Check the resolve parent, unless we're looking for the 
338          // ResolveAttribute, which must not be looked up
339          if (att == null)
340              {
341                AttributeSet parent = getResolveParent();
342                if (parent != null)
343                  att = parent.getAttribute(key);
344              }
345          
346          return att;
347        }
348    
349        public int getAttributeCount()
350        {
351          return attrs.length / 2;
352        }
353    
354        public Enumeration<?> getAttributeNames()
355        {      
356          return new Enumeration() 
357            {
358              int i = 0;
359              public boolean hasMoreElements() 
360              { 
361                return i < attrs.length; 
362              }
363              public Object nextElement() 
364              { 
365                i += 2; 
366                return attrs[i-2]; 
367              }
368            };
369        }
370    
371        public AttributeSet getResolveParent()
372        {
373          return resolveParent;
374        }
375    
376        public int hashCode()
377        {
378          return java.util.Arrays.asList(attrs).hashCode();
379        }
380    
381        public boolean isDefined(Object key)
382        {
383          for (int i = 0; i < attrs.length; i += 2)
384            {
385              if (attrs[i].equals(key))
386                return true;
387            }
388          return false;
389        }
390            
391        public boolean isEqual(AttributeSet attr)
392        {
393          boolean eq;
394          // If the other one is also a SmallAttributeSet, it is only considered
395          // equal if it's the same instance.
396          if (attr instanceof SmallAttributeSet)
397            eq = attr == this;
398          else
399            eq = getAttributeCount() == attr.getAttributeCount()
400                 && this.containsAttributes(attr);
401          return eq;
402        }
403            
404        public String toString()
405        {
406          StringBuilder sb = new StringBuilder();
407          sb.append('{');
408          for (int i = 0; i < attrs.length; i += 2)
409            {
410              if (attrs[i + 1] instanceof AttributeSet)
411                {
412                  sb.append(attrs[i]);
413                  sb.append("=AttributeSet,");
414                }
415              else
416                {
417                  sb.append(attrs[i]);
418                  sb.append('=');
419                  sb.append(attrs[i + 1]);
420                  sb.append(',');
421                }
422            }
423          sb.append("}");
424          return sb.toString();
425        }
426      }
427    
428      /**
429       * Register StyleConstant keys as static attribute keys for serialization.
430       */
431      static
432      {
433        // Don't let problems while doing this prevent class loading.
434        try
435          {
436            for (Iterator i = StyleConstants.keys.iterator(); i.hasNext();)
437              registerStaticAttributeKey(i.next());
438          }
439        catch (Throwable t)
440          {
441            t.printStackTrace();
442          }
443      }
444    
445      /**
446       * The name of the default style.
447       */
448      public static final String DEFAULT_STYLE = "default";
449      
450      static Hashtable sharedAttributeSets = new Hashtable();
451      static Hashtable sharedFonts = new Hashtable();
452    
453      static StyleContext defaultStyleContext;
454      static final int compressionThreshold = 9;
455    
456      /**
457       * These attribute keys are handled specially in serialization.
458       */
459      private static Hashtable writeAttributeKeys;
460      private static Hashtable readAttributeKeys;
461    
462      private NamedStyle styles;
463    
464      /**
465       * Used for searching attributes in the pool.
466       */
467      private transient MutableAttributeSet search = new SimpleAttributeSet();
468    
469      /**
470       * A pool of immutable AttributeSets.
471       */
472      private transient Map attributeSetPool =
473        Collections.synchronizedMap(new WeakHashMap());
474    
475      /**
476       * Creates a new instance of the style context. Add the default style
477       * to the style table.
478       */
479      public StyleContext()
480      {
481        styles = new NamedStyle(null);
482        addStyle(DEFAULT_STYLE, null);
483      }
484    
485      protected SmallAttributeSet createSmallAttributeSet(AttributeSet a)
486      {
487        return new SmallAttributeSet(a);
488      }
489      
490      protected MutableAttributeSet createLargeAttributeSet(AttributeSet a)
491      {
492        return new SimpleAttributeSet(a);
493      }
494    
495      public void addChangeListener(ChangeListener listener)
496      {
497        styles.addChangeListener(listener);
498      }
499    
500      public void removeChangeListener(ChangeListener listener)
501      {
502        styles.removeChangeListener(listener);
503      }
504    
505      public ChangeListener[] getChangeListeners()
506      {
507        return styles.getChangeListeners();
508      }
509        
510      public Style addStyle(String name, Style parent)
511      {
512        Style newStyle = new NamedStyle(name, parent);
513        if (name != null)
514          styles.addAttribute(name, newStyle);
515        return newStyle;
516      }
517    
518      public void removeStyle(String name)
519      {
520        styles.removeAttribute(name);
521      }
522    
523      /**
524       * Get the style from the style table. If the passed name
525       * matches {@link #DEFAULT_STYLE}, returns the default style.
526       * Otherwise returns the previously defined style of
527       * <code>null</code> if the style with the given name is not defined.
528       *
529       * @param name the name of the style.
530       *
531       * @return the style with the given name or null if no such defined.
532       */
533      public Style getStyle(String name)
534      {
535        return (Style) styles.getAttribute(name);
536      }
537      
538      /**
539       * Get the names of the style. The returned enumeration always
540       * contains at least one member, the default style.
541       */
542      public Enumeration<?> getStyleNames()
543      {
544        return styles.getAttributeNames();
545      }
546    
547      private void readObject(ObjectInputStream in)
548        throws ClassNotFoundException, IOException
549      {
550        search = new SimpleAttributeSet();
551        attributeSetPool = Collections.synchronizedMap(new WeakHashMap());
552        in.defaultReadObject();
553      }
554    
555      private void writeObject(ObjectOutputStream out)
556        throws IOException
557      {
558        cleanupPool();
559        out.defaultWriteObject();
560      }
561    
562      //
563      // StyleContexts only understand the "simple" model of fonts present in
564      // pre-java2d systems: fonts are a family name, a size (integral number
565      // of points), and a mask of style parameters (plain, bold, italic, or
566      // bold|italic). We have an inner class here called SimpleFontSpec which
567      // holds such triples.
568      //
569      // A SimpleFontSpec can be built for *any* AttributeSet because the size,
570      // family, and style keys in an AttributeSet have default values (defined
571      // over in StyleConstants).
572      //
573      // We keep a static cache mapping SimpleFontSpecs to java.awt.Fonts, so
574      // that we reuse Fonts between styles and style contexts.
575      // 
576    
577      private static class SimpleFontSpec
578      {
579        String family;
580        int style;
581        int size;
582        public SimpleFontSpec(String family,
583                              int style,
584                              int size)
585        {
586          this.family = family;
587          this.style = style;
588          this.size = size;
589        }
590        public boolean equals(Object obj)
591        {
592          return (obj != null)
593            && (obj instanceof SimpleFontSpec)
594            && (((SimpleFontSpec)obj).family.equals(this.family))
595            && (((SimpleFontSpec)obj).style == this.style)
596            && (((SimpleFontSpec)obj).size == this.size);
597        }
598        public int hashCode()
599        {
600          return family.hashCode() + style + size;
601        }
602      }
603      
604      public Font getFont(AttributeSet attr)
605      {
606        String family = StyleConstants.getFontFamily(attr);
607        int style = Font.PLAIN;
608        if (StyleConstants.isBold(attr))
609          style += Font.BOLD;
610        if (StyleConstants.isItalic(attr))
611          style += Font.ITALIC;      
612        int size = StyleConstants.getFontSize(attr);
613        return getFont(family, style, size);
614      }
615    
616      public Font getFont(String family, int style, int size)
617      {
618        SimpleFontSpec spec = new SimpleFontSpec(family, style, size);
619        if (sharedFonts.containsKey(spec))
620          return (Font) sharedFonts.get(spec);
621        else
622          {
623            Font tmp = new Font(family, style, size);
624            sharedFonts.put(spec, tmp);
625            return tmp;
626          }
627      }
628      
629      public FontMetrics getFontMetrics(Font f)
630      {
631        return Toolkit.getDefaultToolkit().getFontMetrics(f);
632      }
633    
634      public Color getForeground(AttributeSet a)
635      {
636        return StyleConstants.getForeground(a);
637      }
638    
639      public Color getBackground(AttributeSet a)
640      {
641        return StyleConstants.getBackground(a);
642      }
643    
644      protected int getCompressionThreshold() 
645      {
646        return compressionThreshold;
647      }
648    
649      public static StyleContext getDefaultStyleContext()
650      {
651        if (defaultStyleContext == null)
652          defaultStyleContext = new StyleContext();
653        return defaultStyleContext;
654      }
655    
656      public synchronized AttributeSet addAttribute(AttributeSet old, Object name,
657                                                    Object value)
658      {
659        AttributeSet ret;
660        if (old.getAttributeCount() + 1 < getCompressionThreshold())
661          {
662            search.removeAttributes(search);
663            search.addAttributes(old);
664            search.addAttribute(name, value);
665            reclaim(old);
666            ret = searchImmutableSet();
667          }
668        else
669          {
670            MutableAttributeSet mas = getMutableAttributeSet(old);
671            mas.addAttribute(name, value);
672            ret = mas;
673          }
674        return ret;
675      }
676    
677      public synchronized AttributeSet addAttributes(AttributeSet old,
678                                                     AttributeSet attributes)
679      {
680        AttributeSet ret;
681        if (old.getAttributeCount() + attributes.getAttributeCount()
682            < getCompressionThreshold())
683          {
684            search.removeAttributes(search);
685            search.addAttributes(old);
686            search.addAttributes(attributes);
687            reclaim(old);
688            ret = searchImmutableSet();
689          }
690        else
691          {
692            MutableAttributeSet mas = getMutableAttributeSet(old);
693            mas.addAttributes(attributes);
694            ret = mas;
695          }
696        return ret;
697      }
698    
699      public AttributeSet getEmptySet()
700      {
701        return SimpleAttributeSet.EMPTY;
702      }
703    
704      public void reclaim(AttributeSet attributes)
705      {
706        cleanupPool();
707      }
708    
709      public synchronized AttributeSet removeAttribute(AttributeSet old,
710                                                       Object name)
711      {
712        AttributeSet ret;
713        if (old.getAttributeCount() - 1 <= getCompressionThreshold())
714          {
715            search.removeAttributes(search);
716            search.addAttributes(old);
717            search.removeAttribute(name);
718            reclaim(old);
719            ret = searchImmutableSet();
720          }
721        else
722          {
723            MutableAttributeSet mas = getMutableAttributeSet(old);
724            mas.removeAttribute(name);
725            ret = mas;
726          }
727        return ret;
728      }
729    
730      public synchronized AttributeSet removeAttributes(AttributeSet old,
731                                                        AttributeSet attributes)
732      {
733        AttributeSet ret;
734        if (old.getAttributeCount() <= getCompressionThreshold())
735          {
736            search.removeAttributes(search);
737            search.addAttributes(old);
738            search.removeAttributes(attributes);
739            reclaim(old);
740            ret = searchImmutableSet();
741          }
742        else
743          {
744            MutableAttributeSet mas = getMutableAttributeSet(old);
745            mas.removeAttributes(attributes);
746            ret = mas;
747          }
748        return ret;
749      }
750    
751      public synchronized AttributeSet removeAttributes(AttributeSet old,
752                                                        Enumeration<?> names)
753      {
754        AttributeSet ret;
755        if (old.getAttributeCount() <= getCompressionThreshold())
756          {
757            search.removeAttributes(search);
758            search.addAttributes(old);
759            search.removeAttributes(names);
760            reclaim(old);
761            ret = searchImmutableSet();
762          }
763        else
764          {
765            MutableAttributeSet mas = getMutableAttributeSet(old);
766            mas.removeAttributes(names);
767            ret = mas;
768          }
769        return ret;
770      }
771    
772      /**
773       * Gets the object previously registered with registerStaticAttributeKey.
774       * 
775       * @param key - the key that was registered.
776       * @return the object previously registered with registerStaticAttributeKey.
777       */
778      public static Object getStaticAttribute(Object key)
779      {
780        if (key == null)
781          return null;
782        return readAttributeKeys.get(key);
783      }
784      
785      /**
786       * Returns the String that key will be registered with
787       * registerStaticAttributeKey.
788       * 
789       * @param key - the key that will be registered.
790       * @return the string the key will be registered with.
791       */
792      public static Object getStaticAttributeKey(Object key)
793      {
794        return key.getClass().getName() + "." + key.toString();
795      }
796    
797      /**
798       * Reads a set of attributes from the given object input stream. This will
799       * attempt to restore keys that were static objects by considering only the
800       * keys that have were registered with registerStaticAttributeKey. The
801       * attributes retrieved will be placed into the given set.
802       * 
803       * @param in - the stream to read from
804       * @param a - the set of attributes
805       * @throws ClassNotFoundException - may be encountered when reading from
806       *           stream
807       * @throws IOException - any I/O error
808       */
809      public static void readAttributeSet(ObjectInputStream in,
810                                          MutableAttributeSet a)
811        throws ClassNotFoundException, IOException
812      {
813        int count = in.readInt();
814        for (int i = 0; i < count; i++)
815          {
816            Object key = in.readObject();
817            Object val = in.readObject();
818            if (readAttributeKeys != null)
819              {
820                Object staticKey = readAttributeKeys.get(key);
821                if (staticKey != null)
822                  key = staticKey;
823                Object staticVal = readAttributeKeys.get(val);
824                if (staticVal != null)
825                  val = staticVal;
826              }
827            a.addAttribute(key, val);
828          }
829      }
830      
831      /**
832       * Serialize an attribute set in a way that is compatible with it
833       * being read in again by {@link #readAttributeSet(ObjectInputStream, MutableAttributeSet)}.
834       * In particular registered static keys are transformed properly.
835       * 
836       * @param out - stream to write to
837       * @param a - the attribute set
838       * @throws IOException - any I/O error
839       */
840      public static void writeAttributeSet(ObjectOutputStream out, AttributeSet a)
841        throws IOException
842      {
843        int count = a.getAttributeCount();
844        out.writeInt(count);
845        Enumeration e = a.getAttributeNames();
846        while (e.hasMoreElements())
847          {
848            Object key = e.nextElement();
849            // Write key.
850            if (key instanceof Serializable)
851              out.writeObject(key);
852            else
853              {
854                Object io = writeAttributeKeys.get(key);
855                if (io == null)
856                  throw new NotSerializableException(key.getClass().getName()
857                                                     + ", key: " + key);
858                out.writeObject(io);
859              }
860            // Write value.
861            Object val = a.getAttribute(key);
862            Object io = writeAttributeKeys.get(val);
863            if (val instanceof Serializable)
864              out.writeObject(io != null ? io : val);
865            else
866              {
867                if (io == null)
868                  throw new NotSerializableException(val.getClass().getName());
869                out.writeObject(io);
870              }
871          }
872      }
873    
874      /**
875       * Handles reading in the attributes. 
876       * @see #readAttributeSet(ObjectInputStream, MutableAttributeSet)
877       * 
878       * @param in - the stream to read from
879       * @param a - the set of attributes
880       * @throws ClassNotFoundException - may be encountered when reading from stream
881       * @throws IOException - any I/O error
882       */
883      public void readAttributes(ObjectInputStream in, MutableAttributeSet a)
884        throws ClassNotFoundException, IOException
885      {
886        readAttributeSet(in, a);
887      }
888    
889      /**
890       * Handles writing of the given attributes.
891       * @see #writeAttributeSet(ObjectOutputStream, AttributeSet)
892       * 
893       * @param out - stream to write to
894       * @param a - the attribute set
895       * @throws IOException - any I/O error
896       */
897      public void writeAttributes(ObjectOutputStream out, AttributeSet a)
898        throws IOException
899      {
900        writeAttributeSet(out, a);
901      }
902    
903      /**
904       * Registers an attribute key as a well-known keys. When an attribute with
905       * such a key is written to a stream, a special syntax is used so that it
906       * can be recognized when it is read back in. All attribute keys defined
907       * in <code>StyleContext</code> are registered as static keys. If you define
908       * additional attribute keys that you want to exist as nonreplicated objects,
909       * then you should register them using this method.
910       *
911       * @param key the key to register as static attribute key
912       */
913      public static void registerStaticAttributeKey(Object key)
914      {
915        String io = key.getClass().getName() + "." + key.toString();
916        if (writeAttributeKeys == null)
917          writeAttributeKeys = new Hashtable();
918        if (readAttributeKeys == null)
919          readAttributeKeys = new Hashtable();
920        writeAttributeKeys.put(key, io);
921        readAttributeKeys.put(io, key);
922      }
923    
924      /**
925       * Returns a string representation of this StyleContext.
926       *
927       * @return a string representation of this StyleContext
928       */
929      public String toString()
930      {
931        cleanupPool();
932        StringBuilder b = new StringBuilder();
933        Iterator i = attributeSetPool.keySet().iterator();
934        while (i.hasNext())
935          {
936            Object att = i.next();
937            b.append(att);
938            b.append('\n');
939          }
940        return b.toString();
941      }
942    
943      /**
944       * Searches the AttributeSet pool and returns a pooled instance if available,
945       * or pool a new one.
946       *
947       * @return an immutable attribute set that equals the current search key
948       */
949      private AttributeSet searchImmutableSet()
950      {
951        SmallAttributeSet k = createSmallAttributeSet(search);
952        WeakReference ref = (WeakReference) attributeSetPool.get(k);
953        SmallAttributeSet a;
954        if (ref == null || (a = (SmallAttributeSet) ref.get()) == null)
955          {
956            a = k;
957            attributeSetPool.put(a, new WeakReference(a));
958          }
959        return a;
960      }
961    
962      /**
963       * Cleans up the attribute set pool from entries that are no longer
964       * referenced.
965       */
966      private void cleanupPool()
967      {
968        // TODO: How else can we force cleaning up the WeakHashMap?
969        attributeSetPool.size();
970      }
971    
972      /**
973       * Returns a MutableAttributeSet that holds a. If a itself is mutable,
974       * this returns a itself, otherwise it creates a new SimpleAtttributeSet
975       * via {@link #createLargeAttributeSet(AttributeSet)}.
976       *
977       * @param a the AttributeSet to create a mutable set for
978       *
979       * @return a mutable attribute set that corresponds to a
980       */
981      private MutableAttributeSet getMutableAttributeSet(AttributeSet a)
982      {
983        MutableAttributeSet mas;
984        if (a instanceof MutableAttributeSet)
985          mas = (MutableAttributeSet) a;
986        else
987          mas = createLargeAttributeSet(a);
988        return mas;
989      }
990    }