001: /*
002: * Copyright 2003-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.collections.map;
017:
018: import java.util.Collection;
019: import java.util.Iterator;
020: import java.util.Map;
021: import java.util.Set;
022:
023: import org.apache.commons.collections.CollectionUtils;
024: import org.apache.commons.collections.collection.CompositeCollection;
025: import org.apache.commons.collections.set.CompositeSet;
026:
027: /**
028: * Decorates a map of other maps to provide a single unified view.
029: * <p>
030: * Changes made to this map will actually be made on the decorated map.
031: * Add and remove operations require the use of a pluggable strategy. If no
032: * strategy is provided then add and remove are unsupported.
033: * <p>
034: * <strong>Note that CompositeMap is not synchronized and is not thread-safe.</strong>
035: * If you wish to use this map from multiple threads concurrently, you must use
036: * appropriate synchronization. The simplest approach is to wrap this map
037: * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
038: * exceptions when accessed by concurrent threads without synchronization.
039: *
040: * @since Commons Collections 3.0
041: * @version $Revision: 348007 $ $Date: 2005-11-21 22:52:57 +0000 (Mon, 21 Nov 2005) $
042: *
043: * @author Brian McCallister
044: */
045: public class CompositeMap implements Map {
046:
047: /** Array of all maps in the composite */
048: private Map[] composite;
049:
050: /** Handle mutation operations */
051: private MapMutator mutator;
052:
053: /**
054: * Create a new, empty, CompositeMap.
055: */
056: public CompositeMap() {
057: this (new Map[] {}, null);
058: }
059:
060: /**
061: * Create a new CompositeMap with two composited Map instances.
062: *
063: * @param one the first Map to be composited
064: * @param two the second Map to be composited
065: * @throws IllegalArgumentException if there is a key collision
066: */
067: public CompositeMap(Map one, Map two) {
068: this (new Map[] { one, two }, null);
069: }
070:
071: /**
072: * Create a new CompositeMap with two composited Map instances.
073: *
074: * @param one the first Map to be composited
075: * @param two the second Map to be composited
076: * @param mutator MapMutator to be used for mutation operations
077: */
078: public CompositeMap(Map one, Map two, MapMutator mutator) {
079: this (new Map[] { one, two }, mutator);
080: }
081:
082: /**
083: * Create a new CompositeMap which composites all of the Map instances in the
084: * argument. It copies the argument array, it does not use it directly.
085: *
086: * @param composite the Maps to be composited
087: * @throws IllegalArgumentException if there is a key collision
088: */
089: public CompositeMap(Map[] composite) {
090: this (composite, null);
091: }
092:
093: /**
094: * Create a new CompositeMap which composites all of the Map instances in the
095: * argument. It copies the argument array, it does not use it directly.
096: *
097: * @param composite Maps to be composited
098: * @param mutator MapMutator to be used for mutation operations
099: */
100: public CompositeMap(Map[] composite, MapMutator mutator) {
101: this .mutator = mutator;
102: this .composite = new Map[0];
103: for (int i = composite.length - 1; i >= 0; --i) {
104: this .addComposited(composite[i]);
105: }
106: }
107:
108: //-----------------------------------------------------------------------
109: /**
110: * Specify the MapMutator to be used by mutation operations.
111: *
112: * @param mutator the MapMutator to be used for mutation delegation
113: */
114: public void setMutator(MapMutator mutator) {
115: this .mutator = mutator;
116: }
117:
118: /**
119: * Add an additional Map to the composite.
120: *
121: * @param map the Map to be added to the composite
122: * @throws IllegalArgumentException if there is a key collision and there is no
123: * MapMutator set to handle it.
124: */
125: public synchronized void addComposited(Map map)
126: throws IllegalArgumentException {
127: for (int i = composite.length - 1; i >= 0; --i) {
128: Collection intersect = CollectionUtils.intersection(
129: this .composite[i].keySet(), map.keySet());
130: if (intersect.size() != 0) {
131: if (this .mutator == null) {
132: throw new IllegalArgumentException(
133: "Key collision adding Map to CompositeMap");
134: } else {
135: this .mutator.resolveCollision(this ,
136: this .composite[i], map, intersect);
137: }
138: }
139: }
140: Map[] temp = new Map[this .composite.length + 1];
141: System.arraycopy(this .composite, 0, temp, 0,
142: this .composite.length);
143: temp[temp.length - 1] = map;
144: this .composite = temp;
145: }
146:
147: /**
148: * Remove a Map from the composite.
149: *
150: * @param map the Map to be removed from the composite
151: * @return The removed Map or <code>null</code> if map is not in the composite
152: */
153: public synchronized Map removeComposited(Map map) {
154: int size = this .composite.length;
155: for (int i = 0; i < size; ++i) {
156: if (this .composite[i].equals(map)) {
157: Map[] temp = new Map[size - 1];
158: System.arraycopy(this .composite, 0, temp, 0, i);
159: System.arraycopy(this .composite, i + 1, temp, i, size
160: - i - 1);
161: this .composite = temp;
162: return map;
163: }
164: }
165: return null;
166: }
167:
168: //-----------------------------------------------------------------------
169: /**
170: * Calls <code>clear()</code> on all composited Maps.
171: *
172: * @throws UnsupportedOperationException if any of the composited Maps do not support clear()
173: */
174: public void clear() {
175: for (int i = this .composite.length - 1; i >= 0; --i) {
176: this .composite[i].clear();
177: }
178: }
179:
180: /**
181: * Returns <tt>true</tt> if this map contains a mapping for the specified
182: * key. More formally, returns <tt>true</tt> if and only if
183: * this map contains at a mapping for a key <tt>k</tt> such that
184: * <tt>(key==null ? k==null : key.equals(k))</tt>. (There can be
185: * at most one such mapping.)
186: *
187: * @param key key whose presence in this map is to be tested.
188: * @return <tt>true</tt> if this map contains a mapping for the specified
189: * key.
190: *
191: * @throws ClassCastException if the key is of an inappropriate type for
192: * this map (optional).
193: * @throws NullPointerException if the key is <tt>null</tt> and this map
194: * does not not permit <tt>null</tt> keys (optional).
195: */
196: public boolean containsKey(Object key) {
197: for (int i = this .composite.length - 1; i >= 0; --i) {
198: if (this .composite[i].containsKey(key)) {
199: return true;
200: }
201: }
202: return false;
203: }
204:
205: /**
206: * Returns <tt>true</tt> if this map maps one or more keys to the
207: * specified value. More formally, returns <tt>true</tt> if and only if
208: * this map contains at least one mapping to a value <tt>v</tt> such that
209: * <tt>(value==null ? v==null : value.equals(v))</tt>. This operation
210: * will probably require time linear in the map size for most
211: * implementations of the <tt>Map</tt> interface.
212: *
213: * @param value value whose presence in this map is to be tested.
214: * @return <tt>true</tt> if this map maps one or more keys to the
215: * specified value.
216: * @throws ClassCastException if the value is of an inappropriate type for
217: * this map (optional).
218: * @throws NullPointerException if the value is <tt>null</tt> and this map
219: * does not not permit <tt>null</tt> values (optional).
220: */
221: public boolean containsValue(Object value) {
222: for (int i = this .composite.length - 1; i >= 0; --i) {
223: if (this .composite[i].containsValue(value)) {
224: return true;
225: }
226: }
227: return false;
228: }
229:
230: /**
231: * Returns a set view of the mappings contained in this map. Each element
232: * in the returned set is a <code>Map.Entry</code>. The set is backed by the
233: * map, so changes to the map are reflected in the set, and vice-versa.
234: * If the map is modified while an iteration over the set is in progress,
235: * the results of the iteration are undefined. The set supports element
236: * removal, which removes the corresponding mapping from the map, via the
237: * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, <tt>removeAll</tt>,
238: * <tt>retainAll</tt> and <tt>clear</tt> operations. It does not support
239: * the <tt>add</tt> or <tt>addAll</tt> operations.
240: * <p>
241: * This implementation returns a <code>CompositeSet</code> which
242: * composites the entry sets from all of the composited maps.
243: *
244: * @see CompositeSet
245: * @return a set view of the mappings contained in this map.
246: */
247: public Set entrySet() {
248: CompositeSet entries = new CompositeSet();
249: for (int i = this .composite.length - 1; i >= 0; --i) {
250: entries.addComposited(this .composite[i].entrySet());
251: }
252: return entries;
253: }
254:
255: /**
256: * Returns the value to which this map maps the specified key. Returns
257: * <tt>null</tt> if the map contains no mapping for this key. A return
258: * value of <tt>null</tt> does not <i>necessarily</i> indicate that the
259: * map contains no mapping for the key; it's also possible that the map
260: * explicitly maps the key to <tt>null</tt>. The <tt>containsKey</tt>
261: * operation may be used to distinguish these two cases.
262: *
263: * <p>More formally, if this map contains a mapping from a key
264: * <tt>k</tt> to a value <tt>v</tt> such that <tt>(key==null ? k==null :
265: * key.equals(k))</tt>, then this method returns <tt>v</tt>; otherwise
266: * it returns <tt>null</tt>. (There can be at most one such mapping.)
267: *
268: * @param key key whose associated value is to be returned.
269: * @return the value to which this map maps the specified key, or
270: * <tt>null</tt> if the map contains no mapping for this key.
271: *
272: * @throws ClassCastException if the key is of an inappropriate type for
273: * this map (optional).
274: * @throws NullPointerException key is <tt>null</tt> and this map does not
275: * not permit <tt>null</tt> keys (optional).
276: *
277: * @see #containsKey(Object)
278: */
279: public Object get(Object key) {
280: for (int i = this .composite.length - 1; i >= 0; --i) {
281: if (this .composite[i].containsKey(key)) {
282: return this .composite[i].get(key);
283: }
284: }
285: return null;
286: }
287:
288: /**
289: * Returns <tt>true</tt> if this map contains no key-value mappings.
290: *
291: * @return <tt>true</tt> if this map contains no key-value mappings.
292: */
293: public boolean isEmpty() {
294: for (int i = this .composite.length - 1; i >= 0; --i) {
295: if (!this .composite[i].isEmpty()) {
296: return false;
297: }
298: }
299: return true;
300: }
301:
302: /**
303: * Returns a set view of the keys contained in this map. The set is
304: * backed by the map, so changes to the map are reflected in the set, and
305: * vice-versa. If the map is modified while an iteration over the set is
306: * in progress, the results of the iteration are undefined. The set
307: * supports element removal, which removes the corresponding mapping from
308: * the map, via the <tt>Iterator.remove</tt>, <tt>Set.remove</tt>,
309: * <tt>removeAll</tt> <tt>retainAll</tt>, and <tt>clear</tt> operations.
310: * It does not support the add or <tt>addAll</tt> operations.
311: * <p>
312: * This implementation returns a <code>CompositeSet</code> which
313: * composites the key sets from all of the composited maps.
314: *
315: * @return a set view of the keys contained in this map.
316: */
317: public Set keySet() {
318: CompositeSet keys = new CompositeSet();
319: for (int i = this .composite.length - 1; i >= 0; --i) {
320: keys.addComposited(this .composite[i].keySet());
321: }
322: return keys;
323: }
324:
325: /**
326: * Associates the specified value with the specified key in this map
327: * (optional operation). If the map previously contained a mapping for
328: * this key, the old value is replaced by the specified value. (A map
329: * <tt>m</tt> is said to contain a mapping for a key <tt>k</tt> if and only
330: * if {@link #containsKey(Object) m.containsKey(k)} would return
331: * <tt>true</tt>.))
332: *
333: * @param key key with which the specified value is to be associated.
334: * @param value value to be associated with the specified key.
335: * @return previous value associated with specified key, or <tt>null</tt>
336: * if there was no mapping for key. A <tt>null</tt> return can
337: * also indicate that the map previously associated <tt>null</tt>
338: * with the specified key, if the implementation supports
339: * <tt>null</tt> values.
340: *
341: * @throws UnsupportedOperationException if no MapMutator has been specified
342: * @throws ClassCastException if the class of the specified key or value
343: * prevents it from being stored in this map.
344: * @throws IllegalArgumentException if some aspect of this key or value
345: * prevents it from being stored in this map.
346: * @throws NullPointerException this map does not permit <tt>null</tt>
347: * keys or values, and the specified key or value is
348: * <tt>null</tt>.
349: */
350: public Object put(Object key, Object value) {
351: if (this .mutator == null) {
352: throw new UnsupportedOperationException(
353: "No mutator specified");
354: }
355: return this .mutator.put(this , this .composite, key, value);
356: }
357:
358: /**
359: * Copies all of the mappings from the specified map to this map
360: * (optional operation). The effect of this call is equivalent to that
361: * of calling {@link #put(Object,Object) put(k, v)} on this map once
362: * for each mapping from key <tt>k</tt> to value <tt>v</tt> in the
363: * specified map. The behavior of this operation is unspecified if the
364: * specified map is modified while the operation is in progress.
365: *
366: * @param map Mappings to be stored in this map.
367: *
368: * @throws UnsupportedOperationException if the <tt>putAll</tt> method is
369: * not supported by this map.
370: *
371: * @throws ClassCastException if the class of a key or value in the
372: * specified map prevents it from being stored in this map.
373: *
374: * @throws IllegalArgumentException some aspect of a key or value in the
375: * specified map prevents it from being stored in this map.
376: * @throws NullPointerException the specified map is <tt>null</tt>, or if
377: * this map does not permit <tt>null</tt> keys or values, and the
378: * specified map contains <tt>null</tt> keys or values.
379: */
380: public void putAll(Map map) {
381: if (this .mutator == null) {
382: throw new UnsupportedOperationException(
383: "No mutator specified");
384: }
385: this .mutator.putAll(this , this .composite, map);
386: }
387:
388: /**
389: * Removes the mapping for this key from this map if it is present
390: * (optional operation). More formally, if this map contains a mapping
391: * from key <tt>k</tt> to value <tt>v</tt> such that
392: * <code>(key==null ? k==null : key.equals(k))</code>, that mapping
393: * is removed. (The map can contain at most one such mapping.)
394: *
395: * <p>Returns the value to which the map previously associated the key, or
396: * <tt>null</tt> if the map contained no mapping for this key. (A
397: * <tt>null</tt> return can also indicate that the map previously
398: * associated <tt>null</tt> with the specified key if the implementation
399: * supports <tt>null</tt> values.) The map will not contain a mapping for
400: * the specified key once the call returns.
401: *
402: * @param key key whose mapping is to be removed from the map.
403: * @return previous value associated with specified key, or <tt>null</tt>
404: * if there was no mapping for key.
405: *
406: * @throws ClassCastException if the key is of an inappropriate type for
407: * the composited map (optional).
408: * @throws NullPointerException if the key is <tt>null</tt> and the composited map
409: * does not not permit <tt>null</tt> keys (optional).
410: * @throws UnsupportedOperationException if the <tt>remove</tt> method is
411: * not supported by the composited map containing the key
412: */
413: public Object remove(Object key) {
414: for (int i = this .composite.length - 1; i >= 0; --i) {
415: if (this .composite[i].containsKey(key)) {
416: return this .composite[i].remove(key);
417: }
418: }
419: return null;
420: }
421:
422: /**
423: * Returns the number of key-value mappings in this map. If the
424: * map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
425: * <tt>Integer.MAX_VALUE</tt>.
426: *
427: * @return the number of key-value mappings in this map.
428: */
429: public int size() {
430: int size = 0;
431: for (int i = this .composite.length - 1; i >= 0; --i) {
432: size += this .composite[i].size();
433: }
434: return size;
435: }
436:
437: /**
438: * Returns a collection view of the values contained in this map. The
439: * collection is backed by the map, so changes to the map are reflected in
440: * the collection, and vice-versa. If the map is modified while an
441: * iteration over the collection is in progress, the results of the
442: * iteration are undefined. The collection supports element removal,
443: * which removes the corresponding mapping from the map, via the
444: * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
445: * <tt>removeAll</tt>, <tt>retainAll</tt> and <tt>clear</tt> operations.
446: * It does not support the add or <tt>addAll</tt> operations.
447: *
448: * @return a collection view of the values contained in this map.
449: */
450: public Collection values() {
451: CompositeCollection keys = new CompositeCollection();
452: for (int i = this .composite.length - 1; i >= 0; --i) {
453: keys.addComposited(this .composite[i].values());
454: }
455: return keys;
456: }
457:
458: /**
459: * Checks if this Map equals another as per the Map specification.
460: *
461: * @param obj the object to compare to
462: * @return true if the maps are equal
463: */
464: public boolean equals(Object obj) {
465: if (obj instanceof Map) {
466: Map map = (Map) obj;
467: return (this .entrySet().equals(map.entrySet()));
468: }
469: return false;
470: }
471:
472: /**
473: * Gets a hash code for the Map as per the Map specification.
474: */
475: public int hashCode() {
476: int code = 0;
477: for (Iterator i = this .entrySet().iterator(); i.hasNext();) {
478: code += i.next().hashCode();
479: }
480: return code;
481: }
482:
483: /**
484: * This interface allows definition for all of the indeterminate
485: * mutators in a CompositeMap, as well as providing a hook for
486: * callbacks on key collisions.
487: */
488: public static interface MapMutator {
489: /**
490: * Called when adding a new Composited Map results in a
491: * key collision.
492: *
493: * @param composite the CompositeMap with the collision
494: * @param existing the Map already in the composite which contains the
495: * offending key
496: * @param added the Map being added
497: * @param intersect the intersection of the keysets of the existing and added maps
498: */
499: public void resolveCollision(CompositeMap composite,
500: Map existing, Map added, Collection intersect);
501:
502: /**
503: * Called when the CompositeMap.put() method is invoked.
504: *
505: * @param map the CompositeMap which is being modified
506: * @param composited array of Maps in the CompositeMap being modified
507: * @param key key with which the specified value is to be associated.
508: * @param value value to be associated with the specified key.
509: * @return previous value associated with specified key, or <tt>null</tt>
510: * if there was no mapping for key. A <tt>null</tt> return can
511: * also indicate that the map previously associated <tt>null</tt>
512: * with the specified key, if the implementation supports
513: * <tt>null</tt> values.
514: *
515: * @throws UnsupportedOperationException if not defined
516: * @throws ClassCastException if the class of the specified key or value
517: * prevents it from being stored in this map.
518: * @throws IllegalArgumentException if some aspect of this key or value
519: * prevents it from being stored in this map.
520: * @throws NullPointerException this map does not permit <tt>null</tt>
521: * keys or values, and the specified key or value is
522: * <tt>null</tt>.
523: */
524: public Object put(CompositeMap map, Map[] composited,
525: Object key, Object value);
526:
527: /**
528: * Called when the CompositeMap.putAll() method is invoked.
529: *
530: * @param map the CompositeMap which is being modified
531: * @param composited array of Maps in the CompositeMap being modified
532: * @param mapToAdd Mappings to be stored in this CompositeMap
533: *
534: * @throws UnsupportedOperationException if not defined
535: * @throws ClassCastException if the class of the specified key or value
536: * prevents it from being stored in this map.
537: * @throws IllegalArgumentException if some aspect of this key or value
538: * prevents it from being stored in this map.
539: * @throws NullPointerException this map does not permit <tt>null</tt>
540: * keys or values, and the specified key or value is
541: * <tt>null</tt>.
542: */
543: public void putAll(CompositeMap map, Map[] composited,
544: Map mapToAdd);
545: }
546: }
|