blob: 49d538d1ddc854d90e8baf4fb575d411c31985aa [file] [log] [blame]
/*
* @(#)$RCSfile: AttributeMap.java,v $ $Revision: 1.3 $ $Date: 2003/12/02 01:34:32 $
*
* (C) Copyright IBM Corp. 1998-1999. All Rights Reserved.
*
* The program is provided "as is" without any warranty express or
* implied, including the warranty of non-infringement and the implied
* warranties of merchantibility and fitness for a particular purpose.
* IBM will not be liable for any damages suffered by you as a result
* of using the Program. In no event will IBM be liable for any
* special, indirect or consequential damages or lost profits even if
* IBM has been advised of the possibility of their occurrence. IBM
* will not be liable for any third party claims against you.
*/
// Requires Java2
package com.ibm.richtext.textlayout.attributes;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
import java.io.Externalizable;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.IOException;
/**
* AttributeMap is an immutable Map. Additionally, there are
* several methods for common operations (union,
* remove, intersect); these methods return new AttributeMap
* instances.
* <p>
* Although any non-null Object can be a key or value in an
* AttributeMap, typically the keys are fields of TextAttribute.
* @see TextAttribute
*/
public final class AttributeMap implements java.util.Map,
com.ibm.richtext.textlayout.attributes.Map,
Externalizable {
static final String COPYRIGHT =
"(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
private static final int CURRENT_VERSION = 1;
private static final long serialVersionUID = 9510803;
private static final String errString = "StyleSets are immutable.";
// This is passed to the Hashtable constructor as the
// load factor argument. It is chosen to avoid resizing
// the Hashtable whenever possible. I think that 1
// does this.
private static final int LOAD_FACTOR = 1;
private Hashtable styleTable;
private transient AttributeSet cachedKeySet = null;
private transient Collection cachedValueCollection = null;
private transient Set cachedEntrySet = null;
/**
* An empty AttributeMap.
*/
public static final AttributeMap EMPTY_ATTRIBUTE_MAP = new AttributeMap();
// ==============
// Constructors
// ==============
/**
* Create a new, empty AttributeMap. EMPTY_STYLE_SET can be used
* in place of an AttributeMap produced by this constructor.
*/
public AttributeMap() {
styleTable = new Hashtable(1, LOAD_FACTOR);
}
/**
* Create an AttributeMap with the same key-value
* entries as the given Map.
* @param map a Map whose key-value entries will
* become the entries for this AttributeMap. <code>map</code>
* is not modified, and must not contain null keys or values.
*/
public AttributeMap(java.util.Map map) {
styleTable = new Hashtable(map.size(), LOAD_FACTOR);
styleTable.putAll(map);
}
/**
* Create an AttributeMap with the same key-value
* entries as the given Hashtable.
* @param hashtable a Hashtable whose key-value entries will
* become the entries for this AttributeMap. <code>table</code>
* is not modified.
*/
public AttributeMap(Hashtable hashtable) {
this((java.util.Map) hashtable);
}
/**
* Create an AttributeMap with a single entry of
* <code>{attribute, value}</code>.
* @param key the key in this AttributeMap's single entry
* @param value the value in this AttributeMap's single entry
*/
public AttributeMap(Object key, Object value) {
styleTable = new Hashtable(1, LOAD_FACTOR);
// hashtable checks value for null
styleTable.put(key, value);
}
// For internal use only.
private AttributeMap(Hashtable table, boolean clone) {
if (clone) {
styleTable = (Hashtable) table.clone();
}
else {
this.styleTable = table;
}
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(CURRENT_VERSION);
out.writeInt(styleTable.size());
Enumeration e = styleTable.keys();
while (e.hasMoreElements()) {
Object key = e.nextElement();
out.writeObject(AttributeKey.mapAttributeToKey(key));
out.writeObject(styleTable.get(key));
}
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
if (in.readInt() != CURRENT_VERSION) {
throw new IOException("Invalid version of StyleBuffer");
}
int count = in.readInt();
for (int i=0; i < count; i += 1) {
Object key = AttributeKey.mapKeyToAttribute(in.readObject());
Object value = in.readObject();
styleTable.put(key, value);
}
}
// ==============
// Map interface
// ==============
// queries
/**
* Return the number of entries in the AttributeMap.
* @return the number of entries in the AttributeMap
*/
public int size() {
return styleTable.size();
}
/**
* Return true if the number of entries in the AttributeMap
* is 0.
* @return true if the number of entries in the AttributeMap
* is 0
*/
public boolean isEmpty() {
return styleTable.isEmpty();
}
/**
* Return true if the given key is in this AttributeMap.
* @param key the key to test
* @return true if <code>key</code> is in this AttributeMap
*/
public boolean containsKey(Object key) {
return styleTable.containsKey(key);
}
/**
* Return true if the given value is in this AttributeMap.
* @param value the value to test
* @return true if <code>value</code> is in this AttributeMap
*/
public boolean containsValue(Object value) {
return styleTable.containsValue(value);
}
/**
* Return the value associated with the given key. If the
* key is not in this AttributeMap null is returned.
* @param key the key to look up
* @return the value associated with <code>key</code>, or
* null if <code>key</code> is not in this AttributeMap
*/
public Object get(Object key) {
return styleTable.get(key);
}
// modifiers - all throw exceptions
/**
* Throws UnsupportedOperationException.
* @see #addAttribute
* @throws UnsupportedOperationException
*/
public Object put(Object key, Object value) {
throw new UnsupportedOperationException(errString);
}
/**
* Throws UnsupportedOperationException.
* @see #removeAttributes
* @throws UnsupportedOperationException
*/
public Object remove(Object key) {
throw new UnsupportedOperationException(errString);
}
/**
* Throws UnsupportedOperationException.
* @see #addAttributes
* @throws UnsupportedOperationException
*/
public void putAll(java.util.Map t) {
throw new UnsupportedOperationException(errString);
}
/**
* Throws UnsupportedOperationException.
* @see #EMPTY_ATTRIBUTE_MAP
* @throws UnsupportedOperationException
*/
public void clear() {
throw new UnsupportedOperationException(errString);
}
// views
/**
* Return an AttributeSet containing every key in this AttributeMap.
* @return an AttributeSet containing every key in this AttributeMap
*/
public Set keySet() {
return getKeySet();
}
/**
* Return an AttributeSet containing every key in this AttributeMap.
* @return an AttributeSet containing every key in this AttributeMap
*/
public AttributeSet getKeySet() {
AttributeSet result = cachedKeySet;
if (result == null) {
result = AttributeSet.createKeySet(styleTable);
cachedKeySet = result;
}
return result;
}
/**
* Return a Collection containing every value in this AttributeMap.
* @return a Collection containing every value in this AttributeMap
*/
public Collection values() {
Collection result = cachedValueCollection;
if (result == null) {
result = Collections.unmodifiableCollection(styleTable.values());
cachedValueCollection = result;
}
return result;
}
/**
* Return a Set containing all entries in this AttributeMap.
*/
public Set entrySet() {
Set result = cachedEntrySet;
if (result == null) {
result = Collections.unmodifiableSet(styleTable.entrySet());
cachedEntrySet = result;
}
return result;
}
public boolean equals(Object rhs) {
if (rhs == this) {
return true;
}
if (rhs == null) {
return false;
}
AttributeMap rhsStyleSet = null;
try {
rhsStyleSet = (AttributeMap) rhs;
}
catch(ClassCastException e) {
return false;
}
return styleTable.equals(rhsStyleSet.styleTable);
}
public int hashCode() {
return styleTable.hashCode();
}
public String toString() {
return styleTable.toString();
}
// ==============
// Operations
// ==============
/**
* Return a AttributeMap which contains entries in this AttributeMap,
* along with an entry for <attribute, value>. If attribute
* is already present in this AttributeMap its value becomes value.
*/
public AttributeMap addAttribute(Object key, Object value) {
// try to optimize for case where <key, value> is already there?
Hashtable newTable = new Hashtable(styleTable.size() + 1, LOAD_FACTOR);
newTable.putAll(styleTable);
newTable.put(key, value);
return new AttributeMap(newTable, false);
}
/**
* Return a AttributeMap which contains entries in this AttributeMap
* and in rhs. If an attribute appears in both StyleSets the
* value from rhs is used.
*/
public AttributeMap addAttributes(AttributeMap rhs) {
int thisSize = size();
if (thisSize == 0) {
return rhs;
}
int otherSize = rhs.size();
if (otherSize == 0) {
return this;
}
Hashtable newTable = new Hashtable(thisSize + otherSize, LOAD_FACTOR);
newTable.putAll(styleTable);
newTable.putAll(rhs);
return new AttributeMap(newTable, false);
}
/**
* Return a AttributeMap which contains entries in this AttributeMap
* and in rhs. If an attribute appears in both StyleSets the
* value from rhs is used.
* The Map's keys and values must be non-null.
*/
public AttributeMap addAttributes(java.util.Map rhs) {
if (rhs instanceof AttributeMap) {
return addAttributes((AttributeMap)rhs);
}
Hashtable newTable = new Hashtable(size() + rhs.size(), LOAD_FACTOR);
newTable.putAll(styleTable);
newTable.putAll(rhs);
return new AttributeMap(newTable, false);
}
/**
* Return a AttributeMap with the entries in this AttributeMap, but
* without attribute as a key.
*/
public AttributeMap removeAttribute(Object attribute) {
if (!containsKey(attribute)) {
return this;
}
Hashtable newTable = new Hashtable(styleTable.size(), LOAD_FACTOR);
newTable.putAll(styleTable);
newTable.remove(attribute);
return new AttributeMap(newTable, false);
}
/**
* Return a AttributeMap with the entries of this AttributeMap whose
* attributes are <b>not</b> in the Set.
*/
public AttributeMap removeAttributes(AttributeSet attributes) {
Set set = attributes;
return removeAttributes(set);
}
/**
* Return a AttributeMap with the entries of this AttributeMap whose
* attributes are <b>not</b> in the Set.
*/
public AttributeMap removeAttributes(Set attributes) {
// Create newTable on demand; if null at
// end of iteration then return this set.
// Should we intersect styleTable.keySet with
// attributes instead?
Hashtable newTable = null;
Iterator attrIter = attributes.iterator();
while (attrIter.hasNext()) {
Object current = attrIter.next();
if (current != null && styleTable.containsKey(current)) {
if (newTable == null) {
newTable = new Hashtable(styleTable.size(), LOAD_FACTOR);
newTable.putAll(styleTable);
}
newTable.remove(current);
}
}
if (newTable != null) {
return new AttributeMap(newTable, false);
}
else {
return this;
}
}
/**
* Return a AttributeMap with the keys of this AttributeMap which
* are also in the Set. The set must not contain null.
*/
public AttributeMap intersectWith(AttributeSet attributes) {
Set set = attributes;
return intersectWith(set);
}
/**
* Return a AttributeMap with the keys of this AttributeMap which
* are also in the Set. The set must not contain null.
*/
public AttributeMap intersectWith(Set attributes) {
// For now, forget about optimizing for the case when
// the return value is equivalent to this set.
int attrSize = attributes.size();
int styleTableSize = styleTable.size();
int size = Math.min(attrSize, styleTableSize);
Hashtable newTable = new Hashtable(size, LOAD_FACTOR);
if (attrSize < styleTableSize) {
Iterator attrIter = attributes.iterator();
while (attrIter.hasNext()) {
Object current = attrIter.next();
if (current != null) {
Object value = styleTable.get(current);
if (value != null) {
newTable.put(current, value);
}
}
}
}
else {
Iterator attrIter = keySet().iterator();
while (attrIter.hasNext()) {
Object current = attrIter.next();
if (attributes.contains(current)) {
newTable.put(current, styleTable.get(current));
}
}
}
return new AttributeMap(newTable, false);
}
/**
* Put all entries in this AttributeMap into the given Map.
* @param rhs the Map into which entries are placed
*/
public void putAllInto(java.util.Map rhs) {
rhs.putAll(this);
}
}