This fixes several issues in StyleContext, a very central class in
Swing's text package. In particular this affects the (de)serialization
of StyleContext and AttributeSets and the caching method of the
StyleContext class, as well as some minor issues. All of these fixes
were driven by the Intel testsuite, and now we pass all StyleContext
(and subclass) tests :-) This should help with memory footprint (we were
actually potentially memory leaking here) as well as performance.
2006-08-27 Roman Kennke <[EMAIL PROTECTED]>
* javax/swing/text/StyleContext.java
(NamedStyle.attributes): Made field transient.
(NamedStyle.changeEvent): Made field transient.
(NamedStyle.name): Removed field. The name is stored as
attribute.
(NamedStyle.NamedStyle(String,Style)): Call setName() for
storing the name and check for null name and resolveParent.
Don't initialize changeEvent.
(NamedStyle.copyAttributes): Return a new NamedStyle,
rather than a plain copy of the attributes field.
(NamedStyle.fireStateChange): Lazily create changeEvent
field.
(NamedStyle.getName): Fetch name from attributes.
(NamedStyle.setName): Store name from attributes.
(NamedStyle.readObject): Implemented for correct
deserialization.
(NamedStyle.writeObject): Implemented for correct
serialization.
(NamedStyle.setResolveParent): When new parent is null,
remove resolveParent attribute. Use addAttribute() method
rather than StyleContext addAttribute().
(NamedStyle.toString): Fixed to produce output equal to the
RI.
(SmallAttributeSet.resolveParent): New field.
(SmallAttributeSet.SmallAttributeSet(AttributeSet)): Update
the resolveParent field correctly.
(SmallAttributeSet.SmallAttributeSet(Object[])): Don't copy
array but store it directly. Update
the resolveParent field correctly.
(SmallAttributeSet.clone): Return this as the object is
immutable.
(SmallAttributeSet.containsAttributes): Make sure that keys
and values are the same.
(SmallAttributeSet.containsAttribute): Make sure that keys
and values are the same.
(SmallAttributeSet.copyAttributes): Return this as the object is
immutable.
(SmallAttributeSet.equals): Fixed comparison. Two AttributeSet
are equal if they have the same number of attributes and
one contains the other.
(SmallAttributeSet.getAttribute): Improved lookup of
resolveParent.
(SmallAttributeSet.getResolveParent): Improved lookup of
resolveParent.
(SmallAttributeSet.isEqual): When comparing object is a
SmallAttributeSet, consider them equal only if they are the
same object.
(SmallAttributeSet.toString): Fixed to produce output equal to the
RI.
(attributeSetPool): New field.
(defaultStyleContext): Initialize lazily.
(defaultStyle): Removed field. This is stored in the style context
as attribute.
(listenerList): Removed field. The NamedStyle stores the
listeners.
(readAttributeKeys): New static field. Used for looking up
the serialization mappings when reading.
(search): New field. Used as search key.
(staticAttributeKeys): Replaced by read/writeAttributeKeys.
(styles): New field. Stores the styles and listeners.
(styleTable): Removed field. Replaced by styles field.
(writeAttributeKeys): New static field. Used for looking up
the serialization mappings when writing.
(static_initializer): Register mappings for all keys in
StyleConstants.
(StyleContext): Initialize styles correctly.
(addAttributes): Fixed caching of immutable attributes.
(addAttribute): Fixed caching of immutable attributes.
(removeAttributes): Fixed caching of immutable attributes.
(removeAttribute): Fixed caching of immutable attributes.
(addChangeListener): Add listener to styles field.
(removeChangeListener): Remove listener from styles field.
(getChangeListeners): Fetch listeners from styles field.
(addStyle): Add style to styles field.
(cleanupPool): New method.
(getDefaultStyleContext): Lazily create context.
(getEmptySet): Simply return SimpleAttributeSet.EMPTY.
(getMutableAttributeSet): New helper method. Used for
caching.
(getStaticAttribute): Fetch key from readAttributeKeys.
(getStyleNames): Return names from styles field.
(getStyle): Lookup style in styles field.
(removeStyle): Remove style from styles field.
(readAttributeSet): Fixed deserialization.
(writeAttributeSet): Fixed serialization.
(readObject): Fixed deserialization.
(writeObject): Fixed serialization.
(reclaim): Simply cleanup the pool.
(registerStaticAttributeKey): Store mapping in both ways.
(searchImmutableSet): New helper method for caching.
(toString): Fixed for output like the RI.
* javax/swing/text/StyleConstants.java
(keys): New field. Stores all known keys.
(StyleConstants): Store created key in keys list.
* javax/swing/event/EventListenerList.java
(readObject): Fixed deserialization.
(writeObject): Fixed serialization.
/Roman
Index: javax/swing/event/EventListenerList.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/event/EventListenerList.java,v
retrieving revision 1.17
diff -u -1 -2 -r1.17 EventListenerList.java
--- javax/swing/event/EventListenerList.java 1 Jul 2006 21:33:17 -0000 1.17
+++ javax/swing/event/EventListenerList.java 27 Aug 2006 20:03:09 -0000
@@ -28,24 +28,27 @@
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package javax.swing.event;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.EventListener;
/**
* A utility class for keeping track of [EMAIL PROTECTED] EventListener}s.
*
* <p><b>Example for using this class:</b>
*
* <blockquote><pre> import java.util.EventListener;
* import javax.swing.event.EventListenerList;
@@ -295,13 +298,60 @@
StringBuffer buf = new StringBuffer("EventListenerList: ");
buf.append(listenerList.length / 2);
buf.append(" listeners: ");
for (int i = 0; i < listenerList.length; i += 2)
{
buf.append(" type ");
buf.append(((Class) listenerList[i]).getName());
buf.append(" listener ");
buf.append(listenerList[i + 1]);
}
return buf.toString();
}
+
+ /**
+ * Serializes an instance to an ObjectOutputStream.
+ *
+ * @param out the stream to serialize to
+ *
+ * @throws IOException if something goes wrong
+ */
+ private void writeObject(ObjectOutputStream out)
+ throws IOException
+ {
+ out.defaultWriteObject();
+ for (int i = 0; i < listenerList.length; i += 2)
+ {
+ Class cl = (Class) listenerList[i];
+ EventListener l = (EventListener) listenerList[i + 1];
+ if (l != null && l instanceof Serializable)
+ {
+ out.writeObject(cl.getName());
+ out.writeObject(l);
+ }
+ }
+ // Write end marker.
+ out.writeObject(null);
+ }
+
+ /**
+ * Deserializes an instance from an ObjectInputStream.
+ *
+ * @param in the input stream
+ *
+ * @throws ClassNotFoundException if a serialized class can't be found
+ * @throws IOException if something goes wrong
+ */
+ private void readObject(ObjectInputStream in)
+ throws ClassNotFoundException, IOException
+ {
+ listenerList = NO_LISTENERS;
+ in.defaultReadObject();
+ Object type;
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ while ((type = in.readObject()) != null)
+ {
+ EventListener l = (EventListener) in.readObject();
+ add(Class.forName((String) type, true, cl), l);
+ }
+ }
}
Index: javax/swing/text/StyleConstants.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/StyleConstants.java,v
retrieving revision 1.12
diff -u -1 -2 -r1.12 StyleConstants.java
--- javax/swing/text/StyleConstants.java 12 Aug 2006 22:16:12 -0000 1.12
+++ javax/swing/text/StyleConstants.java 27 Aug 2006 20:03:10 -0000
@@ -31,24 +31,25 @@
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package javax.swing.text;
import java.awt.Color;
import java.awt.Component;
+import java.util.ArrayList;
import javax.swing.Icon;
/**
* Represents standard attribute keys. This class also contains a set of
* useful static utility methods for querying and populating an
* [EMAIL PROTECTED] AttributeSet}.
*
* @since 1.2
*/
public class StyleConstants
{
@@ -154,31 +155,40 @@
public static final String IconElementName = "icon";
public static final Object ComposedTextAttribute
= new StyleConstants("composed text");
public static final Object ModelAttribute = new StyleConstants("model");
public static final Object NameAttribute = new StyleConstants("name");
public static final Object ResolveAttribute = new StyleConstants("resolver");
+ /**
+ * All StyleConstants keys. This is used in StyleContext to register
+ * all known keys as static attribute keys for serialization.
+ */
+ static ArrayList keys;
+
String keyname;
// Package-private to avoid accessor constructor for use by
// subclasses.
StyleConstants(String k)
{
keyname = k;
+ if (keys == null)
+ keys = new ArrayList();
+ keys.add(this);
}
/**
* Returns a string representation of the attribute key.
*
* @return A string representation of the attribute key.
*/
public String toString()
{
return keyname;
}
Index: javax/swing/text/StyleContext.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/StyleContext.java,v
retrieving revision 1.13
diff -u -1 -2 -r1.13 StyleContext.java
--- javax/swing/text/StyleContext.java 6 Jun 2006 19:56:16 -0000 1.13
+++ javax/swing/text/StyleContext.java 27 Aug 2006 20:03:10 -0000
@@ -34,81 +34,88 @@
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package javax.swing.text;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Toolkit;
import java.io.IOException;
+import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
+import java.lang.ref.WeakReference;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.WeakHashMap;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;
public class StyleContext
- implements Serializable, AbstractDocument.AttributeContext
+ implements Serializable, AbstractDocument.AttributeContext
{
/** The serialization UID (compatible with JDK1.5). */
private static final long serialVersionUID = 8042858831190784241L;
public class NamedStyle
implements Serializable, Style
{
/** The serialization UID (compatible with JDK1.5). */
private static final long serialVersionUID = -6690628971806226374L;
- protected ChangeEvent changeEvent;
+ protected transient ChangeEvent changeEvent;
protected EventListenerList listenerList;
- AttributeSet attributes;
- String name;
+ private transient AttributeSet attributes;
public NamedStyle()
{
this(null, null);
}
public NamedStyle(Style parent)
{
this(null, parent);
}
public NamedStyle(String name, Style parent)
{
- this.name = name;
- this.attributes = getEmptySet();
- this.changeEvent = new ChangeEvent(this);
- this.listenerList = new EventListenerList();
- setResolveParent(parent);
+ attributes = getEmptySet();
+ listenerList = new EventListenerList();
+ if (name != null)
+ setName(name);
+ if (parent != null)
+ setResolveParent(parent);
}
public String getName()
{
+ String name = null;
+ if (isDefined(StyleConstants.NameAttribute))
+ name = getAttribute(StyleConstants.NameAttribute).toString();
return name;
}
public void setName(String n)
{
- name = n;
- fireStateChanged();
+ if (n != null)
+ addAttribute(StyleConstants.NameAttribute, n);
}
public void addChangeListener(ChangeListener l)
{
listenerList.add(ChangeListener.class, l);
}
public void removeChangeListener(ChangeListener l)
{
listenerList.remove(ChangeListener.class, l);
}
@@ -118,24 +125,27 @@
}
public ChangeListener[] getChangeListeners()
{
return (ChangeListener[]) getListeners(ChangeListener.class);
}
protected void fireStateChanged()
{
ChangeListener[] listeners = getChangeListeners();
for (int i = 0; i < listeners.length; ++i)
{
+ // Lazily create event.
+ if (changeEvent == null)
+ changeEvent = new ChangeEvent(this);
listeners[i].stateChanged(changeEvent);
}
}
public void addAttribute(Object name, Object value)
{
attributes = StyleContext.this.addAttribute(attributes, name, value);
fireStateChanged();
}
public void addAttributes(AttributeSet attr)
{
@@ -146,25 +156,28 @@
public boolean containsAttribute(Object name, Object value)
{
return attributes.containsAttribute(name, value);
}
public boolean containsAttributes(AttributeSet attrs)
{
return attributes.containsAttributes(attrs);
}
public AttributeSet copyAttributes()
{
- return attributes.copyAttributes();
+ // The RI returns a NamedStyle as copy, so do we.
+ NamedStyle copy = new NamedStyle();
+ copy.attributes = attributes.copyAttributes();
+ return copy;
}
public Object getAttribute(Object attrName)
{
return attributes.getAttribute(attrName);
}
public int getAttributeCount()
{
return attributes.getAttributeCount();
}
@@ -201,130 +214,143 @@
fireStateChanged();
}
public AttributeSet getResolveParent()
{
return attributes.getResolveParent();
}
public void setResolveParent(AttributeSet parent)
{
if (parent != null)
- {
- attributes = StyleContext.this.addAttribute
- (attributes, ResolveAttribute, parent);
- }
- fireStateChanged();
+ addAttribute(StyleConstants.ResolveAttribute, parent);
+ else
+ removeAttribute(StyleConstants.ResolveAttribute);
}
public String toString()
{
- return ("[NamedStyle: name=" + name + ", attrs=" + attributes.toString() + "]");
- }
+ return "NamedStyle:" + getName() + " " + attributes;
+ }
+
+ private void writeObject(ObjectOutputStream s)
+ throws IOException
+ {
+ s.defaultWriteObject();
+ writeAttributeSet(s, attributes);
+ }
+
+ private void readObject(ObjectInputStream s)
+ throws ClassNotFoundException, IOException
+ {
+ s.defaultReadObject();
+ attributes = SimpleAttributeSet.EMPTY;
+ readAttributeSet(s, this);
+ }
}
public class SmallAttributeSet
implements AttributeSet
{
final Object [] attrs;
+ private AttributeSet resolveParent;
public SmallAttributeSet(AttributeSet a)
{
- if (a == null)
- attrs = new Object[0];
- else
+ int n = a.getAttributeCount();
+ int i = 0;
+ attrs = new Object[n * 2];
+ Enumeration e = a.getAttributeNames();
+ while (e.hasMoreElements())
{
- int n = a.getAttributeCount();
- int i = 0;
- attrs = new Object[n * 2];
- Enumeration e = a.getAttributeNames();
- while (e.hasMoreElements())
- {
- Object name = e.nextElement();
- attrs[i++] = name;
- attrs[i++] = a.getAttribute(name);
- }
+ Object name = e.nextElement();
+ Object value = a.getAttribute(name);
+ if (name == ResolveAttribute)
+ resolveParent = (AttributeSet) value;
+ attrs[i++] = name;
+ attrs[i++] = value;
}
}
public SmallAttributeSet(Object [] a)
{
- if (a == null)
- attrs = new Object[0];
- else
+ attrs = a;
+ for (int i = 0; i < attrs.length; i += 2)
{
- attrs = new Object[a.length];
- System.arraycopy(a, 0, attrs, 0, a.length);
+ if (attrs[i] == ResolveAttribute)
+ resolveParent = (AttributeSet) attrs[i + 1];
}
}
public Object clone()
{
- return new SmallAttributeSet(this.attrs);
+ return this;
}
public boolean containsAttribute(Object name, Object value)
{
- for (int i = 0; i < attrs.length; i += 2)
- {
- if (attrs[i].equals(name) &&
- attrs[i+1].equals(value))
- return true;
- }
- return false;
+ return value.equals(getAttribute(name));
}
public boolean containsAttributes(AttributeSet a)
{
+ boolean res = true;
Enumeration e = a.getAttributeNames();
- while (e.hasMoreElements())
+ while (e.hasMoreElements() && res)
{
Object name = e.nextElement();
- Object val = a.getAttribute(name);
- if (!containsAttribute(name, val))
- return false;
+ res = a.getAttribute(name).equals(getAttribute(name));
}
- return true;
+ return res;
}
public AttributeSet copyAttributes()
{
- return (AttributeSet) clone();
+ return this;
}
public boolean equals(Object obj)
{
- return
- (obj instanceof AttributeSet)
- && this.isEqual((AttributeSet)obj);
+ boolean eq = false;
+ if (obj instanceof AttributeSet)
+ {
+ AttributeSet atts = (AttributeSet) obj;
+ eq = getAttributeCount() == atts.getAttributeCount()
+ && containsAttributes(atts);
+ }
+ return eq;
}
public Object getAttribute(Object key)
{
- for (int i = 0; i < attrs.length; i += 2)
+ Object att = null;
+ if (key == StyleConstants.ResolveAttribute)
+ att = resolveParent;
+
+ for (int i = 0; i < attrs.length && att == null; i += 2)
{
if (attrs[i].equals(key))
- return attrs[i+1];
+ att = attrs[i + 1];
}
-
+
// Check the resolve parent, unless we're looking for the
- // ResolveAttribute, which would cause an infinite loop
- if (!(key.equals(ResolveAttribute)))
+ // ResolveAttribute, which must not be looked up
+ if (att == null)
{
- Object p = getResolveParent();
- if (p != null && p instanceof AttributeSet)
- return (((AttributeSet)p).getAttribute(key));
+ AttributeSet parent = getResolveParent();
+ if (parent != null)
+ att = parent.getAttribute(key);
}
- return null;
+ return att;
}
public int getAttributeCount()
{
return attrs.length / 2;
}
public Enumeration getAttributeNames()
{
return new Enumeration()
{
int i = 0;
@@ -333,168 +359,210 @@
return i < attrs.length;
}
public Object nextElement()
{
i += 2;
return attrs[i-2];
}
};
}
public AttributeSet getResolveParent()
{
- return (AttributeSet) getAttribute(ResolveAttribute);
+ return resolveParent;
}
public int hashCode()
{
return java.util.Arrays.asList(attrs).hashCode();
}
public boolean isDefined(Object key)
{
for (int i = 0; i < attrs.length; i += 2)
{
if (attrs[i].equals(key))
return true;
}
return false;
}
public boolean isEqual(AttributeSet attr)
{
- return getAttributeCount() == attr.getAttributeCount()
+ boolean eq;
+ // If the other one is also a SmallAttributeSet, it is only considered
+ // equal if it's the same instance.
+ if (attr instanceof SmallAttributeSet)
+ eq = attr == this;
+ else
+ eq = getAttributeCount() == attr.getAttributeCount()
&& this.containsAttributes(attr);
+ return eq;
}
public String toString()
{
- StringBuffer sb = new StringBuffer();
- sb.append("[StyleContext.SmallattributeSet:");
- for (int i = 0; i < attrs.length - 1; ++i)
+ StringBuilder sb = new StringBuilder();
+ sb.append('{');
+ for (int i = 0; i < attrs.length; i += 2)
{
- sb.append(" (");
- sb.append(attrs[i].toString());
- sb.append("=");
- sb.append(attrs[i+1].toString());
- sb.append(")");
+ if (attrs[i + 1] instanceof AttributeSet)
+ {
+ sb.append(attrs[i]);
+ sb.append("=AttributeSet,");
+ }
+ else
+ {
+ sb.append(attrs[i]);
+ sb.append('=');
+ sb.append(attrs[i + 1]);
+ sb.append(',');
+ }
}
- sb.append("]");
+ sb.append("}");
return sb.toString();
}
}
- // FIXME: official javadocs suggest that these might be more usefully
- // implemented using a WeakHashMap, but not sure if that works most
- // places or whether it really matters anyways.
- //
- // FIXME: also not sure if these tables ought to be static (singletons),
- // shared across all StyleContexts. I think so, but it's not clear in
- // docs. revert to non-shared if you think it matters.
-
/**
- * The name of the default style.
+ * Register StyleConstant keys as static attribute keys for serialization.
*/
- public static final String DEFAULT_STYLE = "default";
-
+ static
+ {
+ // Don't let problems while doing this prevent class loading.
+ try
+ {
+ for (Iterator i = StyleConstants.keys.iterator(); i.hasNext();)
+ registerStaticAttributeKey(i.next());
+ }
+ catch (Throwable t)
+ {
+ t.printStackTrace();
+ }
+ }
+
/**
- * The default style for this style context.
+ * The name of the default style.
*/
- NamedStyle defaultStyle = new NamedStyle(DEFAULT_STYLE, null);
+ public static final String DEFAULT_STYLE = "default";
static Hashtable sharedAttributeSets = new Hashtable();
static Hashtable sharedFonts = new Hashtable();
- static StyleContext defaultStyleContext = new StyleContext();
+ static StyleContext defaultStyleContext;
static final int compressionThreshold = 9;
/**
* These attribute keys are handled specially in serialization.
*/
- private static Hashtable staticAttributeKeys = new Hashtable();
+ private static Hashtable writeAttributeKeys;
+ private static Hashtable readAttributeKeys;
+
+ private NamedStyle styles;
+
+ /**
+ * Used for searching attributes in the pool.
+ */
+ private transient MutableAttributeSet search = new SimpleAttributeSet();
+
+ /**
+ * A pool of immutable AttributeSets.
+ */
+ private transient WeakHashMap attributeSetPool = new WeakHashMap();
- EventListenerList listenerList;
- Hashtable styleTable;
-
/**
* Creates a new instance of the style context. Add the default style
* to the style table.
*/
public StyleContext()
{
- listenerList = new EventListenerList();
- styleTable = new Hashtable();
- styleTable.put(DEFAULT_STYLE, defaultStyle);
+ styles = new NamedStyle(null);
+ addStyle(DEFAULT_STYLE, null);
}
protected SmallAttributeSet createSmallAttributeSet(AttributeSet a)
{
return new SmallAttributeSet(a);
}
protected MutableAttributeSet createLargeAttributeSet(AttributeSet a)
{
return new SimpleAttributeSet(a);
}
public void addChangeListener(ChangeListener listener)
{
- listenerList.add(ChangeListener.class, listener);
+ styles.addChangeListener(listener);
}
public void removeChangeListener(ChangeListener listener)
{
- listenerList.remove(ChangeListener.class, listener);
+ styles.removeChangeListener(listener);
}
public ChangeListener[] getChangeListeners()
{
- return (ChangeListener[]) listenerList.getListeners(ChangeListener.class);
+ return styles.getChangeListeners();
}
public Style addStyle(String name, Style parent)
{
Style newStyle = new NamedStyle(name, parent);
if (name != null)
- styleTable.put(name, newStyle);
+ styles.addAttribute(name, newStyle);
return newStyle;
}
public void removeStyle(String name)
{
- styleTable.remove(name);
+ styles.removeAttribute(name);
}
/**
* Get the style from the style table. If the passed name
* matches [EMAIL PROTECTED] #DEFAULT_STYLE}, returns the default style.
* Otherwise returns the previously defined style of
* <code>null</code> if the style with the given name is not defined.
*
* @param name the name of the style.
*
* @return the style with the given name or null if no such defined.
*/
public Style getStyle(String name)
{
- return (Style) styleTable.get(name);
+ return (Style) styles.getAttribute(name);
}
/**
* Get the names of the style. The returned enumeration always
* contains at least one member, the default style.
*/
public Enumeration getStyleNames()
{
- return styleTable.keys();
+ return styles.getAttributeNames();
+ }
+
+ private void readObject(ObjectInputStream in)
+ throws ClassNotFoundException, IOException
+ {
+ search = new SimpleAttributeSet();
+ attributeSetPool = new WeakHashMap();
+ in.defaultReadObject();
+ }
+
+ private void writeObject(ObjectOutputStream out)
+ throws IOException
+ {
+ cleanupPool();
+ out.defaultWriteObject();
}
//
// StyleContexts only understand the "simple" model of fonts present in
// pre-java2d systems: fonts are a family name, a size (integral number
// of points), and a mask of style parameters (plain, bold, italic, or
// bold|italic). We have an inner class here called SimpleFontSpec which
// holds such triples.
//
// A SimpleFontSpec can be built for *any* AttributeSet because the size,
// family, and style keys in an AttributeSet have default values (defined
// over in StyleConstants).
@@ -568,163 +636,151 @@
public Color getBackground(AttributeSet a)
{
return StyleConstants.getBackground(a);
}
protected int getCompressionThreshold()
{
return compressionThreshold;
}
public static StyleContext getDefaultStyleContext()
{
+ if (defaultStyleContext == null)
+ defaultStyleContext = new StyleContext();
return defaultStyleContext;
}
public AttributeSet addAttribute(AttributeSet old, Object name, Object value)
{
- if (old instanceof MutableAttributeSet)
+ AttributeSet ret;
+ if (old.getAttributeCount() + 1 < getCompressionThreshold())
{
- ((MutableAttributeSet)old).addAttribute(name, value);
- return old;
+ search.removeAttributes(search);
+ search.addAttributes(old);
+ search.addAttribute(name, value);
+ reclaim(old);
+ ret = searchImmutableSet();
}
- else
+ else
{
- MutableAttributeSet mutable = createLargeAttributeSet(old);
- mutable.addAttribute(name, value);
- if (mutable.getAttributeCount() >= getCompressionThreshold())
- return mutable;
- else
- {
- SmallAttributeSet small = createSmallAttributeSet(mutable);
- if (sharedAttributeSets.containsKey(small))
- small = (SmallAttributeSet) sharedAttributeSets.get(small);
- else
- sharedAttributeSets.put(small,small);
- return small;
- }
+ MutableAttributeSet mas = getMutableAttributeSet(old);
+ mas.addAttribute(name, value);
+ ret = mas;
}
+ return ret;
}
public AttributeSet addAttributes(AttributeSet old, AttributeSet attributes)
{
- if (old instanceof MutableAttributeSet)
+ AttributeSet ret;
+ if (old.getAttributeCount() + attributes.getAttributeCount()
+ < getCompressionThreshold())
{
- ((MutableAttributeSet)old).addAttributes(attributes);
- return old;
+ search.removeAttributes(search);
+ search.addAttributes(old);
+ search.addAttributes(attributes);
+ reclaim(old);
+ ret = searchImmutableSet();
}
- else
+ else
{
- MutableAttributeSet mutable = createLargeAttributeSet(old);
- mutable.addAttributes(attributes);
- if (mutable.getAttributeCount() >= getCompressionThreshold())
- return mutable;
- else
- {
- SmallAttributeSet small = createSmallAttributeSet(mutable);
- if (sharedAttributeSets.containsKey(small))
- small = (SmallAttributeSet) sharedAttributeSets.get(small);
- else
- sharedAttributeSets.put(small,small);
- return small;
- }
+ MutableAttributeSet mas = getMutableAttributeSet(old);
+ mas.addAttributes(attributes);
+ ret = mas;
}
+ return ret;
}
public AttributeSet getEmptySet()
{
- AttributeSet e = createSmallAttributeSet(null);
- if (sharedAttributeSets.containsKey(e))
- e = (AttributeSet) sharedAttributeSets.get(e);
- else
- sharedAttributeSets.put(e, e);
- return e;
+ return SimpleAttributeSet.EMPTY;
}
public void reclaim(AttributeSet attributes)
{
- if (sharedAttributeSets.containsKey(attributes))
- sharedAttributeSets.remove(attributes);
+ cleanupPool();
}
public AttributeSet removeAttribute(AttributeSet old, Object name)
{
- if (old instanceof MutableAttributeSet)
+ AttributeSet ret;
+ if (old.getAttributeCount() - 1 <= getCompressionThreshold())
{
- ((MutableAttributeSet)old).removeAttribute(name);
- if (old.getAttributeCount() < getCompressionThreshold())
- {
- SmallAttributeSet small = createSmallAttributeSet(old);
- if (!sharedAttributeSets.containsKey(small))
- sharedAttributeSets.put(small,small);
- old = (AttributeSet) sharedAttributeSets.get(small);
- }
- return old;
+ search.removeAttributes(search);
+ search.addAttributes(old);
+ search.removeAttribute(name);
+ reclaim(old);
+ ret = searchImmutableSet();
}
- else
- {
- MutableAttributeSet mutable = createLargeAttributeSet(old);
- mutable.removeAttribute(name);
- SmallAttributeSet small = createSmallAttributeSet(mutable);
- if (sharedAttributeSets.containsKey(small))
- small = (SmallAttributeSet) sharedAttributeSets.get(small);
- else
- sharedAttributeSets.put(small,small);
- return small;
+ else
+ {
+ MutableAttributeSet mas = getMutableAttributeSet(old);
+ mas.removeAttribute(name);
+ ret = mas;
}
+ return ret;
}
public AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes)
{
- return removeAttributes(old, attributes.getAttributeNames());
+ AttributeSet ret;
+ if (old.getAttributeCount() <= getCompressionThreshold())
+ {
+ search.removeAttributes(search);
+ search.addAttributes(old);
+ search.removeAttributes(attributes);
+ reclaim(old);
+ ret = searchImmutableSet();
+ }
+ else
+ {
+ MutableAttributeSet mas = getMutableAttributeSet(old);
+ mas.removeAttributes(attributes);
+ ret = mas;
+ }
+ return ret;
}
public AttributeSet removeAttributes(AttributeSet old, Enumeration names)
{
- if (old instanceof MutableAttributeSet)
+ AttributeSet ret;
+ if (old.getAttributeCount() <= getCompressionThreshold())
{
- ((MutableAttributeSet)old).removeAttributes(names);
- if (old.getAttributeCount() < getCompressionThreshold())
- {
- SmallAttributeSet small = createSmallAttributeSet(old);
- if (!sharedAttributeSets.containsKey(small))
- sharedAttributeSets.put(small,small);
- old = (AttributeSet) sharedAttributeSets.get(small);
- }
- return old;
+ search.removeAttributes(search);
+ search.addAttributes(old);
+ search.removeAttributes(names);
+ reclaim(old);
+ ret = searchImmutableSet();
}
- else
- {
- MutableAttributeSet mutable = createLargeAttributeSet(old);
- mutable.removeAttributes(names);
- SmallAttributeSet small = createSmallAttributeSet(mutable);
- if (sharedAttributeSets.containsKey(small))
- small = (SmallAttributeSet) sharedAttributeSets.get(small);
- else
- sharedAttributeSets.put(small,small);
- return small;
- }
+ else
+ {
+ MutableAttributeSet mas = getMutableAttributeSet(old);
+ mas.removeAttributes(names);
+ ret = mas;
+ }
+ return ret;
}
/**
* Gets the object previously registered with registerStaticAttributeKey.
*
* @param key - the key that was registered.
* @return the object previously registered with registerStaticAttributeKey.
*/
public static Object getStaticAttribute(Object key)
{
if (key == null)
return null;
- return staticAttributeKeys.get(key);
+ return readAttributeKeys.get(key);
}
/**
* Returns the String that key will be registered with
* registerStaticAttributeKey.
*
* @param key - the key that will be registered.
* @return the string the key will be registered with.
*/
public static Object getStaticAttributeKey(Object key)
{
return key.getClass().getName() + "." + key.toString();
@@ -733,72 +789,87 @@
/**
* Reads a set of attributes from the given object input stream. This will
* attempt to restore keys that were static objects by considering only the
* keys that have were registered with registerStaticAttributeKey. The
* attributes retrieved will be placed into the given set.
*
* @param in - the stream to read from
* @param a - the set of attributes
* @throws ClassNotFoundException - may be encountered when reading from
* stream
* @throws IOException - any I/O error
*/
- public static void readAttributeSet(ObjectInputStream in, MutableAttributeSet a)
+ public static void readAttributeSet(ObjectInputStream in,
+ MutableAttributeSet a)
throws ClassNotFoundException, IOException
{
- if (in == null || a == null)
- return;
-
- Object key = in.readObject();
- Object val = in.readObject();
- while (key != null && val != null)
- {
- Object staticKey = staticAttributeKeys.get(key);
- Object staticVal = staticAttributeKeys.get(val);
-
- if (staticKey != null)
- key = staticKey;
- if (staticVal != null)
- val = staticVal;
-
+ int count = in.readInt();
+ for (int i = 0; i < count; i++)
+ {
+ Object key = in.readObject();
+ Object val = in.readObject();
+ if (readAttributeKeys != null)
+ {
+ Object staticKey = readAttributeKeys.get(key);
+ if (staticKey != null)
+ key = staticKey;
+ Object staticVal = readAttributeKeys.get(val);
+ if (staticVal != null)
+ val = staticVal;
+ }
a.addAttribute(key, val);
- key = in.readObject();
- val = in.readObject();
}
}
/**
* Serialize an attribute set in a way that is compatible with it
* being read in again by [EMAIL PROTECTED] #readAttributeSet(ObjectInputStream, MutableAttributeSet)}.
* In particular registered static keys are transformed properly.
*
* @param out - stream to write to
* @param a - the attribute set
* @throws IOException - any I/O error
*/
public static void writeAttributeSet(ObjectOutputStream out, AttributeSet a)
throws IOException
{
+ int count = a.getAttributeCount();
+ out.writeInt(count);
Enumeration e = a.getAttributeNames();
while (e.hasMoreElements())
{
- Object oldKey = e.nextElement();
- Object newKey = getStaticAttribute(oldKey);
- Object key = (newKey == null) ? oldKey : newKey;
-
- out.writeObject(key);
- out.writeObject(a.getAttribute(oldKey));
+ Object key = e.nextElement();
+ // Write key.
+ if (key instanceof Serializable)
+ out.writeObject(key);
+ else
+ {
+ Object io = writeAttributeKeys.get(key);
+ if (io == null)
+ throw new NotSerializableException(key.getClass().getName()
+ + ", key: " + key);
+ out.writeObject(io);
+ }
+ // Write value.
+ Object val = a.getAttribute(key);
+ Object io = writeAttributeKeys.get(val);
+ if (val instanceof Serializable)
+ out.writeObject(io != null ? io : val);
+ else
+ {
+ if (io == null)
+ throw new NotSerializableException(val.getClass().getName());
+ out.writeObject(io);
+ }
}
- out.writeObject(null);
- out.writeObject(null);
}
/**
* Handles reading in the attributes.
* @see #readAttributeSet(ObjectInputStream, MutableAttributeSet)
*
* @param in - the stream to read from
* @param a - the set of attributes
* @throws ClassNotFoundException - may be encountered when reading from stream
* @throws IOException - any I/O error
*/
public void readAttributes(ObjectInputStream in, MutableAttributeSet a)
@@ -824,17 +895,88 @@
/**
* Registers an attribute key as a well-known keys. When an attribute with
* such a key is written to a stream, a special syntax is used so that it
* can be recognized when it is read back in. All attribute keys defined
* in <code>StyleContext</code> are registered as static keys. If you define
* additional attribute keys that you want to exist as nonreplicated objects,
* then you should register them using this method.
*
* @param key the key to register as static attribute key
*/
public static void registerStaticAttributeKey(Object key)
{
- if (key != null)
- staticAttributeKeys.put(key.getClass().getName() + "." + key.toString(),
- key);
+ String io = key.getClass().getName() + "." + key.toString();
+ if (writeAttributeKeys == null)
+ writeAttributeKeys = new Hashtable();
+ if (readAttributeKeys == null)
+ readAttributeKeys = new Hashtable();
+ writeAttributeKeys.put(key, io);
+ readAttributeKeys.put(io, key);
+ }
+
+ /**
+ * Returns a string representation of this StyleContext.
+ *
+ * @return a string representation of this StyleContext
+ */
+ public String toString()
+ {
+ cleanupPool();
+ StringBuilder b = new StringBuilder();
+ Iterator i = attributeSetPool.keySet().iterator();
+ while (i.hasNext())
+ {
+ Object att = i.next();
+ b.append(att);
+ b.append('\n');
+ }
+ return b.toString();
+ }
+
+ /**
+ * Searches the AttributeSet pool and returns a pooled instance if available,
+ * or pool a new one.
+ *
+ * @return an immutable attribute set that equals the current search key
+ */
+ private AttributeSet searchImmutableSet()
+ {
+ SmallAttributeSet k = createSmallAttributeSet(search);
+ WeakReference ref = (WeakReference) attributeSetPool.get(k);
+ SmallAttributeSet a;
+ if (ref == null || (a = (SmallAttributeSet) ref.get()) == null)
+ {
+ a = k;
+ attributeSetPool.put(a, new WeakReference(a));
+ }
+ return a;
+ }
+
+ /**
+ * Cleans up the attribute set pool from entries that are no longer
+ * referenced.
+ */
+ private void cleanupPool()
+ {
+ // TODO: How else can we force cleaning up the WeakHashMap?
+ attributeSetPool.size();
+ }
+
+ /**
+ * Returns a MutableAttributeSet that holds a. If a itself is mutable,
+ * this returns a itself, otherwise it creates a new SimpleAtttributeSet
+ * via [EMAIL PROTECTED] #createLargeAttributeSet(AttributeSet)}.
+ *
+ * @param a the AttributeSet to create a mutable set for
+ *
+ * @return a mutable attribute set that corresponds to a
+ */
+ private MutableAttributeSet getMutableAttributeSet(AttributeSet a)
+ {
+ MutableAttributeSet mas;
+ if (a instanceof MutableAttributeSet)
+ mas = (MutableAttributeSet) a;
+ else
+ mas = createLargeAttributeSet(a);
+ return mas;
}
}