/* * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.swing.text; import java.awt.*; import java.util.*; import java.io.*; import javax.swing.SwingUtilities; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; import javax.swing.event.ChangeEvent; import java.lang.ref.WeakReference; import java.util.WeakHashMap; import sun.font.FontUtilities; /** * A pool of styles and their associated resources. This class determines * the lifetime of a group of resources by being a container that holds * caches for various resources such as font and color that get reused * by the various style definitions. This can be shared by multiple * documents if desired to maximize the sharing of related resources. *
* This class also provides efficient support for small sets of attributes * and compresses them by sharing across uses and taking advantage of * their immutable nature. Since many styles are replicated, the potential * for sharing is significant, and copies can be extremely cheap. * Larger sets reduce the possibility of sharing, and therefore revert * automatically to a less space-efficient implementation. *
* Warning:
* Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running
* the same version of Swing. As of 1.4, support for long term storage
* of all JavaBeansTM
* has been added to the java.beans
package.
* Please see {@link java.beans.XMLEncoder}.
*
* @author Timothy Prinzing
*/
public class StyleContext implements Serializable, AbstractDocument.AttributeContext {
/**
* Returns default AttributeContext shared by all documents that
* don't bother to define/supply their own context.
*
* @return the context
*/
public static final StyleContext getDefaultStyleContext() {
if (defaultContext == null) {
defaultContext = new StyleContext();
}
return defaultContext;
}
private static StyleContext defaultContext;
/**
* Creates a new StyleContext object.
*/
public StyleContext() {
styles = new NamedStyle(null);
addStyle(DEFAULT_STYLE, null);
}
/**
* Adds a new style into the style hierarchy. Style attributes
* resolve from bottom up so an attribute specified in a child
* will override an attribute specified in the parent.
*
* @param nm the name of the style (must be unique within the
* collection of named styles in the document). The name may
* be null if the style is unnamed, but the caller is responsible
* for managing the reference returned as an unnamed style can't
* be fetched by name. An unnamed style may be useful for things
* like character attribute overrides such as found in a style
* run.
* @param parent the parent style. This may be null if unspecified
* attributes need not be resolved in some other style.
* @return the created style
*/
public Style addStyle(String nm, Style parent) {
Style style = new NamedStyle(nm, parent);
if (nm != null) {
// add a named style, a class of attributes
styles.addAttribute(nm, style);
}
return style;
}
/**
* Removes a named style previously added to the document.
*
* @param nm the name of the style to remove
*/
public void removeStyle(String nm) {
styles.removeAttribute(nm);
}
/**
* Fetches a named style previously added to the document
*
* @param nm the name of the style
* @return the style
*/
public Style getStyle(String nm) {
return (Style) styles.getAttribute(nm);
}
/**
* Fetches the names of the styles defined.
*
* @return the list of names as an enumeration
*/
public Enumeration> getStyleNames() {
return styles.getAttributeNames();
}
/**
* Adds a listener to track when styles are added
* or removed.
*
* @param l the change listener
*/
public void addChangeListener(ChangeListener l) {
styles.addChangeListener(l);
}
/**
* Removes a listener that was tracking styles being
* added or removed.
*
* @param l the change listener
*/
public void removeChangeListener(ChangeListener l) {
styles.removeChangeListener(l);
}
/**
* Returns an array of all the ChangeListener
s added
* to this StyleContext with addChangeListener().
*
* @return all of the ChangeListener
s added or an empty
* array if no listeners have been added
* @since 1.4
*/
public ChangeListener[] getChangeListeners() {
return ((NamedStyle)styles).getChangeListeners();
}
/**
* Gets the font from an attribute set. This is
* implemented to try and fetch a cached font
* for the given AttributeSet, and if that fails
* the font features are resolved and the
* font is fetched from the low-level font cache.
*
* @param attr the attribute set
* @return the font
*/
public Font getFont(AttributeSet attr) {
// PENDING(prinz) add cache behavior
int style = Font.PLAIN;
if (StyleConstants.isBold(attr)) {
style |= Font.BOLD;
}
if (StyleConstants.isItalic(attr)) {
style |= Font.ITALIC;
}
String family = StyleConstants.getFontFamily(attr);
int size = StyleConstants.getFontSize(attr);
/**
* if either superscript or subscript is
* is set, we need to reduce the font size
* by 2.
*/
if (StyleConstants.isSuperscript(attr) ||
StyleConstants.isSubscript(attr)) {
size -= 2;
}
return getFont(family, style, size);
}
/**
* Takes a set of attributes and turn it into a foreground color
* specification. This might be used to specify things
* like brighter, more hue, etc. By default it simply returns
* the value specified by the StyleConstants.Foreground attribute.
*
* @param attr the set of attributes
* @return the color
*/
public Color getForeground(AttributeSet attr) {
return StyleConstants.getForeground(attr);
}
/**
* Takes a set of attributes and turn it into a background color
* specification. This might be used to specify things
* like brighter, more hue, etc. By default it simply returns
* the value specified by the StyleConstants.Background attribute.
*
* @param attr the set of attributes
* @return the color
*/
public Color getBackground(AttributeSet attr) {
return StyleConstants.getBackground(attr);
}
/**
* Gets a new font. This returns a Font from a cache
* if a cached font exists. If not, a Font is added to
* the cache. This is basically a low-level cache for
* 1.1 font features.
*
* @param family the font family (such as "Monospaced")
* @param style the style of the font (such as Font.PLAIN)
* @param size the point size >= 1
* @return the new font
*/
public Font getFont(String family, int style, int size) {
fontSearch.setValue(family, style, size);
Font f = fontTable.get(fontSearch);
if (f == null) {
// haven't seen this one yet.
Style defaultStyle =
getStyle(StyleContext.DEFAULT_STYLE);
if (defaultStyle != null) {
final String FONT_ATTRIBUTE_KEY = "FONT_ATTRIBUTE_KEY";
Font defaultFont =
(Font) defaultStyle.getAttribute(FONT_ATTRIBUTE_KEY);
if (defaultFont != null
&& defaultFont.getFamily().equalsIgnoreCase(family)) {
f = defaultFont.deriveFont(style, size);
}
}
if (f == null) {
f = new Font(family, style, size);
}
if (! FontUtilities.fontSupportsDefaultEncoding(f)) {
f = FontUtilities.getCompositeFontUIResource(f);
}
FontKey key = new FontKey(family, style, size);
fontTable.put(key, f);
}
return f;
}
/**
* Returns font metrics for a font.
*
* @param f the font
* @return the metrics
*/
public FontMetrics getFontMetrics(Font f) {
// The Toolkit implementations cache, so we just forward
// to the default toolkit.
return Toolkit.getDefaultToolkit().getFontMetrics(f);
}
// --- AttributeContext methods --------------------
/**
* Adds an attribute to the given set, and returns
* the new representative set.
*
* This method is thread safe, although most Swing methods * are not. Please see * Concurrency * in Swing for more information. * * @param old the old attribute set * @param name the non-null attribute name * @param value the attribute value * @return the updated attribute set * @see MutableAttributeSet#addAttribute */ public synchronized AttributeSet addAttribute(AttributeSet old, Object name, Object value) { if ((old.getAttributeCount() + 1) <= getCompressionThreshold()) { // build a search key and find/create an immutable and unique // set. search.removeAttributes(search); search.addAttributes(old); search.addAttribute(name, value); reclaim(old); return getImmutableUniqueSet(); } MutableAttributeSet ma = getMutableAttributeSet(old); ma.addAttribute(name, value); return ma; } /** * Adds a set of attributes to the element. *
* This method is thread safe, although most Swing methods * are not. Please see * Concurrency * in Swing for more information. * * @param old the old attribute set * @param attr the attributes to add * @return the updated attribute set * @see MutableAttributeSet#addAttribute */ public synchronized AttributeSet addAttributes(AttributeSet old, AttributeSet attr) { if ((old.getAttributeCount() + attr.getAttributeCount()) <= getCompressionThreshold()) { // build a search key and find/create an immutable and unique // set. search.removeAttributes(search); search.addAttributes(old); search.addAttributes(attr); reclaim(old); return getImmutableUniqueSet(); } MutableAttributeSet ma = getMutableAttributeSet(old); ma.addAttributes(attr); return ma; } /** * Removes an attribute from the set. *
* This method is thread safe, although most Swing methods * are not. Please see * Concurrency * in Swing for more information. * * @param old the old set of attributes * @param name the non-null attribute name * @return the updated attribute set * @see MutableAttributeSet#removeAttribute */ public synchronized AttributeSet removeAttribute(AttributeSet old, Object name) { if ((old.getAttributeCount() - 1) <= getCompressionThreshold()) { // build a search key and find/create an immutable and unique // set. search.removeAttributes(search); search.addAttributes(old); search.removeAttribute(name); reclaim(old); return getImmutableUniqueSet(); } MutableAttributeSet ma = getMutableAttributeSet(old); ma.removeAttribute(name); return ma; } /** * Removes a set of attributes for the element. *
* This method is thread safe, although most Swing methods * are not. Please see * Concurrency * in Swing for more information. * * @param old the old attribute set * @param names the attribute names * @return the updated attribute set * @see MutableAttributeSet#removeAttributes */ public synchronized AttributeSet removeAttributes(AttributeSet old, Enumeration> names) { if (old.getAttributeCount() <= getCompressionThreshold()) { // build a search key and find/create an immutable and unique // set. search.removeAttributes(search); search.addAttributes(old); search.removeAttributes(names); reclaim(old); return getImmutableUniqueSet(); } MutableAttributeSet ma = getMutableAttributeSet(old); ma.removeAttributes(names); return ma; } /** * Removes a set of attributes for the element. *
* This method is thread safe, although most Swing methods * are not. Please see * Concurrency * in Swing for more information. * * @param old the old attribute set * @param attrs the attributes * @return the updated attribute set * @see MutableAttributeSet#removeAttributes */ public synchronized AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs) { if (old.getAttributeCount() <= getCompressionThreshold()) { // build a search key and find/create an immutable and unique // set. search.removeAttributes(search); search.addAttributes(old); search.removeAttributes(attrs); reclaim(old); return getImmutableUniqueSet(); } MutableAttributeSet ma = getMutableAttributeSet(old); ma.removeAttributes(attrs); return ma; } /** * Fetches an empty AttributeSet. * * @return the set */ public AttributeSet getEmptySet() { return SimpleAttributeSet.EMPTY; } /** * Returns a set no longer needed by the MutableAttributeSet implmentation. * This is useful for operation under 1.1 where there are no weak * references. This would typically be called by the finalize method * of the MutableAttributeSet implementation. *
* This method is thread safe, although most Swing methods
* are not. Please see
* Concurrency
* in Swing for more information.
*
* @param a the set to reclaim
*/
public void reclaim(AttributeSet a) {
if (SwingUtilities.isEventDispatchThread()) {
attributesPool.size(); // force WeakHashMap to expunge stale entries
}
// if current thread is not event dispatching thread
// do not bother with expunging stale entries.
}
// --- local methods -----------------------------------------------
/**
* Returns the maximum number of key/value pairs to try and
* compress into unique/immutable sets. Any sets above this
* limit will use hashtables and be a MutableAttributeSet.
*
* @return the threshold
*/
protected int getCompressionThreshold() {
return THRESHOLD;
}
/**
* Create a compact set of attributes that might be shared.
* This is a hook for subclasses that want to alter the
* behavior of SmallAttributeSet. This can be reimplemented
* to return an AttributeSet that provides some sort of
* attribute conversion.
*
* @param a The set of attributes to be represented in the
* the compact form.
*/
protected SmallAttributeSet createSmallAttributeSet(AttributeSet a) {
return new SmallAttributeSet(a);
}
/**
* Create a large set of attributes that should trade off
* space for time. This set will not be shared. This is
* a hook for subclasses that want to alter the behavior
* of the larger attribute storage format (which is
* SimpleAttributeSet by default). This can be reimplemented
* to return a MutableAttributeSet that provides some sort of
* attribute conversion.
*
* @param a The set of attributes to be represented in the
* the larger form.
*/
protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) {
return new SimpleAttributeSet(a);
}
/**
* Clean the unused immutable sets out of the hashtable.
*/
synchronized void removeUnusedSets() {
attributesPool.size(); // force WeakHashMap to expunge stale entries
}
/**
* Search for an existing attribute set using the current search
* parameters. If a matching set is found, return it. If a match
* is not found, we create a new set and add it to the pool.
*/
AttributeSet getImmutableUniqueSet() {
// PENDING(prinz) should consider finding a alternative to
// generating extra garbage on search key.
SmallAttributeSet key = createSmallAttributeSet(search);
WeakReference
* For operation under a 1.1 virtual machine, this
* uses the value returned by registerStaticAttributeKey
method.
* Any attribute key not regsitered as a static key
* will be serialized directly. All values are expected
* to be serializable.
*
* @param out the output stream
* @param a the attribute set
* @exception IOException on any I/O error
*/
public static void writeAttributeSet(ObjectOutputStream out,
AttributeSet a) throws IOException {
int n = a.getAttributeCount();
out.writeInt(n);
Enumeration keys = a.getAttributeNames();
while (keys.hasMoreElements()) {
Object key = keys.nextElement();
if (key instanceof Serializable) {
out.writeObject(key);
} else {
Object ioFmt = freezeKeyMap.get(key);
if (ioFmt == null) {
throw new NotSerializableException(key.getClass().
getName() + " is not serializable as a key in an AttributeSet");
}
out.writeObject(ioFmt);
}
Object value = a.getAttribute(key);
Object ioFmt = freezeKeyMap.get(value);
if (value instanceof Serializable) {
out.writeObject((ioFmt != null) ? ioFmt : value);
} else {
if (ioFmt == null) {
throw new NotSerializableException(value.getClass().
getName() + " is not serializable as a value in an AttributeSet");
}
out.writeObject(ioFmt);
}
}
}
/**
* Reads a set of attributes from the given object input
* stream that have been previously written out with
* writeAttributeSet
. This will try to restore
* keys that were static objects to the static objects in
* the current virtual machine considering only those keys
* that have been registered with the
* registerStaticAttributeKey
method.
* The attributes retrieved from the stream will be placed
* into the given mutable set.
*
* @param in the object stream to read the attribute data from.
* @param a the attribute set to place the attribute
* definitions in.
* @exception ClassNotFoundException passed upward if encountered
* when reading the object stream.
* @exception IOException passed upward if encountered when
* reading the object stream.
*/
public static void readAttributeSet(ObjectInputStream in,
MutableAttributeSet a) throws ClassNotFoundException, IOException {
int n = in.readInt();
for (int i = 0; i < n; i++) {
Object key = in.readObject();
Object value = in.readObject();
if (thawKeyMap != null) {
Object staticKey = thawKeyMap.get(key);
if (staticKey != null) {
key = staticKey;
}
Object staticValue = thawKeyMap.get(value);
if (staticValue != null) {
value = staticValue;
}
}
a.addAttribute(key, value);
}
}
/**
* Registers an object as a static object that is being
* used as a key in attribute sets. This allows the key
* to be treated specially for serialization.
* toString
* concatenated to the classname. The value returned
* by toString should not have the class reference
* in it (ie it should be reimplemented from the
* definition in Object) in order to be the same when
* recomputed later.
*
* @param key the non-null object key
*/
public static void registerStaticAttributeKey(Object key) {
String ioFmt = key.getClass().getName() + "." + key.toString();
if (freezeKeyMap == null) {
freezeKeyMap = new Hashtable