Source for javax.swing.text.StyleContext

   1: /* StyleContext.java --
   2:    Copyright (C) 2004 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.text;
  40: 
  41: import java.awt.Color;
  42: import java.awt.Font;
  43: import java.awt.FontMetrics;
  44: import java.awt.Toolkit;
  45: import java.io.IOException;
  46: import java.io.NotSerializableException;
  47: import java.io.ObjectInputStream;
  48: import java.io.ObjectOutputStream;
  49: import java.io.Serializable;
  50: import java.lang.ref.WeakReference;
  51: import java.util.Collections;
  52: import java.util.Enumeration;
  53: import java.util.EventListener;
  54: import java.util.Hashtable;
  55: import java.util.Iterator;
  56: import java.util.Map;
  57: import java.util.WeakHashMap;
  58: 
  59: import javax.swing.event.ChangeEvent;
  60: import javax.swing.event.ChangeListener;
  61: import javax.swing.event.EventListenerList;
  62: 
  63: public class StyleContext 
  64:   implements Serializable, AbstractDocument.AttributeContext
  65: {
  66:   /** The serialization UID (compatible with JDK1.5). */
  67:   private static final long serialVersionUID = 8042858831190784241L;
  68: 
  69:   public class NamedStyle
  70:     implements Serializable, Style
  71:   {
  72:     /** The serialization UID (compatible with JDK1.5). */
  73:     private static final long serialVersionUID = -6690628971806226374L;
  74: 
  75:     protected transient ChangeEvent changeEvent;
  76:     protected EventListenerList listenerList;
  77:       
  78:     private transient AttributeSet attributes;
  79: 
  80:     public NamedStyle()
  81:     {
  82:       this(null, null);
  83:     }
  84: 
  85:     public NamedStyle(Style parent)
  86:     {
  87:       this(null, parent);
  88:     }
  89: 
  90:     public NamedStyle(String name, Style parent)
  91:     {
  92:       attributes = getEmptySet();
  93:       listenerList = new EventListenerList();
  94:       if (name != null)
  95:         setName(name);
  96:       if (parent != null)
  97:         setResolveParent(parent);
  98:     }
  99: 
 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: }