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