/*
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
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.
* <p>
* 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.
* <p>
* <strong>Warning:</strong>
* 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 JavaBeans™
* has been added to the <code>java.beans</code> 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 <code>ChangeListener</code>s added
* to this StyleContext with addChangeListener().
*
* @return all of the <code>ChangeListener</code>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.
* <p>
* This method is thread safe, although most Swing methods
* are not. Please see
* <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
* in Swing</A> 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.
* <p>
* This method is thread safe, although most Swing methods
* are not. Please see
* <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
* in Swing</A> 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.
* <p>
* This method is thread safe, although most Swing methods
* are not. Please see
* <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
* in Swing</A> 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.
* <p>
* This method is thread safe, although most Swing methods
* are not. Please see
* <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
* in Swing</A> 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.
* <p>
* This method is thread safe, although most Swing methods
* are not. Please see
* <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
* in Swing</A> 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 implementation.
* 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.
* <p>
* This method is thread safe, although most Swing methods
* are not. Please see
* <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
* in Swing</A> 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<
SmallAttributeSet>
reference =
attributesPool.
get(
key);
SmallAttributeSet a;
if (
reference == null || (
a =
reference.
get()) == null) {
a =
key;
attributesPool.
put(
a, new
WeakReference<
SmallAttributeSet>(
a));
}
return
a;
}
/**
* Creates a mutable attribute set to hand out because the current
* needs are too big to try and use a shared version.
*/
MutableAttributeSet getMutableAttributeSet(
AttributeSet a) {
if (
a instanceof
MutableAttributeSet &&
a !=
SimpleAttributeSet.
EMPTY) {
return (
MutableAttributeSet)
a;
}
return
createLargeAttributeSet(
a);
}
/**
* Converts a StyleContext to a String.
*
* @return the string
*/
public
String toString() {
removeUnusedSets();
String s = "";
for (
SmallAttributeSet set :
attributesPool.
keySet()) {
s =
s +
set + "\n";
}
return
s;
}
// --- serialization ---------------------------------------------
/**
* Context-specific handling of writing out attributes
*/
public void
writeAttributes(
ObjectOutputStream out,
AttributeSet a) throws
IOException {
writeAttributeSet(
out,
a);
}
/**
* Context-specific handling of reading in attributes
*/
public void
readAttributes(
ObjectInputStream in,
MutableAttributeSet a) throws
ClassNotFoundException,
IOException {
readAttributeSet(
in,
a);
}
/**
* Writes a set of attributes to the given object stream
* for the purpose of serialization. This will take
* special care to deal with static attribute keys that
* have been registered wit the
* <code>registerStaticAttributeKey</code> method.
* Any attribute key not registered 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
* <code>writeAttributeSet</code>. 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
* <code>registerStaticAttributeKey</code> 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.
* <p>
* For operation under a 1.1 virtual machine, this
* uses the value returned by <code>toString</code>
* 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<
Object,
String>();
thawKeyMap = new
Hashtable<
String,
Object>();
}
freezeKeyMap.
put(
key,
ioFmt);
thawKeyMap.
put(
ioFmt,
key);
}
/**
* Returns the object previously registered with
* <code>registerStaticAttributeKey</code>.
*/
public static
Object getStaticAttribute(
Object key) {
if (
thawKeyMap == null ||
key == null) {
return null;
}
return
thawKeyMap.
get(
key);
}
/**
* Returns the String that <code>key</code> will be registered with
* @see #getStaticAttribute
* @see #registerStaticAttributeKey
*/
public static
Object getStaticAttributeKey(
Object key) {
return
key.
getClass().
getName() + "." +
key.
toString();
}
private void
writeObject(java.io.
ObjectOutputStream s)
throws
IOException
{
// clean out unused sets before saving
removeUnusedSets();
s.
defaultWriteObject();
}
private void
readObject(
ObjectInputStream s)
throws
ClassNotFoundException,
IOException
{
fontSearch = new
FontKey(null, 0, 0);
fontTable = new
Hashtable<
FontKey,
Font>();
search = new
SimpleAttributeSet();
attributesPool =
Collections.
synchronizedMap(new
WeakHashMap<
SmallAttributeSet,
WeakReference<
SmallAttributeSet>>());
s.
defaultReadObject();
}
// --- variables ---------------------------------------------------
/**
* The name given to the default logical style attached
* to paragraphs.
*/
public static final
String DEFAULT_STYLE = "default";
private static
Hashtable<
Object,
String>
freezeKeyMap;
private static
Hashtable<
String,
Object>
thawKeyMap;
private
Style styles;
private transient
FontKey fontSearch = new
FontKey(null, 0, 0);
private transient
Hashtable<
FontKey,
Font>
fontTable = new
Hashtable<
FontKey,
Font>();
private transient
Map<
SmallAttributeSet,
WeakReference<
SmallAttributeSet>>
attributesPool =
Collections.
synchronizedMap(new
WeakHashMap<
SmallAttributeSet,
WeakReference<
SmallAttributeSet>>());
private transient
MutableAttributeSet search = new
SimpleAttributeSet();
/**
* Number of immutable sets that are not currently
* being used. This helps indicate when the sets need
* to be cleaned out of the hashtable they are stored
* in.
*/
private int
unusedSets;
/**
* The threshold for no longer sharing the set of attributes
* in an immutable table.
*/
static final int
THRESHOLD = 9;
/**
* This class holds a small number of attributes in an array.
* The storage format is key, value, key, value, etc. The size
* of the set is the length of the array divided by two. By
* default, this is the class that will be used to store attributes
* when held in the compact sharable form.
*/
public class
SmallAttributeSet implements
AttributeSet {
public
SmallAttributeSet(
Object[]
attributes) {
this.
attributes =
attributes;
updateResolveParent();
}
public
SmallAttributeSet(
AttributeSet attrs) {
int
n =
attrs.
getAttributeCount();
Object[]
tbl = new
Object[2 *
n];
Enumeration names =
attrs.
getAttributeNames();
int
i = 0;
while (
names.
hasMoreElements()) {
tbl[
i] =
names.
nextElement();
tbl[
i+1] =
attrs.
getAttribute(
tbl[
i]);
i += 2;
}
attributes =
tbl;
updateResolveParent();
}
private void
updateResolveParent() {
resolveParent = null;
Object[]
tbl =
attributes;
for (int
i = 0;
i <
tbl.length;
i += 2) {
if (
tbl[
i] ==
StyleConstants.
ResolveAttribute) {
resolveParent = (
AttributeSet)
tbl[
i + 1];
break;
}
}
}
Object getLocalAttribute(
Object nm) {
if (
nm ==
StyleConstants.
ResolveAttribute) {
return
resolveParent;
}
Object[]
tbl =
attributes;
for (int
i = 0;
i <
tbl.length;
i += 2) {
if (
nm.
equals(
tbl[
i])) {
return
tbl[
i+1];
}
}
return null;
}
// --- Object methods -------------------------
/**
* Returns a string showing the key/value pairs
*/
public
String toString() {
String s = "{";
Object[]
tbl =
attributes;
for (int
i = 0;
i <
tbl.length;
i += 2) {
if (
tbl[
i+1] instanceof
AttributeSet) {
// don't recurse
s =
s +
tbl[
i] + "=" + "AttributeSet" + ",";
} else {
s =
s +
tbl[
i] + "=" +
tbl[
i+1] + ",";
}
}
s =
s + "}";
return
s;
}
/**
* Returns a hashcode for this set of attributes.
* @return a hashcode value for this set of attributes.
*/
public int
hashCode() {
int
code = 0;
Object[]
tbl =
attributes;
for (int
i = 1;
i <
tbl.length;
i += 2) {
code ^=
tbl[
i].
hashCode();
}
return
code;
}
/**
* Compares this object to the specified object.
* The result is <code>true</code> if the object is an equivalent
* set of attributes.
* @param obj the object to compare with.
* @return <code>true</code> if the objects are equal;
* <code>false</code> otherwise.
*/
public boolean
equals(
Object obj) {
if (
obj instanceof
AttributeSet) {
AttributeSet attrs = (
AttributeSet)
obj;
return ((
getAttributeCount() ==
attrs.
getAttributeCount()) &&
containsAttributes(
attrs));
}
return false;
}
/**
* Clones a set of attributes. Since the set is immutable, a
* clone is basically the same set.
*
* @return the set of attributes
*/
public
Object clone() {
return this;
}
// --- AttributeSet methods ----------------------------
/**
* Gets the number of attributes that are defined.
*
* @return the number of attributes
* @see AttributeSet#getAttributeCount
*/
public int
getAttributeCount() {
return
attributes.length / 2;
}
/**
* Checks whether a given attribute is defined.
*
* @param key the attribute key
* @return true if the attribute is defined
* @see AttributeSet#isDefined
*/
public boolean
isDefined(
Object key) {
Object[]
a =
attributes;
int
n =
a.length;
for (int
i = 0;
i <
n;
i += 2) {
if (
key.
equals(
a[
i])) {
return true;
}
}
return false;
}
/**
* Checks whether two attribute sets are equal.
*
* @param attr the attribute set to check against
* @return true if the same
* @see AttributeSet#isEqual
*/
public boolean
isEqual(
AttributeSet attr) {
if (
attr instanceof
SmallAttributeSet) {
return
attr == this;
}
return ((
getAttributeCount() ==
attr.
getAttributeCount()) &&
containsAttributes(
attr));
}
/**
* Copies a set of attributes.
*
* @return the copy
* @see AttributeSet#copyAttributes
*/
public
AttributeSet copyAttributes() {
return this;
}
/**
* Gets the value of an attribute.
*
* @param key the attribute name
* @return the attribute value
* @see AttributeSet#getAttribute
*/
public
Object getAttribute(
Object key) {
Object value =
getLocalAttribute(
key);
if (
value == null) {
AttributeSet parent =
getResolveParent();
if (
parent != null)
value =
parent.
getAttribute(
key);
}
return
value;
}
/**
* Gets the names of all attributes.
*
* @return the attribute names
* @see AttributeSet#getAttributeNames
*/
public
Enumeration<?>
getAttributeNames() {
return new
KeyEnumeration(
attributes);
}
/**
* Checks whether a given attribute name/value is defined.
*
* @param name the attribute name
* @param value the attribute value
* @return true if the name/value is defined
* @see AttributeSet#containsAttribute
*/
public boolean
containsAttribute(
Object name,
Object value) {
return
value.
equals(
getAttribute(
name));
}
/**
* Checks whether the attribute set contains all of
* the given attributes.
*
* @param attrs the attributes to check
* @return true if the element contains all the attributes
* @see AttributeSet#containsAttributes
*/
public boolean
containsAttributes(
AttributeSet attrs) {
boolean
result = true;
Enumeration names =
attrs.
getAttributeNames();
while (
result &&
names.
hasMoreElements()) {
Object name =
names.
nextElement();
result =
attrs.
getAttribute(
name).
equals(
getAttribute(
name));
}
return
result;
}
/**
* If not overriden, the resolving parent defaults to
* the parent element.
*
* @return the attributes from the parent
* @see AttributeSet#getResolveParent
*/
public
AttributeSet getResolveParent() {
return
resolveParent;
}
// --- variables -----------------------------------------
Object[]
attributes;
// This is also stored in attributes
AttributeSet resolveParent;
}
/**
* An enumeration of the keys in a SmallAttributeSet.
*/
class
KeyEnumeration implements
Enumeration<
Object> {
KeyEnumeration(
Object[]
attr) {
this.
attr =
attr;
i = 0;
}
/**
* Tests if this enumeration contains more elements.
*
* @return <code>true</code> if this enumeration contains more elements;
* <code>false</code> otherwise.
* @since JDK1.0
*/
public boolean
hasMoreElements() {
return
i <
attr.length;
}
/**
* Returns the next element of this enumeration.
*
* @return the next element of this enumeration.
* @exception NoSuchElementException if no more elements exist.
* @since JDK1.0
*/
public
Object nextElement() {
if (
i <
attr.length) {
Object o =
attr[
i];
i += 2;
return
o;
}
throw new
NoSuchElementException();
}
Object[]
attr;
int
i;
}
/**
* Sorts the key strings so that they can be very quickly compared
* in the attribute set searches.
*/
class
KeyBuilder {
public void
initialize(
AttributeSet a) {
if (
a instanceof
SmallAttributeSet) {
initialize(((
SmallAttributeSet)
a).
attributes);
} else {
keys.
removeAllElements();
data.
removeAllElements();
Enumeration names =
a.
getAttributeNames();
while (
names.
hasMoreElements()) {
Object name =
names.
nextElement();
addAttribute(
name,
a.
getAttribute(
name));
}
}
}
/**
* Initialize with a set of already sorted
* keys (data from an existing SmallAttributeSet).
*/
private void
initialize(
Object[]
sorted) {
keys.
removeAllElements();
data.
removeAllElements();
int
n =
sorted.length;
for (int
i = 0;
i <
n;
i += 2) {
keys.
addElement(
sorted[
i]);
data.
addElement(
sorted[
i+1]);
}
}
/**
* Creates a table of sorted key/value entries
* suitable for creation of an instance of
* SmallAttributeSet.
*/
public
Object[]
createTable() {
int
n =
keys.
size();
Object[]
tbl = new
Object[2 *
n];
for (int
i = 0;
i <
n;
i ++) {
int
offs = 2 *
i;
tbl[
offs] =
keys.
elementAt(
i);
tbl[
offs + 1] =
data.
elementAt(
i);
}
return
tbl;
}
/**
* The number of key/value pairs contained
* in the current key being forged.
*/
int
getCount() {
return
keys.
size();
}
/**
* Adds a key/value to the set.
*/
public void
addAttribute(
Object key,
Object value) {
keys.
addElement(
key);
data.
addElement(
value);
}
/**
* Adds a set of key/value pairs to the set.
*/
public void
addAttributes(
AttributeSet attr) {
if (
attr instanceof
SmallAttributeSet) {
// avoid searching the keys, they are already interned.
Object[]
tbl = ((
SmallAttributeSet)
attr).
attributes;
int
n =
tbl.length;
for (int
i = 0;
i <
n;
i += 2) {
addAttribute(
tbl[
i],
tbl[
i+1]);
}
} else {
Enumeration names =
attr.
getAttributeNames();
while (
names.
hasMoreElements()) {
Object name =
names.
nextElement();
addAttribute(
name,
attr.
getAttribute(
name));
}
}
}
/**
* Removes the given name from the set.
*/
public void
removeAttribute(
Object key) {
int
n =
keys.
size();
for (int
i = 0;
i <
n;
i++) {
if (
keys.
elementAt(
i).
equals(
key)) {
keys.
removeElementAt(
i);
data.
removeElementAt(
i);
return;
}
}
}
/**
* Removes the set of keys from the set.
*/
public void
removeAttributes(
Enumeration names) {
while (
names.
hasMoreElements()) {
Object name =
names.
nextElement();
removeAttribute(
name);
}
}
/**
* Removes the set of matching attributes from the set.
*/
public void
removeAttributes(
AttributeSet attr) {
Enumeration names =
attr.
getAttributeNames();
while (
names.
hasMoreElements()) {
Object name =
names.
nextElement();
Object value =
attr.
getAttribute(
name);
removeSearchAttribute(
name,
value);
}
}
private void
removeSearchAttribute(
Object ikey,
Object value) {
int
n =
keys.
size();
for (int
i = 0;
i <
n;
i++) {
if (
keys.
elementAt(
i).
equals(
ikey)) {
if (
data.
elementAt(
i).
equals(
value)) {
keys.
removeElementAt(
i);
data.
removeElementAt(
i);
}
return;
}
}
}
private
Vector<
Object>
keys = new
Vector<
Object>();
private
Vector<
Object>
data = new
Vector<
Object>();
}
/**
* key for a font table
*/
static class
FontKey {
private
String family;
private int
style;
private int
size;
/**
* Constructs a font key.
*/
public
FontKey(
String family, int
style, int
size) {
setValue(
family,
style,
size);
}
public void
setValue(
String family, int
style, int
size) {
this.
family = (
family != null) ?
family.
intern() : null;
this.
style =
style;
this.
size =
size;
}
/**
* Returns a hashcode for this font.
* @return a hashcode value for this font.
*/
public int
hashCode() {
int
fhash = (
family != null) ?
family.
hashCode() : 0;
return
fhash ^
style ^
size;
}
/**
* Compares this object to the specified object.
* The result is <code>true</code> if and only if the argument is not
* <code>null</code> and is a <code>Font</code> object with the same
* name, style, and point size as this font.
* @param obj the object to compare this font with.
* @return <code>true</code> if the objects are equal;
* <code>false</code> otherwise.
*/
public boolean
equals(
Object obj) {
if (
obj instanceof
FontKey) {
FontKey font = (
FontKey)
obj;
return (
size ==
font.
size) && (
style ==
font.
style) && (
family ==
font.
family);
}
return false;
}
}
/**
* A collection of attributes, typically used to represent
* character and paragraph styles. This is an implementation
* of MutableAttributeSet that can be observed if desired.
* These styles will take advantage of immutability while
* the sets are small enough, and may be substantially more
* efficient than something like SimpleAttributeSet.
* <p>
* <strong>Warning:</strong>
* 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 JavaBeans™
* has been added to the <code>java.beans</code> package.
* Please see {@link java.beans.XMLEncoder}.
*/
public class
NamedStyle implements
Style,
Serializable {
/**
* Creates a new named style.
*
* @param name the style name, null for unnamed
* @param parent the parent style, null if none
* @since 1.4
*/
public
NamedStyle(
String name,
Style parent) {
attributes =
getEmptySet();
if (
name != null) {
setName(
name);
}
if (
parent != null) {
setResolveParent(
parent);
}
}
/**
* Creates a new named style.
*
* @param parent the parent style, null if none
* @since 1.4
*/
public
NamedStyle(
Style parent) {
this(null,
parent);
}
/**
* Creates a new named style, with a null name and parent.
*/
public
NamedStyle() {
attributes =
getEmptySet();
}
/**
* Converts the style to a string.
*
* @return the string
*/
public
String toString() {
return "NamedStyle:" +
getName() + " " +
attributes;
}
/**
* Fetches the name of the style. A style is not required to be named,
* so null is returned if there is no name associated with the style.
*
* @return the name
*/
public
String getName() {
if (
isDefined(
StyleConstants.
NameAttribute)) {
return
getAttribute(
StyleConstants.
NameAttribute).
toString();
}
return null;
}
/**
* Changes the name of the style. Does nothing with a null name.
*
* @param name the new name
*/
public void
setName(
String name) {
if (
name != null) {
this.
addAttribute(
StyleConstants.
NameAttribute,
name);
}
}
/**
* Adds a change listener.
*
* @param l the change listener
*/
public void
addChangeListener(
ChangeListener l) {
listenerList.
add(
ChangeListener.class,
l);
}
/**
* Removes a change listener.
*
* @param l the change listener
*/
public void
removeChangeListener(
ChangeListener l) {
listenerList.
remove(
ChangeListener.class,
l);
}
/**
* Returns an array of all the <code>ChangeListener</code>s added
* to this NamedStyle with addChangeListener().
*
* @return all of the <code>ChangeListener</code>s added or an empty
* array if no listeners have been added
* @since 1.4
*/
public
ChangeListener[]
getChangeListeners() {
return
listenerList.
getListeners(
ChangeListener.class);
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type. The event instance
* is lazily created using the parameters passed into
* the fire method.
*
* @see EventListenerList
*/
protected void
fireStateChanged() {
// Guaranteed to return a non-null array
Object[]
listeners =
listenerList.
getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int
i =
listeners.length-2;
i>=0;
i-=2) {
if (
listeners[
i]==
ChangeListener.class) {
// Lazily create the event:
if (
changeEvent == null)
changeEvent = new
ChangeEvent(this);
((
ChangeListener)
listeners[
i+1]).
stateChanged(
changeEvent);
}
}
}
/**
* Return an array of all the listeners of the given type that
* were added to this model.
*
* @return all of the objects receiving <em>listenerType</em> notifications
* from this model
*
* @since 1.3
*/
public <T extends
EventListener> T[]
getListeners(
Class<T>
listenerType) {
return
listenerList.
getListeners(
listenerType);
}
// --- AttributeSet ----------------------------
// delegated to the immutable field "attributes"
/**
* Gets the number of attributes that are defined.
*
* @return the number of attributes >= 0
* @see AttributeSet#getAttributeCount
*/
public int
getAttributeCount() {
return
attributes.
getAttributeCount();
}
/**
* Checks whether a given attribute is defined.
*
* @param attrName the non-null attribute name
* @return true if the attribute is defined
* @see AttributeSet#isDefined
*/
public boolean
isDefined(
Object attrName) {
return
attributes.
isDefined(
attrName);
}
/**
* Checks whether two attribute sets are equal.
*
* @param attr the attribute set to check against
* @return true if the same
* @see AttributeSet#isEqual
*/
public boolean
isEqual(
AttributeSet attr) {
return
attributes.
isEqual(
attr);
}
/**
* Copies a set of attributes.
*
* @return the copy
* @see AttributeSet#copyAttributes
*/
public
AttributeSet copyAttributes() {
NamedStyle a = new
NamedStyle();
a.
attributes =
attributes.
copyAttributes();
return
a;
}
/**
* Gets the value of an attribute.
*
* @param attrName the non-null attribute name
* @return the attribute value
* @see AttributeSet#getAttribute
*/
public
Object getAttribute(
Object attrName) {
return
attributes.
getAttribute(
attrName);
}
/**
* Gets the names of all attributes.
*
* @return the attribute names as an enumeration
* @see AttributeSet#getAttributeNames
*/
public
Enumeration<?>
getAttributeNames() {
return
attributes.
getAttributeNames();
}
/**
* Checks whether a given attribute name/value is defined.
*
* @param name the non-null attribute name
* @param value the attribute value
* @return true if the name/value is defined
* @see AttributeSet#containsAttribute
*/
public boolean
containsAttribute(
Object name,
Object value) {
return
attributes.
containsAttribute(
name,
value);
}
/**
* Checks whether the element contains all the attributes.
*
* @param attrs the attributes to check
* @return true if the element contains all the attributes
* @see AttributeSet#containsAttributes
*/
public boolean
containsAttributes(
AttributeSet attrs) {
return
attributes.
containsAttributes(
attrs);
}
/**
* Gets attributes from the parent.
* If not overriden, the resolving parent defaults to
* the parent element.
*
* @return the attributes from the parent
* @see AttributeSet#getResolveParent
*/
public
AttributeSet getResolveParent() {
return
attributes.
getResolveParent();
}
// --- MutableAttributeSet ----------------------------------
// should fetch a new immutable record for the field
// "attributes".
/**
* Adds an attribute.
*
* @param name the non-null attribute name
* @param value the attribute value
* @see MutableAttributeSet#addAttribute
*/
public void
addAttribute(
Object name,
Object value) {
StyleContext context =
StyleContext.this;
attributes =
context.
addAttribute(
attributes,
name,
value);
fireStateChanged();
}
/**
* Adds a set of attributes to the element.
*
* @param attr the attributes to add
* @see MutableAttributeSet#addAttribute
*/
public void
addAttributes(
AttributeSet attr) {
StyleContext context =
StyleContext.this;
attributes =
context.
addAttributes(
attributes,
attr);
fireStateChanged();
}
/**
* Removes an attribute from the set.
*
* @param name the non-null attribute name
* @see MutableAttributeSet#removeAttribute
*/
public void
removeAttribute(
Object name) {
StyleContext context =
StyleContext.this;
attributes =
context.
removeAttribute(
attributes,
name);
fireStateChanged();
}
/**
* Removes a set of attributes for the element.
*
* @param names the attribute names
* @see MutableAttributeSet#removeAttributes
*/
public void
removeAttributes(
Enumeration<?>
names) {
StyleContext context =
StyleContext.this;
attributes =
context.
removeAttributes(
attributes,
names);
fireStateChanged();
}
/**
* Removes a set of attributes for the element.
*
* @param attrs the attributes
* @see MutableAttributeSet#removeAttributes
*/
public void
removeAttributes(
AttributeSet attrs) {
StyleContext context =
StyleContext.this;
if (
attrs == this) {
attributes =
context.
getEmptySet();
} else {
attributes =
context.
removeAttributes(
attributes,
attrs);
}
fireStateChanged();
}
/**
* Sets the resolving parent.
*
* @param parent the parent, null if none
* @see MutableAttributeSet#setResolveParent
*/
public void
setResolveParent(
AttributeSet parent) {
if (
parent != null) {
addAttribute(
StyleConstants.
ResolveAttribute,
parent);
} else {
removeAttribute(
StyleConstants.
ResolveAttribute);
}
}
// --- serialization ---------------------------------------------
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);
}
// --- member variables -----------------------------------------------
/**
* The change listeners for the model.
*/
protected
EventListenerList listenerList = new
EventListenerList();
/**
* Only one ChangeEvent is needed per model instance since the
* event's only (read-only) state is the source property. The source
* of events generated here is always "this".
*/
protected transient
ChangeEvent changeEvent = null;
/**
* Inner AttributeSet implementation, which may be an
* immutable unique set being shared.
*/
private transient
AttributeSet attributes;
}
static {
// initialize the static key registry with the StyleConstants keys
try {
int
n =
StyleConstants.
keys.length;
for (int
i = 0;
i <
n;
i++) {
StyleContext.
registerStaticAttributeKey(
StyleConstants.
keys[
i]);
}
} catch (
Throwable e) {
e.
printStackTrace();
}
}
}