1. /*
  2. * Copyright 2003-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.collections.map;
  17. import java.util.Collection;
  18. import java.util.Iterator;
  19. import java.util.Map;
  20. import java.util.Set;
  21. import org.apache.commons.collections.CollectionUtils;
  22. import org.apache.commons.collections.collection.CompositeCollection;
  23. import org.apache.commons.collections.set.CompositeSet;
  24. /**
  25. * Decorates a map of other maps to provide a single unified view.
  26. * <p>
  27. * Changes made to this map will actually be made on the decorated map.
  28. * Add and remove operations require the use of a pluggable strategy. If no
  29. * strategy is provided then add and remove are unsupported.
  30. *
  31. * @since Commons Collections 3.0
  32. * @version $Revision: 1.7 $ $Date: 2004/02/18 01:13:19 $
  33. *
  34. * @author Brian McCallister
  35. */
  36. public class CompositeMap implements Map {
  37. /** Array of all maps in the composite */
  38. private Map[] composite;
  39. /** Handle mutation operations */
  40. private MapMutator mutator;
  41. /**
  42. * Create a new, empty, CompositeMap.
  43. */
  44. public CompositeMap() {
  45. this(new Map[]{}, null);
  46. }
  47. /**
  48. * Create a new CompositeMap with two composited Map instances.
  49. *
  50. * @param one the first Map to be composited
  51. * @param two the second Map to be composited
  52. * @throws IllegalArgumentException if there is a key collision
  53. */
  54. public CompositeMap(Map one, Map two) {
  55. this(new Map[]{one, two}, null);
  56. }
  57. /**
  58. * Create a new CompositeMap with two composited Map instances.
  59. *
  60. * @param one the first Map to be composited
  61. * @param two the second Map to be composited
  62. * @param mutator MapMutator to be used for mutation operations
  63. */
  64. public CompositeMap(Map one, Map two, MapMutator mutator) {
  65. this(new Map[]{one, two}, mutator);
  66. }
  67. /**
  68. * Create a new CompositeMap which composites all of the Map instances in the
  69. * argument. It copies the argument array, it does not use it directly.
  70. *
  71. * @param composite the Maps to be composited
  72. * @throws IllegalArgumentException if there is a key collision
  73. */
  74. public CompositeMap(Map[] composite) {
  75. this(composite, null);
  76. }
  77. /**
  78. * Create a new CompositeMap which composites all of the Map instances in the
  79. * argument. It copies the argument array, it does not use it directly.
  80. *
  81. * @param composite Maps to be composited
  82. * @param mutator MapMutator to be used for mutation operations
  83. */
  84. public CompositeMap(Map[] composite, MapMutator mutator) {
  85. this.mutator = mutator;
  86. this.composite = new Map[0];
  87. for (int i = composite.length - 1; i >= 0; --i) {
  88. this.addComposited(composite[i]);
  89. }
  90. }
  91. //-----------------------------------------------------------------------
  92. /**
  93. * Specify the MapMutator to be used by mutation operations.
  94. *
  95. * @param mutator the MapMutator to be used for mutation delegation
  96. */
  97. public void setMutator(MapMutator mutator) {
  98. this.mutator = mutator;
  99. }
  100. /**
  101. * Add an additional Map to the composite.
  102. *
  103. * @param map the Map to be added to the composite
  104. * @throws IllegalArgumentException if there is a key collision and there is no
  105. * MapMutator set to handle it.
  106. */
  107. public synchronized void addComposited(Map map) throws IllegalArgumentException {
  108. for (int i = composite.length - 1; i >= 0; --i) {
  109. Collection intersect = CollectionUtils.intersection(this.composite[i].keySet(), map.keySet());
  110. if (intersect.size() != 0) {
  111. if (this.mutator == null) {
  112. throw new IllegalArgumentException("Key collision adding Map to CompositeMap");
  113. }
  114. else {
  115. this.mutator.resolveCollision(this, this.composite[i], map, intersect);
  116. }
  117. }
  118. }
  119. Map[] temp = new Map[this.composite.length + 1];
  120. System.arraycopy(this.composite, 0, temp, 0, this.composite.length);
  121. temp[temp.length - 1] = map;
  122. this.composite = temp;
  123. }
  124. /**
  125. * Remove a Map from the composite.
  126. *
  127. * @param map the Map to be removed from the composite
  128. * @return The removed Map or <code>null</code> if map is not in the composite
  129. */
  130. public synchronized Map removeComposited(Map map) {
  131. int size = this.composite.length;
  132. for (int i = 0; i < size; ++i) {
  133. if (this.composite[i].equals(map)) {
  134. Map[] temp = new Map[size - 1];
  135. System.arraycopy(this.composite, 0, temp, 0, i);
  136. System.arraycopy(this.composite, i + 1, temp, i, size - i - 1);
  137. this.composite = temp;
  138. return map;
  139. }
  140. }
  141. return null;
  142. }
  143. //-----------------------------------------------------------------------
  144. /**
  145. * Calls <code>clear()</code> on all composited Maps.
  146. *
  147. * @throws UnsupportedOperationException if any of the composited Maps do not support clear()
  148. */
  149. public void clear() {
  150. for (int i = this.composite.length - 1; i >= 0; --i) {
  151. this.composite[i].clear();
  152. }
  153. }
  154. /**
  155. * Returns <tt>true</tt> if this map contains a mapping for the specified
  156. * key. More formally, returns <tt>true</tt> if and only if
  157. * this map contains at a mapping for a key <tt>k</tt> such that
  158. * <tt>(key==null ? k==null : key.equals(k))</tt>. (There can be
  159. * at most one such mapping.)
  160. *
  161. * @param key key whose presence in this map is to be tested.
  162. * @return <tt>true</tt> if this map contains a mapping for the specified
  163. * key.
  164. *
  165. * @throws ClassCastException if the key is of an inappropriate type for
  166. * this map (optional).
  167. * @throws NullPointerException if the key is <tt>null</tt> and this map
  168. * does not not permit <tt>null</tt> keys (optional).
  169. */
  170. public boolean containsKey(Object key) {
  171. for (int i = this.composite.length - 1; i >= 0; --i) {
  172. if (this.composite[i].containsKey(key)) {
  173. return true;
  174. }
  175. }
  176. return false;
  177. }
  178. /**
  179. * Returns <tt>true</tt> if this map maps one or more keys to the
  180. * specified value. More formally, returns <tt>true</tt> if and only if
  181. * this map contains at least one mapping to a value <tt>v</tt> such that
  182. * <tt>(value==null ? v==null : value.equals(v))</tt>. This operation
  183. * will probably require time linear in the map size for most
  184. * implementations of the <tt>Map</tt> interface.
  185. *
  186. * @param value value whose presence in this map is to be tested.
  187. * @return <tt>true</tt> if this map maps one or more keys to the
  188. * specified value.
  189. * @throws ClassCastException if the value is of an inappropriate type for
  190. * this map (optional).
  191. * @throws NullPointerException if the value is <tt>null</tt> and this map
  192. * does not not permit <tt>null</tt> values (optional).
  193. */
  194. public boolean containsValue(Object value) {
  195. for (int i = this.composite.length - 1; i >= 0; --i) {
  196. if (this.composite[i].containsValue(value)) {
  197. return true;
  198. }
  199. }
  200. return false;
  201. }
  202. /**
  203. * Returns a set view of the mappings contained in this map. Each element
  204. * in the returned set is a <code>Map.Entry</code>. The set is backed by the
  205. * map, so changes to the map are reflected in the set, and vice-versa.
  206. * If the map is modified while an iteration over the set is in progress,
  207. * the results of the iteration are undefined. The set supports element
  208. * removal, which removes the corresponding mapping from the map, via the
  209. * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, <tt>removeAll</tt>,
  210. * <tt>retainAll</tt> and <tt>clear</tt> operations. It does not support
  211. * the <tt>add</tt> or <tt>addAll</tt> operations.
  212. * <p>
  213. * This implementation returns a <code>CompositeSet</code> which
  214. * composites the entry sets from all of the composited maps.
  215. *
  216. * @see CompositeSet
  217. * @return a set view of the mappings contained in this map.
  218. */
  219. public Set entrySet() {
  220. CompositeSet entries = new CompositeSet();
  221. for (int i = this.composite.length - 1; i >= 0; --i) {
  222. entries.addComposited(this.composite[i].entrySet());
  223. }
  224. return entries;
  225. }
  226. /**
  227. * Returns the value to which this map maps the specified key. Returns
  228. * <tt>null</tt> if the map contains no mapping for this key. A return
  229. * value of <tt>null</tt> does not <i>necessarily</i> indicate that the
  230. * map contains no mapping for the key; it's also possible that the map
  231. * explicitly maps the key to <tt>null</tt>. The <tt>containsKey</tt>
  232. * operation may be used to distinguish these two cases.
  233. *
  234. * <p>More formally, if this map contains a mapping from a key
  235. * <tt>k</tt> to a value <tt>v</tt> such that <tt>(key==null ? k==null :
  236. * key.equals(k))</tt>, then this method returns <tt>v</tt> otherwise
  237. * it returns <tt>null</tt>. (There can be at most one such mapping.)
  238. *
  239. * @param key key whose associated value is to be returned.
  240. * @return the value to which this map maps the specified key, or
  241. * <tt>null</tt> if the map contains no mapping for this key.
  242. *
  243. * @throws ClassCastException if the key is of an inappropriate type for
  244. * this map (optional).
  245. * @throws NullPointerException key is <tt>null</tt> and this map does not
  246. * not permit <tt>null</tt> keys (optional).
  247. *
  248. * @see #containsKey(Object)
  249. */
  250. public Object get(Object key) {
  251. for (int i = this.composite.length - 1; i >= 0; --i) {
  252. if (this.composite[i].containsKey(key)) {
  253. return this.composite[i].get(key);
  254. }
  255. }
  256. return null;
  257. }
  258. /**
  259. * Returns <tt>true</tt> if this map contains no key-value mappings.
  260. *
  261. * @return <tt>true</tt> if this map contains no key-value mappings.
  262. */
  263. public boolean isEmpty() {
  264. for (int i = this.composite.length - 1; i >= 0; --i) {
  265. if (!this.composite[i].isEmpty()) {
  266. return false;
  267. }
  268. }
  269. return true;
  270. }
  271. /**
  272. * Returns a set view of the keys contained in this map. The set is
  273. * backed by the map, so changes to the map are reflected in the set, and
  274. * vice-versa. If the map is modified while an iteration over the set is
  275. * in progress, the results of the iteration are undefined. The set
  276. * supports element removal, which removes the corresponding mapping from
  277. * the map, via the <tt>Iterator.remove</tt>, <tt>Set.remove</tt>,
  278. * <tt>removeAll</tt> <tt>retainAll</tt>, and <tt>clear</tt> operations.
  279. * It does not support the add or <tt>addAll</tt> operations.
  280. * <p>
  281. * This implementation returns a <code>CompositeSet</code> which
  282. * composites the key sets from all of the composited maps.
  283. *
  284. * @return a set view of the keys contained in this map.
  285. */
  286. public Set keySet() {
  287. CompositeSet keys = new CompositeSet();
  288. for (int i = this.composite.length - 1; i >= 0; --i) {
  289. keys.addComposited(this.composite[i].keySet());
  290. }
  291. return keys;
  292. }
  293. /**
  294. * Associates the specified value with the specified key in this map
  295. * (optional operation). If the map previously contained a mapping for
  296. * this key, the old value is replaced by the specified value. (A map
  297. * <tt>m</tt> is said to contain a mapping for a key <tt>k</tt> if and only
  298. * if {@link #containsKey(Object) m.containsKey(k)} would return
  299. * <tt>true</tt>.))
  300. *
  301. * @param key key with which the specified value is to be associated.
  302. * @param value value to be associated with the specified key.
  303. * @return previous value associated with specified key, or <tt>null</tt>
  304. * if there was no mapping for key. A <tt>null</tt> return can
  305. * also indicate that the map previously associated <tt>null</tt>
  306. * with the specified key, if the implementation supports
  307. * <tt>null</tt> values.
  308. *
  309. * @throws UnsupportedOperationException if no MapMutator has been specified
  310. * @throws ClassCastException if the class of the specified key or value
  311. * prevents it from being stored in this map.
  312. * @throws IllegalArgumentException if some aspect of this key or value
  313. * prevents it from being stored in this map.
  314. * @throws NullPointerException this map does not permit <tt>null</tt>
  315. * keys or values, and the specified key or value is
  316. * <tt>null</tt>.
  317. */
  318. public Object put(Object key, Object value) {
  319. if (this.mutator == null) {
  320. throw new UnsupportedOperationException("No mutator specified");
  321. }
  322. return this.mutator.put(this, this.composite, key, value);
  323. }
  324. /**
  325. * Copies all of the mappings from the specified map to this map
  326. * (optional operation). The effect of this call is equivalent to that
  327. * of calling {@link #put(Object,Object) put(k, v)} on this map once
  328. * for each mapping from key <tt>k</tt> to value <tt>v</tt> in the
  329. * specified map. The behavior of this operation is unspecified if the
  330. * specified map is modified while the operation is in progress.
  331. *
  332. * @param map Mappings to be stored in this map.
  333. *
  334. * @throws UnsupportedOperationException if the <tt>putAll</tt> method is
  335. * not supported by this map.
  336. *
  337. * @throws ClassCastException if the class of a key or value in the
  338. * specified map prevents it from being stored in this map.
  339. *
  340. * @throws IllegalArgumentException some aspect of a key or value in the
  341. * specified map prevents it from being stored in this map.
  342. * @throws NullPointerException the specified map is <tt>null</tt>, or if
  343. * this map does not permit <tt>null</tt> keys or values, and the
  344. * specified map contains <tt>null</tt> keys or values.
  345. */
  346. public void putAll(Map map) {
  347. if (this.mutator == null) {
  348. throw new UnsupportedOperationException("No mutator specified");
  349. }
  350. this.mutator.putAll(this, this.composite, map);
  351. }
  352. /**
  353. * Removes the mapping for this key from this map if it is present
  354. * (optional operation). More formally, if this map contains a mapping
  355. * from key <tt>k</tt> to value <tt>v</tt> such that
  356. * <code>(key==null ? k==null : key.equals(k))</code>, that mapping
  357. * is removed. (The map can contain at most one such mapping.)
  358. *
  359. * <p>Returns the value to which the map previously associated the key, or
  360. * <tt>null</tt> if the map contained no mapping for this key. (A
  361. * <tt>null</tt> return can also indicate that the map previously
  362. * associated <tt>null</tt> with the specified key if the implementation
  363. * supports <tt>null</tt> values.) The map will not contain a mapping for
  364. * the specified key once the call returns.
  365. *
  366. * @param key key whose mapping is to be removed from the map.
  367. * @return previous value associated with specified key, or <tt>null</tt>
  368. * if there was no mapping for key.
  369. *
  370. * @throws ClassCastException if the key is of an inappropriate type for
  371. * the composited map (optional).
  372. * @throws NullPointerException if the key is <tt>null</tt> and the composited map
  373. * does not not permit <tt>null</tt> keys (optional).
  374. * @throws UnsupportedOperationException if the <tt>remove</tt> method is
  375. * not supported by the composited map containing the key
  376. */
  377. public Object remove(Object key) {
  378. for (int i = this.composite.length - 1; i >= 0; --i) {
  379. if (this.composite[i].containsKey(key)) {
  380. return this.composite[i].remove(key);
  381. }
  382. }
  383. return null;
  384. }
  385. /**
  386. * Returns the number of key-value mappings in this map. If the
  387. * map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
  388. * <tt>Integer.MAX_VALUE</tt>.
  389. *
  390. * @return the number of key-value mappings in this map.
  391. */
  392. public int size() {
  393. int size = 0;
  394. for (int i = this.composite.length - 1; i >= 0; --i) {
  395. size += this.composite[i].size();
  396. }
  397. return size;
  398. }
  399. /**
  400. * Returns a collection view of the values contained in this map. The
  401. * collection is backed by the map, so changes to the map are reflected in
  402. * the collection, and vice-versa. If the map is modified while an
  403. * iteration over the collection is in progress, the results of the
  404. * iteration are undefined. The collection supports element removal,
  405. * which removes the corresponding mapping from the map, via the
  406. * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
  407. * <tt>removeAll</tt>, <tt>retainAll</tt> and <tt>clear</tt> operations.
  408. * It does not support the add or <tt>addAll</tt> operations.
  409. *
  410. * @return a collection view of the values contained in this map.
  411. */
  412. public Collection values() {
  413. CompositeCollection keys = new CompositeCollection();
  414. for (int i = this.composite.length - 1; i >= 0; --i) {
  415. keys.addComposited(this.composite[i].values());
  416. }
  417. return keys;
  418. }
  419. /**
  420. * Checks if this Map equals another as per the Map specification.
  421. *
  422. * @param obj the object to compare to
  423. * @return true if the maps are equal
  424. */
  425. public boolean equals(Object obj) {
  426. if (obj instanceof Map) {
  427. Map map = (Map) obj;
  428. return (this.entrySet().equals(map.entrySet()));
  429. }
  430. return false;
  431. }
  432. /**
  433. * Gets a hash code for the Map as per the Map specification.
  434. */
  435. public int hashCode() {
  436. int code = 0;
  437. for (Iterator i = this.entrySet().iterator(); i.hasNext();) {
  438. code += i.next().hashCode();
  439. }
  440. return code;
  441. }
  442. /**
  443. * This interface allows definition for all of the indeterminate
  444. * mutators in a CompositeMap, as well as providing a hook for
  445. * callbacks on key collisions.
  446. */
  447. public static interface MapMutator {
  448. /**
  449. * Called when adding a new Composited Map results in a
  450. * key collision.
  451. *
  452. * @param composite the CompositeMap with the collision
  453. * @param existing the Map already in the composite which contains the
  454. * offending key
  455. * @param added the Map being added
  456. * @param intersect the intersection of the keysets of the existing and added maps
  457. */
  458. public void resolveCollision(
  459. CompositeMap composite, Map existing, Map added, Collection intersect);
  460. /**
  461. * Called when the CompositeMap.put() method is invoked.
  462. *
  463. * @param map the CompositeMap which is being modified
  464. * @param composited array of Maps in the CompositeMap being modified
  465. * @param key key with which the specified value is to be associated.
  466. * @param value value to be associated with the specified key.
  467. * @return previous value associated with specified key, or <tt>null</tt>
  468. * if there was no mapping for key. A <tt>null</tt> return can
  469. * also indicate that the map previously associated <tt>null</tt>
  470. * with the specified key, if the implementation supports
  471. * <tt>null</tt> values.
  472. *
  473. * @throws UnsupportedOperationException if not defined
  474. * @throws ClassCastException if the class of the specified key or value
  475. * prevents it from being stored in this map.
  476. * @throws IllegalArgumentException if some aspect of this key or value
  477. * prevents it from being stored in this map.
  478. * @throws NullPointerException this map does not permit <tt>null</tt>
  479. * keys or values, and the specified key or value is
  480. * <tt>null</tt>.
  481. */
  482. public Object put(CompositeMap map, Map[] composited, Object key, Object value);
  483. /**
  484. * Called when the CompositeMap.putAll() method is invoked.
  485. *
  486. * @param map the CompositeMap which is being modified
  487. * @param composited array of Maps in the CompositeMap being modified
  488. * @param mapToAdd Mappings to be stored in this CompositeMap
  489. *
  490. * @throws UnsupportedOperationException if not defined
  491. * @throws ClassCastException if the class of the specified key or value
  492. * prevents it from being stored in this map.
  493. * @throws IllegalArgumentException if some aspect of this key or value
  494. * prevents it from being stored in this map.
  495. * @throws NullPointerException this map does not permit <tt>null</tt>
  496. * keys or values, and the specified key or value is
  497. * <tt>null</tt>.
  498. */
  499. public void putAll(CompositeMap map, Map[] composited, Map mapToAdd);
  500. }
  501. }