001/* StyleContext.java --
002   Copyright (C) 2004 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.text;
040
041import java.awt.Color;
042import java.awt.Font;
043import java.awt.FontMetrics;
044import java.awt.Toolkit;
045import java.io.IOException;
046import java.io.NotSerializableException;
047import java.io.ObjectInputStream;
048import java.io.ObjectOutputStream;
049import java.io.Serializable;
050import java.lang.ref.WeakReference;
051import java.util.Collections;
052import java.util.Enumeration;
053import java.util.EventListener;
054import java.util.Hashtable;
055import java.util.Iterator;
056import java.util.Map;
057import java.util.WeakHashMap;
058
059import javax.swing.event.ChangeEvent;
060import javax.swing.event.ChangeListener;
061import javax.swing.event.EventListenerList;
062
063public 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}