001 /* StyleContext.java --
002 Copyright (C) 2004 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.text;
040
041 import java.awt.Color;
042 import java.awt.Font;
043 import java.awt.FontMetrics;
044 import java.awt.Toolkit;
045 import java.io.IOException;
046 import java.io.NotSerializableException;
047 import java.io.ObjectInputStream;
048 import java.io.ObjectOutputStream;
049 import java.io.Serializable;
050 import java.lang.ref.WeakReference;
051 import java.util.Collections;
052 import java.util.Enumeration;
053 import java.util.EventListener;
054 import java.util.Hashtable;
055 import java.util.Iterator;
056 import java.util.Map;
057 import java.util.WeakHashMap;
058
059 import javax.swing.event.ChangeEvent;
060 import javax.swing.event.ChangeListener;
061 import javax.swing.event.EventListenerList;
062
063 public class StyleContext
064 implements Serializable, AbstractDocument.AttributeContext
065 {
066 /** The serialization UID (compatible with JDK1.5). */
067 private static final long serialVersionUID = 8042858831190784241L;
068
069 public class NamedStyle
070 implements Serializable, Style
071 {
072 /** The serialization UID (compatible with JDK1.5). */
073 private static final long serialVersionUID = -6690628971806226374L;
074
075 protected transient ChangeEvent changeEvent;
076 protected EventListenerList listenerList;
077
078 private transient AttributeSet attributes;
079
080 public NamedStyle()
081 {
082 this(null, null);
083 }
084
085 public NamedStyle(Style parent)
086 {
087 this(null, parent);
088 }
089
090 public NamedStyle(String name, Style parent)
091 {
092 attributes = getEmptySet();
093 listenerList = new EventListenerList();
094 if (name != null)
095 setName(name);
096 if (parent != null)
097 setResolveParent(parent);
098 }
099
100 public String getName()
101 {
102 String name = null;
103 if (isDefined(StyleConstants.NameAttribute))
104 name = getAttribute(StyleConstants.NameAttribute).toString();
105 return name;
106 }
107
108 public void setName(String n)
109 {
110 if (n != null)
111 addAttribute(StyleConstants.NameAttribute, n);
112 }
113
114 public void addChangeListener(ChangeListener l)
115 {
116 listenerList.add(ChangeListener.class, l);
117 }
118
119 public void removeChangeListener(ChangeListener l)
120 {
121 listenerList.remove(ChangeListener.class, l);
122 }
123
124 public <T extends EventListener> T[] getListeners(Class<T> listenerType)
125 {
126 return listenerList.getListeners(listenerType);
127 }
128
129 public ChangeListener[] getChangeListeners()
130 {
131 return (ChangeListener[]) getListeners(ChangeListener.class);
132 }
133
134 protected void fireStateChanged()
135 {
136 ChangeListener[] listeners = getChangeListeners();
137 for (int i = 0; i < listeners.length; ++i)
138 {
139 // Lazily create event.
140 if (changeEvent == null)
141 changeEvent = new ChangeEvent(this);
142 listeners[i].stateChanged(changeEvent);
143 }
144 }
145
146 public void addAttribute(Object name, Object value)
147 {
148 attributes = StyleContext.this.addAttribute(attributes, name, value);
149 fireStateChanged();
150 }
151
152 public void addAttributes(AttributeSet attr)
153 {
154 attributes = StyleContext.this.addAttributes(attributes, attr);
155 fireStateChanged();
156 }
157
158 public boolean containsAttribute(Object name, Object value)
159 {
160 return attributes.containsAttribute(name, value);
161 }
162
163 public boolean containsAttributes(AttributeSet attrs)
164 {
165 return attributes.containsAttributes(attrs);
166 }
167
168 public AttributeSet copyAttributes()
169 {
170 // The RI returns a NamedStyle as copy, so do we.
171 NamedStyle copy = new NamedStyle();
172 copy.attributes = attributes.copyAttributes();
173 return copy;
174 }
175
176 public Object getAttribute(Object attrName)
177 {
178 return attributes.getAttribute(attrName);
179 }
180
181 public int getAttributeCount()
182 {
183 return attributes.getAttributeCount();
184 }
185
186 public Enumeration<?> getAttributeNames()
187 {
188 return attributes.getAttributeNames();
189 }
190
191 public boolean isDefined(Object attrName)
192 {
193 return attributes.isDefined(attrName);
194 }
195
196 public boolean isEqual(AttributeSet attr)
197 {
198 return attributes.isEqual(attr);
199 }
200
201 public void removeAttribute(Object name)
202 {
203 attributes = StyleContext.this.removeAttribute(attributes, name);
204 fireStateChanged();
205 }
206
207 public void removeAttributes(AttributeSet attrs)
208 {
209 attributes = StyleContext.this.removeAttributes(attributes, attrs);
210 fireStateChanged();
211 }
212
213 public void removeAttributes(Enumeration<?> names)
214 {
215 attributes = StyleContext.this.removeAttributes(attributes, names);
216 fireStateChanged();
217 }
218
219
220 public AttributeSet getResolveParent()
221 {
222 return attributes.getResolveParent();
223 }
224
225 public void setResolveParent(AttributeSet parent)
226 {
227 if (parent != null)
228 addAttribute(StyleConstants.ResolveAttribute, parent);
229 else
230 removeAttribute(StyleConstants.ResolveAttribute);
231 }
232
233 public String toString()
234 {
235 return "NamedStyle:" + getName() + " " + attributes;
236 }
237
238 private void writeObject(ObjectOutputStream s)
239 throws IOException
240 {
241 s.defaultWriteObject();
242 writeAttributeSet(s, attributes);
243 }
244
245 private void readObject(ObjectInputStream s)
246 throws ClassNotFoundException, IOException
247 {
248 s.defaultReadObject();
249 attributes = SimpleAttributeSet.EMPTY;
250 readAttributeSet(s, this);
251 }
252 }
253
254 public class SmallAttributeSet
255 implements AttributeSet
256 {
257 final Object [] attrs;
258 private AttributeSet resolveParent;
259 public SmallAttributeSet(AttributeSet a)
260 {
261 int n = a.getAttributeCount();
262 int i = 0;
263 attrs = new Object[n * 2];
264 Enumeration e = a.getAttributeNames();
265 while (e.hasMoreElements())
266 {
267 Object name = e.nextElement();
268 Object value = a.getAttribute(name);
269 if (name == ResolveAttribute)
270 resolveParent = (AttributeSet) value;
271 attrs[i++] = name;
272 attrs[i++] = value;
273 }
274 }
275
276 public SmallAttributeSet(Object [] a)
277 {
278 attrs = a;
279 for (int i = 0; i < attrs.length; i += 2)
280 {
281 if (attrs[i] == ResolveAttribute)
282 resolveParent = (AttributeSet) attrs[i + 1];
283 }
284 }
285
286 public Object clone()
287 {
288 return this;
289 }
290
291 public boolean containsAttribute(Object name, Object value)
292 {
293 return value.equals(getAttribute(name));
294 }
295
296 public boolean containsAttributes(AttributeSet a)
297 {
298 boolean res = true;
299 Enumeration e = a.getAttributeNames();
300 while (e.hasMoreElements() && res)
301 {
302 Object name = e.nextElement();
303 res = a.getAttribute(name).equals(getAttribute(name));
304 }
305 return res;
306 }
307
308 public AttributeSet copyAttributes()
309 {
310 return this;
311 }
312
313 public boolean equals(Object obj)
314 {
315 boolean eq = false;
316 if (obj instanceof AttributeSet)
317 {
318 AttributeSet atts = (AttributeSet) obj;
319 eq = getAttributeCount() == atts.getAttributeCount()
320 && containsAttributes(atts);
321 }
322 return eq;
323 }
324
325 public Object getAttribute(Object key)
326 {
327 Object att = null;
328 if (key == StyleConstants.ResolveAttribute)
329 att = resolveParent;
330
331 for (int i = 0; i < attrs.length && att == null; i += 2)
332 {
333 if (attrs[i].equals(key))
334 att = attrs[i + 1];
335 }
336
337 // Check the resolve parent, unless we're looking for the
338 // ResolveAttribute, which must not be looked up
339 if (att == null)
340 {
341 AttributeSet parent = getResolveParent();
342 if (parent != null)
343 att = parent.getAttribute(key);
344 }
345
346 return att;
347 }
348
349 public int getAttributeCount()
350 {
351 return attrs.length / 2;
352 }
353
354 public Enumeration<?> getAttributeNames()
355 {
356 return new Enumeration()
357 {
358 int i = 0;
359 public boolean hasMoreElements()
360 {
361 return i < attrs.length;
362 }
363 public Object nextElement()
364 {
365 i += 2;
366 return attrs[i-2];
367 }
368 };
369 }
370
371 public AttributeSet getResolveParent()
372 {
373 return resolveParent;
374 }
375
376 public int hashCode()
377 {
378 return java.util.Arrays.asList(attrs).hashCode();
379 }
380
381 public boolean isDefined(Object key)
382 {
383 for (int i = 0; i < attrs.length; i += 2)
384 {
385 if (attrs[i].equals(key))
386 return true;
387 }
388 return false;
389 }
390
391 public boolean isEqual(AttributeSet attr)
392 {
393 boolean eq;
394 // If the other one is also a SmallAttributeSet, it is only considered
395 // equal if it's the same instance.
396 if (attr instanceof SmallAttributeSet)
397 eq = attr == this;
398 else
399 eq = getAttributeCount() == attr.getAttributeCount()
400 && this.containsAttributes(attr);
401 return eq;
402 }
403
404 public String toString()
405 {
406 StringBuilder sb = new StringBuilder();
407 sb.append('{');
408 for (int i = 0; i < attrs.length; i += 2)
409 {
410 if (attrs[i + 1] instanceof AttributeSet)
411 {
412 sb.append(attrs[i]);
413 sb.append("=AttributeSet,");
414 }
415 else
416 {
417 sb.append(attrs[i]);
418 sb.append('=');
419 sb.append(attrs[i + 1]);
420 sb.append(',');
421 }
422 }
423 sb.append("}");
424 return sb.toString();
425 }
426 }
427
428 /**
429 * Register StyleConstant keys as static attribute keys for serialization.
430 */
431 static
432 {
433 // Don't let problems while doing this prevent class loading.
434 try
435 {
436 for (Iterator i = StyleConstants.keys.iterator(); i.hasNext();)
437 registerStaticAttributeKey(i.next());
438 }
439 catch (Throwable t)
440 {
441 t.printStackTrace();
442 }
443 }
444
445 /**
446 * The name of the default style.
447 */
448 public static final String DEFAULT_STYLE = "default";
449
450 static Hashtable sharedAttributeSets = new Hashtable();
451 static Hashtable sharedFonts = new Hashtable();
452
453 static StyleContext defaultStyleContext;
454 static final int compressionThreshold = 9;
455
456 /**
457 * These attribute keys are handled specially in serialization.
458 */
459 private static Hashtable writeAttributeKeys;
460 private static Hashtable readAttributeKeys;
461
462 private NamedStyle styles;
463
464 /**
465 * Used for searching attributes in the pool.
466 */
467 private transient MutableAttributeSet search = new SimpleAttributeSet();
468
469 /**
470 * A pool of immutable AttributeSets.
471 */
472 private transient Map attributeSetPool =
473 Collections.synchronizedMap(new WeakHashMap());
474
475 /**
476 * Creates a new instance of the style context. Add the default style
477 * to the style table.
478 */
479 public StyleContext()
480 {
481 styles = new NamedStyle(null);
482 addStyle(DEFAULT_STYLE, null);
483 }
484
485 protected SmallAttributeSet createSmallAttributeSet(AttributeSet a)
486 {
487 return new SmallAttributeSet(a);
488 }
489
490 protected MutableAttributeSet createLargeAttributeSet(AttributeSet a)
491 {
492 return new SimpleAttributeSet(a);
493 }
494
495 public void addChangeListener(ChangeListener listener)
496 {
497 styles.addChangeListener(listener);
498 }
499
500 public void removeChangeListener(ChangeListener listener)
501 {
502 styles.removeChangeListener(listener);
503 }
504
505 public ChangeListener[] getChangeListeners()
506 {
507 return styles.getChangeListeners();
508 }
509
510 public Style addStyle(String name, Style parent)
511 {
512 Style newStyle = new NamedStyle(name, parent);
513 if (name != null)
514 styles.addAttribute(name, newStyle);
515 return newStyle;
516 }
517
518 public void removeStyle(String name)
519 {
520 styles.removeAttribute(name);
521 }
522
523 /**
524 * Get the style from the style table. If the passed name
525 * matches {@link #DEFAULT_STYLE}, returns the default style.
526 * Otherwise returns the previously defined style of
527 * <code>null</code> if the style with the given name is not defined.
528 *
529 * @param name the name of the style.
530 *
531 * @return the style with the given name or null if no such defined.
532 */
533 public Style getStyle(String name)
534 {
535 return (Style) styles.getAttribute(name);
536 }
537
538 /**
539 * Get the names of the style. The returned enumeration always
540 * contains at least one member, the default style.
541 */
542 public Enumeration<?> getStyleNames()
543 {
544 return styles.getAttributeNames();
545 }
546
547 private void readObject(ObjectInputStream in)
548 throws ClassNotFoundException, IOException
549 {
550 search = new SimpleAttributeSet();
551 attributeSetPool = Collections.synchronizedMap(new WeakHashMap());
552 in.defaultReadObject();
553 }
554
555 private void writeObject(ObjectOutputStream out)
556 throws IOException
557 {
558 cleanupPool();
559 out.defaultWriteObject();
560 }
561
562 //
563 // StyleContexts only understand the "simple" model of fonts present in
564 // pre-java2d systems: fonts are a family name, a size (integral number
565 // of points), and a mask of style parameters (plain, bold, italic, or
566 // bold|italic). We have an inner class here called SimpleFontSpec which
567 // holds such triples.
568 //
569 // A SimpleFontSpec can be built for *any* AttributeSet because the size,
570 // family, and style keys in an AttributeSet have default values (defined
571 // over in StyleConstants).
572 //
573 // We keep a static cache mapping SimpleFontSpecs to java.awt.Fonts, so
574 // that we reuse Fonts between styles and style contexts.
575 //
576
577 private static class SimpleFontSpec
578 {
579 String family;
580 int style;
581 int size;
582 public SimpleFontSpec(String family,
583 int style,
584 int size)
585 {
586 this.family = family;
587 this.style = style;
588 this.size = size;
589 }
590 public boolean equals(Object obj)
591 {
592 return (obj != null)
593 && (obj instanceof SimpleFontSpec)
594 && (((SimpleFontSpec)obj).family.equals(this.family))
595 && (((SimpleFontSpec)obj).style == this.style)
596 && (((SimpleFontSpec)obj).size == this.size);
597 }
598 public int hashCode()
599 {
600 return family.hashCode() + style + size;
601 }
602 }
603
604 public Font getFont(AttributeSet attr)
605 {
606 String family = StyleConstants.getFontFamily(attr);
607 int style = Font.PLAIN;
608 if (StyleConstants.isBold(attr))
609 style += Font.BOLD;
610 if (StyleConstants.isItalic(attr))
611 style += Font.ITALIC;
612 int size = StyleConstants.getFontSize(attr);
613 return getFont(family, style, size);
614 }
615
616 public Font getFont(String family, int style, int size)
617 {
618 SimpleFontSpec spec = new SimpleFontSpec(family, style, size);
619 if (sharedFonts.containsKey(spec))
620 return (Font) sharedFonts.get(spec);
621 else
622 {
623 Font tmp = new Font(family, style, size);
624 sharedFonts.put(spec, tmp);
625 return tmp;
626 }
627 }
628
629 public FontMetrics getFontMetrics(Font f)
630 {
631 return Toolkit.getDefaultToolkit().getFontMetrics(f);
632 }
633
634 public Color getForeground(AttributeSet a)
635 {
636 return StyleConstants.getForeground(a);
637 }
638
639 public Color getBackground(AttributeSet a)
640 {
641 return StyleConstants.getBackground(a);
642 }
643
644 protected int getCompressionThreshold()
645 {
646 return compressionThreshold;
647 }
648
649 public static StyleContext getDefaultStyleContext()
650 {
651 if (defaultStyleContext == null)
652 defaultStyleContext = new StyleContext();
653 return defaultStyleContext;
654 }
655
656 public synchronized AttributeSet addAttribute(AttributeSet old, Object name,
657 Object value)
658 {
659 AttributeSet ret;
660 if (old.getAttributeCount() + 1 < getCompressionThreshold())
661 {
662 search.removeAttributes(search);
663 search.addAttributes(old);
664 search.addAttribute(name, value);
665 reclaim(old);
666 ret = searchImmutableSet();
667 }
668 else
669 {
670 MutableAttributeSet mas = getMutableAttributeSet(old);
671 mas.addAttribute(name, value);
672 ret = mas;
673 }
674 return ret;
675 }
676
677 public synchronized AttributeSet addAttributes(AttributeSet old,
678 AttributeSet attributes)
679 {
680 AttributeSet ret;
681 if (old.getAttributeCount() + attributes.getAttributeCount()
682 < getCompressionThreshold())
683 {
684 search.removeAttributes(search);
685 search.addAttributes(old);
686 search.addAttributes(attributes);
687 reclaim(old);
688 ret = searchImmutableSet();
689 }
690 else
691 {
692 MutableAttributeSet mas = getMutableAttributeSet(old);
693 mas.addAttributes(attributes);
694 ret = mas;
695 }
696 return ret;
697 }
698
699 public AttributeSet getEmptySet()
700 {
701 return SimpleAttributeSet.EMPTY;
702 }
703
704 public void reclaim(AttributeSet attributes)
705 {
706 cleanupPool();
707 }
708
709 public synchronized AttributeSet removeAttribute(AttributeSet old,
710 Object name)
711 {
712 AttributeSet ret;
713 if (old.getAttributeCount() - 1 <= getCompressionThreshold())
714 {
715 search.removeAttributes(search);
716 search.addAttributes(old);
717 search.removeAttribute(name);
718 reclaim(old);
719 ret = searchImmutableSet();
720 }
721 else
722 {
723 MutableAttributeSet mas = getMutableAttributeSet(old);
724 mas.removeAttribute(name);
725 ret = mas;
726 }
727 return ret;
728 }
729
730 public synchronized AttributeSet removeAttributes(AttributeSet old,
731 AttributeSet attributes)
732 {
733 AttributeSet ret;
734 if (old.getAttributeCount() <= getCompressionThreshold())
735 {
736 search.removeAttributes(search);
737 search.addAttributes(old);
738 search.removeAttributes(attributes);
739 reclaim(old);
740 ret = searchImmutableSet();
741 }
742 else
743 {
744 MutableAttributeSet mas = getMutableAttributeSet(old);
745 mas.removeAttributes(attributes);
746 ret = mas;
747 }
748 return ret;
749 }
750
751 public synchronized AttributeSet removeAttributes(AttributeSet old,
752 Enumeration<?> names)
753 {
754 AttributeSet ret;
755 if (old.getAttributeCount() <= getCompressionThreshold())
756 {
757 search.removeAttributes(search);
758 search.addAttributes(old);
759 search.removeAttributes(names);
760 reclaim(old);
761 ret = searchImmutableSet();
762 }
763 else
764 {
765 MutableAttributeSet mas = getMutableAttributeSet(old);
766 mas.removeAttributes(names);
767 ret = mas;
768 }
769 return ret;
770 }
771
772 /**
773 * Gets the object previously registered with registerStaticAttributeKey.
774 *
775 * @param key - the key that was registered.
776 * @return the object previously registered with registerStaticAttributeKey.
777 */
778 public static Object getStaticAttribute(Object key)
779 {
780 if (key == null)
781 return null;
782 return readAttributeKeys.get(key);
783 }
784
785 /**
786 * Returns the String that key will be registered with
787 * registerStaticAttributeKey.
788 *
789 * @param key - the key that will be registered.
790 * @return the string the key will be registered with.
791 */
792 public static Object getStaticAttributeKey(Object key)
793 {
794 return key.getClass().getName() + "." + key.toString();
795 }
796
797 /**
798 * Reads a set of attributes from the given object input stream. This will
799 * attempt to restore keys that were static objects by considering only the
800 * keys that have were registered with registerStaticAttributeKey. The
801 * attributes retrieved will be placed into the given set.
802 *
803 * @param in - the stream to read from
804 * @param a - the set of attributes
805 * @throws ClassNotFoundException - may be encountered when reading from
806 * stream
807 * @throws IOException - any I/O error
808 */
809 public static void readAttributeSet(ObjectInputStream in,
810 MutableAttributeSet a)
811 throws ClassNotFoundException, IOException
812 {
813 int count = in.readInt();
814 for (int i = 0; i < count; i++)
815 {
816 Object key = in.readObject();
817 Object val = in.readObject();
818 if (readAttributeKeys != null)
819 {
820 Object staticKey = readAttributeKeys.get(key);
821 if (staticKey != null)
822 key = staticKey;
823 Object staticVal = readAttributeKeys.get(val);
824 if (staticVal != null)
825 val = staticVal;
826 }
827 a.addAttribute(key, val);
828 }
829 }
830
831 /**
832 * Serialize an attribute set in a way that is compatible with it
833 * being read in again by {@link #readAttributeSet(ObjectInputStream, MutableAttributeSet)}.
834 * In particular registered static keys are transformed properly.
835 *
836 * @param out - stream to write to
837 * @param a - the attribute set
838 * @throws IOException - any I/O error
839 */
840 public static void writeAttributeSet(ObjectOutputStream out, AttributeSet a)
841 throws IOException
842 {
843 int count = a.getAttributeCount();
844 out.writeInt(count);
845 Enumeration e = a.getAttributeNames();
846 while (e.hasMoreElements())
847 {
848 Object key = e.nextElement();
849 // Write key.
850 if (key instanceof Serializable)
851 out.writeObject(key);
852 else
853 {
854 Object io = writeAttributeKeys.get(key);
855 if (io == null)
856 throw new NotSerializableException(key.getClass().getName()
857 + ", key: " + key);
858 out.writeObject(io);
859 }
860 // Write value.
861 Object val = a.getAttribute(key);
862 Object io = writeAttributeKeys.get(val);
863 if (val instanceof Serializable)
864 out.writeObject(io != null ? io : val);
865 else
866 {
867 if (io == null)
868 throw new NotSerializableException(val.getClass().getName());
869 out.writeObject(io);
870 }
871 }
872 }
873
874 /**
875 * Handles reading in the attributes.
876 * @see #readAttributeSet(ObjectInputStream, MutableAttributeSet)
877 *
878 * @param in - the stream to read from
879 * @param a - the set of attributes
880 * @throws ClassNotFoundException - may be encountered when reading from stream
881 * @throws IOException - any I/O error
882 */
883 public void readAttributes(ObjectInputStream in, MutableAttributeSet a)
884 throws ClassNotFoundException, IOException
885 {
886 readAttributeSet(in, a);
887 }
888
889 /**
890 * Handles writing of the given attributes.
891 * @see #writeAttributeSet(ObjectOutputStream, AttributeSet)
892 *
893 * @param out - stream to write to
894 * @param a - the attribute set
895 * @throws IOException - any I/O error
896 */
897 public void writeAttributes(ObjectOutputStream out, AttributeSet a)
898 throws IOException
899 {
900 writeAttributeSet(out, a);
901 }
902
903 /**
904 * Registers an attribute key as a well-known keys. When an attribute with
905 * such a key is written to a stream, a special syntax is used so that it
906 * can be recognized when it is read back in. All attribute keys defined
907 * in <code>StyleContext</code> are registered as static keys. If you define
908 * additional attribute keys that you want to exist as nonreplicated objects,
909 * then you should register them using this method.
910 *
911 * @param key the key to register as static attribute key
912 */
913 public static void registerStaticAttributeKey(Object key)
914 {
915 String io = key.getClass().getName() + "." + key.toString();
916 if (writeAttributeKeys == null)
917 writeAttributeKeys = new Hashtable();
918 if (readAttributeKeys == null)
919 readAttributeKeys = new Hashtable();
920 writeAttributeKeys.put(key, io);
921 readAttributeKeys.put(io, key);
922 }
923
924 /**
925 * Returns a string representation of this StyleContext.
926 *
927 * @return a string representation of this StyleContext
928 */
929 public String toString()
930 {
931 cleanupPool();
932 StringBuilder b = new StringBuilder();
933 Iterator i = attributeSetPool.keySet().iterator();
934 while (i.hasNext())
935 {
936 Object att = i.next();
937 b.append(att);
938 b.append('\n');
939 }
940 return b.toString();
941 }
942
943 /**
944 * Searches the AttributeSet pool and returns a pooled instance if available,
945 * or pool a new one.
946 *
947 * @return an immutable attribute set that equals the current search key
948 */
949 private AttributeSet searchImmutableSet()
950 {
951 SmallAttributeSet k = createSmallAttributeSet(search);
952 WeakReference ref = (WeakReference) attributeSetPool.get(k);
953 SmallAttributeSet a;
954 if (ref == null || (a = (SmallAttributeSet) ref.get()) == null)
955 {
956 a = k;
957 attributeSetPool.put(a, new WeakReference(a));
958 }
959 return a;
960 }
961
962 /**
963 * Cleans up the attribute set pool from entries that are no longer
964 * referenced.
965 */
966 private void cleanupPool()
967 {
968 // TODO: How else can we force cleaning up the WeakHashMap?
969 attributeSetPool.size();
970 }
971
972 /**
973 * Returns a MutableAttributeSet that holds a. If a itself is mutable,
974 * this returns a itself, otherwise it creates a new SimpleAtttributeSet
975 * via {@link #createLargeAttributeSet(AttributeSet)}.
976 *
977 * @param a the AttributeSet to create a mutable set for
978 *
979 * @return a mutable attribute set that corresponds to a
980 */
981 private MutableAttributeSet getMutableAttributeSet(AttributeSet a)
982 {
983 MutableAttributeSet mas;
984 if (a instanceof MutableAttributeSet)
985 mas = (MutableAttributeSet) a;
986 else
987 mas = createLargeAttributeSet(a);
988 return mas;
989 }
990 }