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