001: /*
002: * (C) Copyright IBM Corp. 1998-2004. All Rights Reserved.
003: *
004: * The program is provided "as is" without any warranty express or
005: * implied, including the warranty of non-infringement and the implied
006: * warranties of merchantibility and fitness for a particular purpose.
007: * IBM will not be liable for any damages suffered by you as a result
008: * of using the Program. In no event will IBM be liable for any
009: * special, indirect or consequential damages or lost profits even if
010: * IBM has been advised of the possibility of their occurrence. IBM
011: * will not be liable for any third party claims against you.
012: */
013: // Requires Java2
014: package com.ibm.richtext.textlayout.attributes;
015:
016: import java.util.Collection;
017: import java.util.Collections;
018: import java.util.Enumeration;
019: import java.util.Hashtable;
020: import java.util.Iterator;
021: import java.util.Set;
022:
023: import java.io.Externalizable;
024: import java.io.ObjectInput;
025: import java.io.ObjectOutput;
026: import java.io.IOException;
027:
028: /**
029: * AttributeMap is an immutable Map. Additionally, there are
030: * several methods for common operations (union,
031: * remove, intersect); these methods return new AttributeMap
032: * instances.
033: * <p>
034: * Although any non-null Object can be a key or value in an
035: * AttributeMap, typically the keys are fields of TextAttribute.
036: * @see TextAttribute
037: */
038: public final class AttributeMap implements java.util.Map,
039: com.ibm.richtext.textlayout.attributes.Map, Externalizable {
040:
041: static final String COPYRIGHT = "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
042: private static final int CURRENT_VERSION = 1;
043:
044: private static final long serialVersionUID = 9510803;
045:
046: private static final String errString = "StyleSets are immutable.";
047:
048: // This is passed to the Hashtable constructor as the
049: // load factor argument. It is chosen to avoid resizing
050: // the Hashtable whenever possible. I think that 1
051: // does this.
052: private static final int LOAD_FACTOR = 1;
053:
054: private Hashtable styleTable;
055: private transient AttributeSet cachedKeySet = null;
056: private transient Collection cachedValueCollection = null;
057: private transient Set cachedEntrySet = null;
058:
059: /**
060: * An empty AttributeMap.
061: */
062: public static final AttributeMap EMPTY_ATTRIBUTE_MAP = new AttributeMap();
063:
064: // ==============
065: // Constructors
066: // ==============
067:
068: /**
069: * Create a new, empty AttributeMap. EMPTY_STYLE_SET can be used
070: * in place of an AttributeMap produced by this constructor.
071: */
072: public AttributeMap() {
073:
074: styleTable = new Hashtable(1, LOAD_FACTOR);
075: }
076:
077: /**
078: * Create an AttributeMap with the same key-value
079: * entries as the given Map.
080: * @param map a Map whose key-value entries will
081: * become the entries for this AttributeMap. <code>map</code>
082: * is not modified, and must not contain null keys or values.
083: */
084: public AttributeMap(java.util.Map map) {
085:
086: styleTable = new Hashtable(map.size(), LOAD_FACTOR);
087: styleTable.putAll(map);
088: }
089:
090: /**
091: * Create an AttributeMap with the same key-value
092: * entries as the given Hashtable.
093: * @param hashtable a Hashtable whose key-value entries will
094: * become the entries for this AttributeMap. <code>table</code>
095: * is not modified.
096: */
097: public AttributeMap(Hashtable hashtable) {
098:
099: this ((java.util.Map) hashtable);
100: }
101:
102: /**
103: * Create an AttributeMap with a single entry of
104: * <code>{attribute, value}</code>.
105: * @param key the key in this AttributeMap's single entry
106: * @param value the value in this AttributeMap's single entry
107: */
108: public AttributeMap(Object key, Object value) {
109:
110: styleTable = new Hashtable(1, LOAD_FACTOR);
111:
112: // hashtable checks value for null
113: styleTable.put(key, value);
114: }
115:
116: // For internal use only.
117: private AttributeMap(Hashtable table, boolean clone) {
118:
119: if (clone) {
120: styleTable = (Hashtable) table.clone();
121: } else {
122: this .styleTable = table;
123: }
124: }
125:
126: public void writeExternal(ObjectOutput out) throws IOException {
127:
128: out.writeInt(CURRENT_VERSION);
129: out.writeInt(styleTable.size());
130: Enumeration e = styleTable.keys();
131: while (e.hasMoreElements()) {
132: Object key = e.nextElement();
133: out.writeObject(AttributeKey.mapAttributeToKey(key));
134: out.writeObject(styleTable.get(key));
135: }
136: }
137:
138: public void readExternal(ObjectInput in) throws IOException,
139: ClassNotFoundException {
140:
141: if (in.readInt() != CURRENT_VERSION) {
142: throw new IOException("Invalid version of StyleBuffer");
143: }
144:
145: int count = in.readInt();
146: for (int i = 0; i < count; i += 1) {
147: Object key = AttributeKey
148: .mapKeyToAttribute(in.readObject());
149: Object value = in.readObject();
150: styleTable.put(key, value);
151: }
152: }
153:
154: // ==============
155: // Map interface
156: // ==============
157:
158: // queries
159: /**
160: * Return the number of entries in the AttributeMap.
161: * @return the number of entries in the AttributeMap
162: */
163: public int size() {
164:
165: return styleTable.size();
166: }
167:
168: /**
169: * Return true if the number of entries in the AttributeMap
170: * is 0.
171: * @return true if the number of entries in the AttributeMap
172: * is 0
173: */
174: public boolean isEmpty() {
175:
176: return styleTable.isEmpty();
177: }
178:
179: /**
180: * Return true if the given key is in this AttributeMap.
181: * @param key the key to test
182: * @return true if <code>key</code> is in this AttributeMap
183: */
184: public boolean containsKey(Object key) {
185:
186: return styleTable.containsKey(key);
187: }
188:
189: /**
190: * Return true if the given value is in this AttributeMap.
191: * @param value the value to test
192: * @return true if <code>value</code> is in this AttributeMap
193: */
194: public boolean containsValue(Object value) {
195:
196: return styleTable.containsValue(value);
197: }
198:
199: /**
200: * Return the value associated with the given key. If the
201: * key is not in this AttributeMap null is returned.
202: * @param key the key to look up
203: * @return the value associated with <code>key</code>, or
204: * null if <code>key</code> is not in this AttributeMap
205: */
206: public Object get(Object key) {
207:
208: return styleTable.get(key);
209: }
210:
211: // modifiers - all throw exceptions
212:
213: /**
214: * Throws UnsupportedOperationException.
215: * @see #addAttribute
216: * @throws UnsupportedOperationException
217: */
218: public Object put(Object key, Object value) {
219:
220: throw new UnsupportedOperationException(errString);
221: }
222:
223: /**
224: * Throws UnsupportedOperationException.
225: * @see #removeAttributes
226: * @throws UnsupportedOperationException
227: */
228: public Object remove(Object key) {
229:
230: throw new UnsupportedOperationException(errString);
231: }
232:
233: /**
234: * Throws UnsupportedOperationException.
235: * @see #addAttributes
236: * @throws UnsupportedOperationException
237: */
238: public void putAll(java.util.Map t) {
239:
240: throw new UnsupportedOperationException(errString);
241: }
242:
243: /**
244: * Throws UnsupportedOperationException.
245: * @see #EMPTY_ATTRIBUTE_MAP
246: * @throws UnsupportedOperationException
247: */
248: public void clear() {
249:
250: throw new UnsupportedOperationException(errString);
251: }
252:
253: // views
254:
255: /**
256: * Return an AttributeSet containing every key in this AttributeMap.
257: * @return an AttributeSet containing every key in this AttributeMap
258: */
259: public Set keySet() {
260:
261: return getKeySet();
262: }
263:
264: /**
265: * Return an AttributeSet containing every key in this AttributeMap.
266: * @return an AttributeSet containing every key in this AttributeMap
267: */
268: public AttributeSet getKeySet() {
269:
270: AttributeSet result = cachedKeySet;
271:
272: if (result == null) {
273: result = AttributeSet.createKeySet(styleTable);
274: cachedKeySet = result;
275: }
276:
277: return result;
278: }
279:
280: /**
281: * Return a Collection containing every value in this AttributeMap.
282: * @return a Collection containing every value in this AttributeMap
283: */
284: public Collection values() {
285:
286: Collection result = cachedValueCollection;
287:
288: if (result == null) {
289: result = Collections.unmodifiableCollection(styleTable
290: .values());
291: cachedValueCollection = result;
292: }
293:
294: return result;
295: }
296:
297: /**
298: * Return a Set containing all entries in this AttributeMap.
299: */
300: public Set entrySet() {
301:
302: Set result = cachedEntrySet;
303:
304: if (result == null) {
305: result = Collections.unmodifiableSet(styleTable.entrySet());
306: cachedEntrySet = result;
307: }
308:
309: return result;
310: }
311:
312: public boolean equals(Object rhs) {
313:
314: if (rhs == this ) {
315: return true;
316: }
317:
318: if (rhs == null) {
319: return false;
320: }
321:
322: AttributeMap rhsStyleSet = null;
323:
324: try {
325: rhsStyleSet = (AttributeMap) rhs;
326: } catch (ClassCastException e) {
327: return false;
328: }
329:
330: return styleTable.equals(rhsStyleSet.styleTable);
331: }
332:
333: public int hashCode() {
334:
335: return styleTable.hashCode();
336: }
337:
338: public String toString() {
339:
340: return styleTable.toString();
341: }
342:
343: // ==============
344: // Operations
345: // ==============
346:
347: /**
348: * Return a AttributeMap which contains entries in this AttributeMap,
349: * along with an entry for <attribute, value>. If attribute
350: * is already present in this AttributeMap its value becomes value.
351: */
352: public AttributeMap addAttribute(Object key, Object value) {
353:
354: // try to optimize for case where <key, value> is already there?
355: Hashtable newTable = new Hashtable(styleTable.size() + 1,
356: LOAD_FACTOR);
357: newTable.putAll(styleTable);
358: newTable.put(key, value);
359: return new AttributeMap(newTable, false);
360: }
361:
362: /**
363: * Return a AttributeMap which contains entries in this AttributeMap
364: * and in rhs. If an attribute appears in both StyleSets the
365: * value from rhs is used.
366: */
367: public AttributeMap addAttributes(AttributeMap rhs) {
368:
369: int this Size = size();
370:
371: if (this Size == 0) {
372: return rhs;
373: }
374:
375: int otherSize = rhs.size();
376:
377: if (otherSize == 0) {
378: return this ;
379: }
380:
381: Hashtable newTable = new Hashtable(this Size + otherSize,
382: LOAD_FACTOR);
383:
384: newTable.putAll(styleTable);
385: newTable.putAll(rhs);
386:
387: return new AttributeMap(newTable, false);
388: }
389:
390: /**
391: * Return a AttributeMap which contains entries in this AttributeMap
392: * and in rhs. If an attribute appears in both StyleSets the
393: * value from rhs is used.
394: * The Map's keys and values must be non-null.
395: */
396: public AttributeMap addAttributes(java.util.Map rhs) {
397:
398: if (rhs instanceof AttributeMap) {
399: return addAttributes((AttributeMap) rhs);
400: }
401:
402: Hashtable newTable = new Hashtable(size() + rhs.size(),
403: LOAD_FACTOR);
404:
405: newTable.putAll(styleTable);
406: newTable.putAll(rhs);
407:
408: return new AttributeMap(newTable, false);
409: }
410:
411: /**
412: * Return a AttributeMap with the entries in this AttributeMap, but
413: * without attribute as a key.
414: */
415: public AttributeMap removeAttribute(Object attribute) {
416:
417: if (!containsKey(attribute)) {
418: return this ;
419: }
420:
421: Hashtable newTable = new Hashtable(styleTable.size(),
422: LOAD_FACTOR);
423: newTable.putAll(styleTable);
424: newTable.remove(attribute);
425:
426: return new AttributeMap(newTable, false);
427: }
428:
429: /**
430: * Return a AttributeMap with the entries of this AttributeMap whose
431: * attributes are <b>not</b> in the Set.
432: */
433: public AttributeMap removeAttributes(AttributeSet attributes) {
434:
435: Set set = attributes;
436: return removeAttributes(set);
437: }
438:
439: /**
440: * Return a AttributeMap with the entries of this AttributeMap whose
441: * attributes are <b>not</b> in the Set.
442: */
443: public AttributeMap removeAttributes(Set attributes) {
444:
445: // Create newTable on demand; if null at
446: // end of iteration then return this set.
447: // Should we intersect styleTable.keySet with
448: // attributes instead?
449:
450: Hashtable newTable = null;
451: Iterator attrIter = attributes.iterator();
452: while (attrIter.hasNext()) {
453: Object current = attrIter.next();
454: if (current != null && styleTable.containsKey(current)) {
455: if (newTable == null) {
456: newTable = new Hashtable(styleTable.size(),
457: LOAD_FACTOR);
458: newTable.putAll(styleTable);
459: }
460: newTable.remove(current);
461: }
462: }
463:
464: if (newTable != null) {
465: return new AttributeMap(newTable, false);
466: } else {
467: return this ;
468: }
469: }
470:
471: /**
472: * Return a AttributeMap with the keys of this AttributeMap which
473: * are also in the Set. The set must not contain null.
474: */
475: public AttributeMap intersectWith(AttributeSet attributes) {
476:
477: Set set = attributes;
478: return intersectWith(set);
479: }
480:
481: /**
482: * Return a AttributeMap with the keys of this AttributeMap which
483: * are also in the Set. The set must not contain null.
484: */
485: public AttributeMap intersectWith(Set attributes) {
486:
487: // For now, forget about optimizing for the case when
488: // the return value is equivalent to this set.
489:
490: int attrSize = attributes.size();
491: int styleTableSize = styleTable.size();
492: int size = Math.min(attrSize, styleTableSize);
493: Hashtable newTable = new Hashtable(size, LOAD_FACTOR);
494:
495: if (attrSize < styleTableSize) {
496: Iterator attrIter = attributes.iterator();
497: while (attrIter.hasNext()) {
498: Object current = attrIter.next();
499: if (current != null) {
500: Object value = styleTable.get(current);
501: if (value != null) {
502: newTable.put(current, value);
503: }
504: }
505: }
506: } else {
507: Iterator attrIter = keySet().iterator();
508: while (attrIter.hasNext()) {
509: Object current = attrIter.next();
510: if (attributes.contains(current)) {
511: newTable.put(current, styleTable.get(current));
512: }
513: }
514: }
515:
516: return new AttributeMap(newTable, false);
517: }
518:
519: /**
520: * Put all entries in this AttributeMap into the given Map.
521: * @param rhs the Map into which entries are placed
522: */
523: public void putAllInto(java.util.Map rhs) {
524:
525: rhs.putAll(this);
526: }
527: }
|