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.ObjectInputStream;
  47: import java.io.ObjectOutputStream;
  48: import java.io.Serializable;
  49: import java.util.Enumeration;
  50: import java.util.EventListener;
  51: import java.util.Hashtable;
  52: 
  53: import javax.swing.event.ChangeEvent;
  54: import javax.swing.event.ChangeListener;
  55: import javax.swing.event.EventListenerList;
  56: 
  57: public class StyleContext 
  58:     implements Serializable, AbstractDocument.AttributeContext
  59: {
  60:   /** The serialization UID (compatible with JDK1.5). */
  61:   private static final long serialVersionUID = 8042858831190784241L;
  62: 
  63:   public class NamedStyle
  64:     implements Serializable, Style
  65:   {
  66:     /** The serialization UID (compatible with JDK1.5). */
  67:     private static final long serialVersionUID = -6690628971806226374L;
  68: 
  69:     protected ChangeEvent changeEvent;
  70:     protected EventListenerList listenerList;
  71:       
  72:     AttributeSet attributes;
  73:     String name;
  74: 
  75:     public NamedStyle()
  76:     {
  77:       this(null, null);
  78:     }
  79: 
  80:     public NamedStyle(Style parent)
  81:     {
  82:       this(null, parent);
  83:     }
  84: 
  85:     public NamedStyle(String name, Style parent)
  86:     {
  87:       this.name = name;
  88:       this.attributes = getEmptySet();
  89:       this.changeEvent = new ChangeEvent(this);
  90:       this.listenerList = new EventListenerList();
  91:       setResolveParent(parent);
  92:     }
  93: 
  94:     public String getName()
  95:     {
  96:       return name;
  97:     }
  98: 
  99:     public void setName(String n)
 100:     {
 101:       name = n;
 102:       fireStateChanged();
 103:     }
 104: 
 105:     public void addChangeListener(ChangeListener l)
 106:     {
 107:       listenerList.add(ChangeListener.class, l);
 108:     }
 109:       
 110:     public void removeChangeListener(ChangeListener l)
 111:     {
 112:       listenerList.remove(ChangeListener.class, l);
 113:     }
 114:       
 115:     public EventListener[] getListeners(Class listenerType)
 116:     {
 117:       return listenerList.getListeners(listenerType);
 118:     }
 119: 
 120:     public ChangeListener[] getChangeListeners()
 121:     {
 122:       return (ChangeListener[]) getListeners(ChangeListener.class);
 123:     }
 124: 
 125:     protected  void fireStateChanged()
 126:     {
 127:       ChangeListener[] listeners = getChangeListeners();
 128:       for (int i = 0; i < listeners.length; ++i)
 129:         {
 130:           listeners[i].stateChanged(changeEvent);
 131:         }
 132:     }
 133: 
 134:     public void addAttribute(Object name, Object value)
 135:     {
 136:       attributes = StyleContext.this.addAttribute(attributes, name, value);
 137:       fireStateChanged();
 138:     }
 139: 
 140:     public void addAttributes(AttributeSet attr)
 141:     {
 142:       attributes = StyleContext.this.addAttributes(attributes, attr);
 143:       fireStateChanged();
 144:     }
 145: 
 146:     public boolean containsAttribute(Object name, Object value)
 147:     {
 148:       return attributes.containsAttribute(name, value);
 149:     }
 150:       
 151:     public boolean containsAttributes(AttributeSet attrs)
 152:     {
 153:       return attributes.containsAttributes(attrs);
 154:     }
 155: 
 156:     public AttributeSet copyAttributes()
 157:     {
 158:       return attributes.copyAttributes();
 159:     }
 160:             
 161:     public Object getAttribute(Object attrName)
 162:     {
 163:       return attributes.getAttribute(attrName);
 164:     }
 165: 
 166:     public int getAttributeCount()
 167:     {
 168:       return attributes.getAttributeCount();
 169:     }
 170: 
 171:     public Enumeration getAttributeNames()
 172:     {
 173:       return attributes.getAttributeNames();
 174:     }
 175:       
 176:     public boolean isDefined(Object attrName)
 177:     {
 178:       return attributes.isDefined(attrName);        
 179:     }
 180: 
 181:     public boolean isEqual(AttributeSet attr)
 182:     {
 183:       return attributes.isEqual(attr);
 184:     }
 185: 
 186:     public void removeAttribute(Object name)
 187:     {
 188:       attributes = StyleContext.this.removeAttribute(attributes, name);
 189:       fireStateChanged();
 190:     }
 191: 
 192:     public void removeAttributes(AttributeSet attrs)
 193:     {
 194:       attributes = StyleContext.this.removeAttributes(attributes, attrs);
 195:       fireStateChanged();
 196:     }
 197: 
 198:     public void removeAttributes(Enumeration names)
 199:     {
 200:       attributes = StyleContext.this.removeAttributes(attributes, names);
 201:       fireStateChanged();
 202:     }
 203: 
 204: 
 205:     public AttributeSet getResolveParent()
 206:     {
 207:       return attributes.getResolveParent();        
 208:     }
 209: 
 210:     public void setResolveParent(AttributeSet parent)
 211:     {
 212:       if (parent != null)
 213:         {
 214:           attributes = StyleContext.this.addAttribute
 215:             (attributes, ResolveAttribute, parent);
 216:         }
 217:       fireStateChanged();
 218:     }
 219:       
 220:     public String toString()
 221:     {
 222:       return ("[NamedStyle: name=" + name + ", attrs=" + attributes.toString() + "]");
 223:     }      
 224:   }
 225:   
 226:   public class SmallAttributeSet
 227:     implements AttributeSet
 228:   {
 229:     final Object [] attrs;
 230:     public SmallAttributeSet(AttributeSet a)
 231:     {
 232:       if (a == null)
 233:         attrs = new Object[0];
 234:       else
 235:         {
 236:           int n = a.getAttributeCount();
 237:           int i = 0;
 238:           attrs = new Object[n * 2];
 239:           Enumeration e = a.getAttributeNames();
 240:           while (e.hasMoreElements())
 241:             {
 242:               Object name = e.nextElement();
 243:               attrs[i++] = name;
 244:               attrs[i++] = a.getAttribute(name);
 245:             }
 246:         }
 247:     }
 248: 
 249:     public SmallAttributeSet(Object [] a)
 250:     {
 251:       if (a == null)
 252:         attrs = new Object[0];
 253:       else
 254:         {
 255:           attrs = new Object[a.length];
 256:           System.arraycopy(a, 0, attrs, 0, a.length);
 257:         }
 258:     }
 259: 
 260:     public Object clone()
 261:     {
 262:       return new SmallAttributeSet(this.attrs);
 263:     }
 264: 
 265:     public boolean containsAttribute(Object name, Object value)
 266:     {
 267:       for (int i = 0; i < attrs.length; i += 2)
 268:         {
 269:           if (attrs[i].equals(name) &&
 270:               attrs[i+1].equals(value))
 271:             return true;
 272:         }
 273:       return false;
 274:     }
 275: 
 276:     public boolean containsAttributes(AttributeSet a)
 277:     {
 278:       Enumeration e = a.getAttributeNames();
 279:       while (e.hasMoreElements())
 280:         {
 281:           Object name = e.nextElement();
 282:           Object val = a.getAttribute(name);
 283:           if (!containsAttribute(name, val))
 284:             return false;
 285:         }
 286:       return true;            
 287:     }
 288: 
 289:     public AttributeSet copyAttributes()
 290:     {
 291:       return (AttributeSet) clone();
 292:     }
 293: 
 294:     public boolean equals(Object obj)
 295:     {
 296:       return 
 297:         (obj instanceof AttributeSet)
 298:         && this.isEqual((AttributeSet)obj);
 299:     }
 300:  
 301:     public Object getAttribute(Object key)
 302:     {
 303:       for (int i = 0; i < attrs.length; i += 2)
 304:         {
 305:           if (attrs[i].equals(key))
 306:             return attrs[i+1];
 307:         }
 308:             
 309:       // Check the resolve parent, unless we're looking for the 
 310:       // ResolveAttribute, which would cause an infinite loop
 311:       if (!(key.equals(ResolveAttribute)))
 312:           {
 313:             Object p = getResolveParent();
 314:             if (p != null && p instanceof AttributeSet)
 315:               return (((AttributeSet)p).getAttribute(key));
 316:           }
 317:       
 318:       return null;
 319:     }
 320: 
 321:     public int getAttributeCount()
 322:     {
 323:       return attrs.length / 2;
 324:     }
 325: 
 326:     public Enumeration getAttributeNames()
 327:     {      
 328:       return new Enumeration() 
 329:         {
 330:           int i = 0;
 331:           public boolean hasMoreElements() 
 332:           { 
 333:             return i < attrs.length; 
 334:           }
 335:           public Object nextElement() 
 336:           { 
 337:             i += 2; 
 338:             return attrs[i-2]; 
 339:           }
 340:         };
 341:     }
 342: 
 343:     public AttributeSet getResolveParent()
 344:     {
 345:       return (AttributeSet) getAttribute(ResolveAttribute);
 346:     }
 347: 
 348:     public int hashCode()
 349:     {
 350:       return java.util.Arrays.asList(attrs).hashCode();
 351:     }
 352: 
 353:     public boolean isDefined(Object key)
 354:     {
 355:       for (int i = 0; i < attrs.length; i += 2)
 356:         {
 357:           if (attrs[i].equals(key))
 358:             return true;
 359:         }
 360:       return false;
 361:     }
 362:     
 363:     public boolean isEqual(AttributeSet attr)
 364:     {
 365:       return getAttributeCount() == attr.getAttributeCount()
 366:              && this.containsAttributes(attr);
 367:     }
 368:     
 369:     public String toString()
 370:     {
 371:       StringBuffer sb = new StringBuffer();
 372:       sb.append("[StyleContext.SmallattributeSet:");
 373:       for (int i = 0; i < attrs.length - 1; ++i)
 374:         {
 375:           sb.append(" (");
 376:           sb.append(attrs[i].toString());
 377:           sb.append("=");
 378:           sb.append(attrs[i+1].toString());
 379:           sb.append(")");
 380:         }
 381:       sb.append("]");
 382:       return sb.toString();
 383:     }
 384:   }
 385: 
 386:   // FIXME: official javadocs suggest that these might be more usefully
 387:   // implemented using a WeakHashMap, but not sure if that works most
 388:   // places or whether it really matters anyways.
 389:   //
 390:   // FIXME: also not sure if these tables ought to be static (singletons),
 391:   // shared across all StyleContexts. I think so, but it's not clear in
 392:   // docs. revert to non-shared if you think it matters.
 393:   
 394:   /**
 395:    * The name of the default style.
 396:    */
 397:   public static final String DEFAULT_STYLE = "default";
 398:   
 399:   /**
 400:    * The default style for this style context.
 401:    */
 402:   NamedStyle defaultStyle = new NamedStyle(DEFAULT_STYLE, null);
 403:   
 404:   static Hashtable sharedAttributeSets = new Hashtable();
 405:   static Hashtable sharedFonts = new Hashtable();
 406: 
 407:   static StyleContext defaultStyleContext = new StyleContext();
 408:   static final int compressionThreshold = 9;
 409: 
 410:   /**
 411:    * These attribute keys are handled specially in serialization.
 412:    */
 413:   private static Hashtable staticAttributeKeys = new Hashtable();
 414: 
 415:   EventListenerList listenerList;
 416:   Hashtable styleTable;
 417:   
 418:   /**
 419:    * Creates a new instance of the style context. Add the default style
 420:    * to the style table.
 421:    */
 422:   public StyleContext()
 423:   {
 424:     listenerList = new EventListenerList();
 425:     styleTable = new Hashtable();
 426:     styleTable.put(DEFAULT_STYLE, defaultStyle);
 427:   }
 428: 
 429:   protected SmallAttributeSet createSmallAttributeSet(AttributeSet a)
 430:   {
 431:     return new SmallAttributeSet(a);
 432:   }
 433:   
 434:   protected MutableAttributeSet createLargeAttributeSet(AttributeSet a)
 435:   {
 436:     return new SimpleAttributeSet(a);
 437:   }
 438: 
 439:   public void addChangeListener(ChangeListener listener)
 440:   {
 441:     listenerList.add(ChangeListener.class, listener);
 442:   }
 443: 
 444:   public void removeChangeListener(ChangeListener listener)
 445:   {
 446:     listenerList.remove(ChangeListener.class, listener);
 447:   }
 448: 
 449:   public ChangeListener[] getChangeListeners()
 450:   {
 451:     return (ChangeListener[]) listenerList.getListeners(ChangeListener.class);
 452:   }
 453:     
 454:   public Style addStyle(String name, Style parent)
 455:   {
 456:     Style newStyle = new NamedStyle(name, parent);
 457:     if (name != null)
 458:       styleTable.put(name, newStyle);
 459:     return newStyle;
 460:   }
 461: 
 462:   public void removeStyle(String name)
 463:   {
 464:     styleTable.remove(name);
 465:   }
 466: 
 467:   /**
 468:    * Get the style from the style table. If the passed name
 469:    * matches {@link #DEFAULT_STYLE}, returns the default style.
 470:    * Otherwise returns the previously defined style of
 471:    * <code>null</code> if the style with the given name is not defined.
 472:    *
 473:    * @param name the name of the style.
 474:    *
 475:    * @return the style with the given name or null if no such defined.
 476:    */
 477:   public Style getStyle(String name)
 478:   {
 479:     return (Style) styleTable.get(name);
 480:   }
 481:   
 482:   /**
 483:    * Get the names of the style. The returned enumeration always
 484:    * contains at least one member, the default style.
 485:    */
 486:   public Enumeration getStyleNames()
 487:   {
 488:     return styleTable.keys();
 489:   }
 490: 
 491:   //
 492:   // StyleContexts only understand the "simple" model of fonts present in
 493:   // pre-java2d systems: fonts are a family name, a size (integral number
 494:   // of points), and a mask of style parameters (plain, bold, italic, or
 495:   // bold|italic). We have an inner class here called SimpleFontSpec which
 496:   // holds such triples.
 497:   //
 498:   // A SimpleFontSpec can be built for *any* AttributeSet because the size,
 499:   // family, and style keys in an AttributeSet have default values (defined
 500:   // over in StyleConstants).
 501:   //
 502:   // We keep a static cache mapping SimpleFontSpecs to java.awt.Fonts, so
 503:   // that we reuse Fonts between styles and style contexts.
 504:   // 
 505: 
 506:   private static class SimpleFontSpec
 507:   {
 508:     String family;
 509:     int style;
 510:     int size;
 511:     public SimpleFontSpec(String family,
 512:                           int style,
 513:                           int size)
 514:     {
 515:       this.family = family;
 516:       this.style = style;
 517:       this.size = size;
 518:     }
 519:     public boolean equals(Object obj)
 520:     {
 521:       return (obj != null)
 522:         && (obj instanceof SimpleFontSpec)
 523:         && (((SimpleFontSpec)obj).family.equals(this.family))
 524:         && (((SimpleFontSpec)obj).style == this.style)
 525:         && (((SimpleFontSpec)obj).size == this.size);
 526:     }
 527:     public int hashCode()
 528:     {
 529:       return family.hashCode() + style + size;
 530:     }
 531:   }
 532:   
 533:   public Font getFont(AttributeSet attr)
 534:   {
 535:     String family = StyleConstants.getFontFamily(attr);
 536:     int style = Font.PLAIN;
 537:     if (StyleConstants.isBold(attr))
 538:       style += Font.BOLD;
 539:     if (StyleConstants.isItalic(attr))
 540:       style += Font.ITALIC;      
 541:     int size = StyleConstants.getFontSize(attr);
 542:     return getFont(family, style, size);
 543:   }
 544: 
 545:   public Font getFont(String family, int style, int size)
 546:   {
 547:     SimpleFontSpec spec = new SimpleFontSpec(family, style, size);
 548:     if (sharedFonts.containsKey(spec))
 549:       return (Font) sharedFonts.get(spec);
 550:     else
 551:       {
 552:         Font tmp = new Font(family, style, size);
 553:         sharedFonts.put(spec, tmp);
 554:         return tmp;
 555:       }
 556:   }
 557:   
 558:   public FontMetrics getFontMetrics(Font f)
 559:   {
 560:     return Toolkit.getDefaultToolkit().getFontMetrics(f);
 561:   }
 562: 
 563:   public Color getForeground(AttributeSet a)
 564:   {
 565:     return StyleConstants.getForeground(a);
 566:   }
 567: 
 568:   public Color getBackground(AttributeSet a)
 569:   {
 570:     return StyleConstants.getBackground(a);
 571:   }
 572: 
 573:   protected int getCompressionThreshold() 
 574:   {
 575:     return compressionThreshold;
 576:   }
 577: 
 578:   public static StyleContext getDefaultStyleContext()
 579:   {
 580:     return defaultStyleContext;
 581:   }
 582: 
 583:   public AttributeSet addAttribute(AttributeSet old, Object name, Object value)
 584:   {
 585:     if (old instanceof MutableAttributeSet)
 586:       {
 587:         ((MutableAttributeSet)old).addAttribute(name, value);
 588:         return old;
 589:       }
 590:     else 
 591:       {
 592:         MutableAttributeSet mutable = createLargeAttributeSet(old);
 593:         mutable.addAttribute(name, value);
 594:         if (mutable.getAttributeCount() >= getCompressionThreshold())
 595:           return mutable;
 596:         else
 597:           {
 598:             SmallAttributeSet small = createSmallAttributeSet(mutable);
 599:             if (sharedAttributeSets.containsKey(small))
 600:               small = (SmallAttributeSet) sharedAttributeSets.get(small);
 601:             else
 602:               sharedAttributeSets.put(small,small);
 603:             return small;
 604:           }
 605:       }
 606:   }
 607: 
 608:   public AttributeSet addAttributes(AttributeSet old, AttributeSet attributes)
 609:   {
 610:     if (old instanceof MutableAttributeSet)
 611:       {
 612:         ((MutableAttributeSet)old).addAttributes(attributes);
 613:         return old;
 614:       }
 615:     else 
 616:       {
 617:         MutableAttributeSet mutable = createLargeAttributeSet(old);
 618:         mutable.addAttributes(attributes);
 619:         if (mutable.getAttributeCount() >= getCompressionThreshold())
 620:           return mutable;
 621:         else
 622:           {
 623:             SmallAttributeSet small = createSmallAttributeSet(mutable);
 624:             if (sharedAttributeSets.containsKey(small))
 625:               small = (SmallAttributeSet) sharedAttributeSets.get(small);
 626:             else
 627:               sharedAttributeSets.put(small,small);
 628:             return small;
 629:           }
 630:       }
 631:   }
 632: 
 633:   public AttributeSet getEmptySet()
 634:   {
 635:     AttributeSet e = createSmallAttributeSet(null);
 636:     if (sharedAttributeSets.containsKey(e))
 637:       e = (AttributeSet) sharedAttributeSets.get(e);
 638:     else
 639:       sharedAttributeSets.put(e, e);
 640:     return e;
 641:   }
 642: 
 643:   public void reclaim(AttributeSet attributes)
 644:   {
 645:     if (sharedAttributeSets.containsKey(attributes))
 646:       sharedAttributeSets.remove(attributes);
 647:   }
 648: 
 649:   public AttributeSet removeAttribute(AttributeSet old, Object name)
 650:   {
 651:     if (old instanceof MutableAttributeSet)
 652:       {
 653:         ((MutableAttributeSet)old).removeAttribute(name);
 654:         if (old.getAttributeCount() < getCompressionThreshold())
 655:           {
 656:             SmallAttributeSet small = createSmallAttributeSet(old);
 657:             if (!sharedAttributeSets.containsKey(small))
 658:               sharedAttributeSets.put(small,small);
 659:             old = (AttributeSet) sharedAttributeSets.get(small);
 660:           }
 661:         return old;
 662:       }
 663:     else 
 664:       {          
 665:         MutableAttributeSet mutable = createLargeAttributeSet(old);
 666:         mutable.removeAttribute(name);
 667:         SmallAttributeSet small = createSmallAttributeSet(mutable);
 668:         if (sharedAttributeSets.containsKey(small))
 669:           small = (SmallAttributeSet) sharedAttributeSets.get(small);
 670:         else
 671:           sharedAttributeSets.put(small,small);
 672:         return small;
 673:       }
 674:   }
 675: 
 676:   public AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes)
 677:   {
 678:     return removeAttributes(old, attributes.getAttributeNames());
 679:   }
 680: 
 681:   public AttributeSet removeAttributes(AttributeSet old, Enumeration names)
 682:   {
 683:     if (old instanceof MutableAttributeSet)
 684:       {
 685:         ((MutableAttributeSet)old).removeAttributes(names);
 686:         if (old.getAttributeCount() < getCompressionThreshold())
 687:           {
 688:             SmallAttributeSet small = createSmallAttributeSet(old);
 689:             if (!sharedAttributeSets.containsKey(small))
 690:               sharedAttributeSets.put(small,small);
 691:             old = (AttributeSet) sharedAttributeSets.get(small);
 692:           }
 693:         return old;
 694:       }
 695:     else 
 696:       {          
 697:         MutableAttributeSet mutable = createLargeAttributeSet(old);
 698:         mutable.removeAttributes(names);
 699:         SmallAttributeSet small = createSmallAttributeSet(mutable);
 700:         if (sharedAttributeSets.containsKey(small))
 701:           small = (SmallAttributeSet) sharedAttributeSets.get(small);
 702:         else
 703:           sharedAttributeSets.put(small,small);
 704:         return small;
 705:       }    
 706:   }
 707: 
 708:   /**
 709:    * Gets the object previously registered with registerStaticAttributeKey.
 710:    * 
 711:    * @param key - the key that was registered.
 712:    * @return the object previously registered with registerStaticAttributeKey.
 713:    */
 714:   public static Object getStaticAttribute(Object key)
 715:   {
 716:     if (key == null)
 717:       return null;
 718:     return staticAttributeKeys.get(key);
 719:   }
 720:   
 721:   /**
 722:    * Returns the String that key will be registered with
 723:    * registerStaticAttributeKey.
 724:    * 
 725:    * @param key - the key that will be registered.
 726:    * @return the string the key will be registered with.
 727:    */
 728:   public static Object getStaticAttributeKey(Object key)
 729:   {
 730:     return key.getClass().getName() + "." + key.toString();
 731:   }
 732: 
 733:   /**
 734:    * Reads a set of attributes from the given object input stream. This will
 735:    * attempt to restore keys that were static objects by considering only the
 736:    * keys that have were registered with registerStaticAttributeKey. The
 737:    * attributes retrieved will be placed into the given set.
 738:    * 
 739:    * @param in - the stream to read from
 740:    * @param a - the set of attributes
 741:    * @throws ClassNotFoundException - may be encountered when reading from
 742:    *           stream
 743:    * @throws IOException - any I/O error
 744:    */
 745:   public static void readAttributeSet(ObjectInputStream in, MutableAttributeSet a)
 746:     throws ClassNotFoundException, IOException
 747:   {
 748:     if (in == null || a == null)
 749:       return;
 750:     
 751:     Object key = in.readObject();
 752:     Object val = in.readObject();
 753:     while (key != null && val != null)
 754:       {
 755:         Object staticKey = staticAttributeKeys.get(key);
 756:         Object staticVal = staticAttributeKeys.get(val);
 757:         
 758:         if (staticKey != null)
 759:           key = staticKey;
 760:         if (staticVal != null)
 761:           val = staticVal;
 762:         
 763:         a.addAttribute(key, val);
 764:         key = in.readObject();
 765:         val = in.readObject();
 766:       }
 767:   }
 768:   
 769:   /**
 770:    * Serialize an attribute set in a way that is compatible with it
 771:    * being read in again by {@link #readAttributeSet(ObjectInputStream, MutableAttributeSet)}.
 772:    * In particular registered static keys are transformed properly.
 773:    * 
 774:    * @param out - stream to write to
 775:    * @param a - the attribute set
 776:    * @throws IOException - any I/O error
 777:    */
 778:   public static void writeAttributeSet(ObjectOutputStream out, AttributeSet a)
 779:     throws IOException
 780:   {
 781:     Enumeration e = a.getAttributeNames();
 782:     while (e.hasMoreElements())
 783:       {
 784:         Object oldKey = e.nextElement();
 785:         Object newKey = getStaticAttribute(oldKey);
 786:         Object key = (newKey == null) ? oldKey : newKey;
 787:  
 788:         out.writeObject(key);
 789:         out.writeObject(a.getAttribute(oldKey));
 790:       }
 791:     out.writeObject(null);
 792:     out.writeObject(null);
 793:   }
 794: 
 795:   /**
 796:    * Handles reading in the attributes. 
 797:    * @see #readAttributeSet(ObjectInputStream, MutableAttributeSet)
 798:    * 
 799:    * @param in - the stream to read from
 800:    * @param a - the set of attributes
 801:    * @throws ClassNotFoundException - may be encountered when reading from stream
 802:    * @throws IOException - any I/O error
 803:    */
 804:   public void readAttributes(ObjectInputStream in, MutableAttributeSet a)
 805:     throws ClassNotFoundException, IOException
 806:   {
 807:     readAttributeSet(in, a);
 808:   }
 809: 
 810:   /**
 811:    * Handles writing of the given attributes.
 812:    * @see #writeAttributeSet(ObjectOutputStream, AttributeSet)
 813:    * 
 814:    * @param out - stream to write to
 815:    * @param a - the attribute set
 816:    * @throws IOException - any I/O error
 817:    */
 818:   public void writeAttributes(ObjectOutputStream out, AttributeSet a)
 819:     throws IOException
 820:   {
 821:     writeAttributeSet(out, a);
 822:   }
 823: 
 824:   /**
 825:    * Registers an attribute key as a well-known keys. When an attribute with
 826:    * such a key is written to a stream, a special syntax is used so that it
 827:    * can be recognized when it is read back in. All attribute keys defined
 828:    * in <code>StyleContext</code> are registered as static keys. If you define
 829:    * additional attribute keys that you want to exist as nonreplicated objects,
 830:    * then you should register them using this method.
 831:    *
 832:    * @param key the key to register as static attribute key
 833:    */
 834:   public static void registerStaticAttributeKey(Object key)
 835:   {
 836:     if (key != null)
 837:       staticAttributeKeys.put(key.getClass().getName() + "." + key.toString(),
 838:                               key);
 839:   }
 840: }