1. /*
  2. * Copyright 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.io.Serializable;
  18. import java.util.Collection;
  19. import java.util.Iterator;
  20. import java.util.Map;
  21. import java.util.Set;
  22. import org.apache.commons.collections.IterableMap;
  23. import org.apache.commons.collections.MapIterator;
  24. import org.apache.commons.collections.keyvalue.MultiKey;
  25. /**
  26. * A <code>Map</code> implementation that uses multiple keys to map the value.
  27. * <p>
  28. * This class is the most efficient way to uses multiple keys to map to a value.
  29. * The best way to use this class is via the additional map-style methods.
  30. * These provide <code>get</code>, <code>containsKey</code>, <code>put</code> and
  31. * <code>remove</code> for individual keys which operate without extra object creation.
  32. * <p>
  33. * The additional methods are the main interface of this map.
  34. * As such, you will not normally hold this map in a variable of type <code>Map</code>.
  35. * <p>
  36. * The normal map methods take in and return a {@link MultiKey}.
  37. * If you try to use <code>put()</code> with any other object type a
  38. * <code>ClassCastException</code> is thrown. If you try to use <code>null</code> as
  39. * the key in <code>put()</code> a <code>NullPointerException</code> is thrown.
  40. * <p>
  41. * This map is implemented as a decorator of a <code>AbstractHashedMap</code> which
  42. * enables extra behaviour to be added easily.
  43. * <ul>
  44. * <li><code>MultiKeyMap.decorate(new LinkedMap())</code> creates an ordered map.
  45. * <li><code>MultiKeyMap.decorate(new LRUMap())</code> creates an least recently used map.
  46. * <li><code>MultiKeyMap.decorate(new ReferenceMap())</code> creates a garbage collector sensitive map.
  47. * </ul>
  48. * Note that <code>IdentityMap</code> and <code>ReferenceIdentityMap</code> are unsuitable
  49. * for use as the key comparison would work on the whole MultiKey, not the elements within.
  50. * <p>
  51. * As an example, consider a least recently used cache that uses a String airline code
  52. * and a Locale to lookup the airline's name:
  53. * <pre>
  54. * private MultiKeyMap cache = MultiKeyMap.decorate(new LRUMap(50));
  55. *
  56. * public String getAirlineName(String code, String locale) {
  57. * String name = (String) cache.get(code, locale);
  58. * if (name == null) {
  59. * name = getAirlineNameFromDB(code, locale);
  60. * cache.put(code, locale, name);
  61. * }
  62. * return name;
  63. * }
  64. * </pre>
  65. *
  66. * @since Commons Collections 3.1
  67. * @version $Revision: 1.3 $ $Date: 2004/06/07 21:00:58 $
  68. *
  69. * @author Stephen Colebourne
  70. */
  71. public class MultiKeyMap
  72. implements IterableMap, Serializable {
  73. /** Serialisation version */
  74. private static final long serialVersionUID = -1788199231038721040L;
  75. /** The decorated map */
  76. protected final AbstractHashedMap map;
  77. //-----------------------------------------------------------------------
  78. /**
  79. * Decorates the specified map to add the MultiKeyMap API and fast query.
  80. * The map must not be null and must be empty.
  81. *
  82. * @param map the map to decorate, not null
  83. * @throws IllegalArgumentException if the map is null or not empty
  84. */
  85. public static MultiKeyMap decorate(AbstractHashedMap map) {
  86. if (map == null) {
  87. throw new IllegalArgumentException("Map must not be null");
  88. }
  89. if (map.size() > 0) {
  90. throw new IllegalArgumentException("Map must be empty");
  91. }
  92. return new MultiKeyMap(map);
  93. }
  94. //-----------------------------------------------------------------------
  95. /**
  96. * Constructs a new MultiKeyMap that decorates a <code>HashedMap</code>.
  97. */
  98. public MultiKeyMap() {
  99. super();
  100. map = new HashedMap();
  101. }
  102. /**
  103. * Constructor that decorates the specified map and is called from
  104. * {@link #decorate(AbstractHashedMap)}.
  105. * The map must not be null and should be empty or only contain valid keys.
  106. * This constructor performs no validation.
  107. *
  108. * @param map the map to decorate
  109. */
  110. protected MultiKeyMap(AbstractHashedMap map) {
  111. super();
  112. this.map = map;
  113. }
  114. //-----------------------------------------------------------------------
  115. /**
  116. * Gets the value mapped to the specified multi-key.
  117. *
  118. * @param key1 the first key
  119. * @param key2 the second key
  120. * @return the mapped value, null if no match
  121. */
  122. public Object get(Object key1, Object key2) {
  123. int hashCode = hash(key1, key2);
  124. AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(hashCode, map.data.length)];
  125. while (entry != null) {
  126. if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) {
  127. return entry.getValue();
  128. }
  129. entry = entry.next;
  130. }
  131. return null;
  132. }
  133. /**
  134. * Checks whether the map contains the specified multi-key.
  135. *
  136. * @param key1 the first key
  137. * @param key2 the second key
  138. * @return true if the map contains the key
  139. */
  140. public boolean containsKey(Object key1, Object key2) {
  141. int hashCode = hash(key1, key2);
  142. AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(hashCode, map.data.length)];
  143. while (entry != null) {
  144. if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) {
  145. return true;
  146. }
  147. entry = entry.next;
  148. }
  149. return false;
  150. }
  151. /**
  152. * Stores the value against the specified multi-key.
  153. *
  154. * @param key1 the first key
  155. * @param key2 the second key
  156. * @param value the value to store
  157. * @return the value previously mapped to this combined key, null if none
  158. */
  159. public Object put(Object key1, Object key2, Object value) {
  160. int hashCode = hash(key1, key2);
  161. int index = map.hashIndex(hashCode, map.data.length);
  162. AbstractHashedMap.HashEntry entry = map.data[index];
  163. while (entry != null) {
  164. if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) {
  165. Object oldValue = entry.getValue();
  166. map.updateEntry(entry, value);
  167. return oldValue;
  168. }
  169. entry = entry.next;
  170. }
  171. map.addMapping(index, hashCode, new MultiKey(key1, key2), value);
  172. return null;
  173. }
  174. /**
  175. * Removes the specified multi-key from this map.
  176. *
  177. * @param key1 the first key
  178. * @param key2 the second key
  179. * @return the value mapped to the removed key, null if key not in map
  180. */
  181. public Object remove(Object key1, Object key2) {
  182. int hashCode = hash(key1, key2);
  183. int index = map.hashIndex(hashCode, map.data.length);
  184. AbstractHashedMap.HashEntry entry = map.data[index];
  185. AbstractHashedMap.HashEntry previous = null;
  186. while (entry != null) {
  187. if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) {
  188. Object oldValue = entry.getValue();
  189. map.removeMapping(entry, index, previous);
  190. return oldValue;
  191. }
  192. previous = entry;
  193. entry = entry.next;
  194. }
  195. return null;
  196. }
  197. /**
  198. * Gets the hash code for the specified multi-key.
  199. *
  200. * @param key1 the first key
  201. * @param key2 the second key
  202. * @return the hash code
  203. */
  204. protected int hash(Object key1, Object key2) {
  205. int h = 0;
  206. if (key1 != null) {
  207. h ^= key1.hashCode();
  208. }
  209. if (key2 != null) {
  210. h ^= key2.hashCode();
  211. }
  212. h += ~(h << 9);
  213. h ^= (h >>> 14);
  214. h += (h << 4);
  215. h ^= (h >>> 10);
  216. return h;
  217. }
  218. /**
  219. * Is the key equal to the combined key.
  220. *
  221. * @param entry the entry to compare to
  222. * @param key1 the first key
  223. * @param key2 the second key
  224. * @return true if the key matches
  225. */
  226. protected boolean isEqualKey(AbstractHashedMap.HashEntry entry, Object key1, Object key2) {
  227. MultiKey multi = (MultiKey) entry.getKey();
  228. return
  229. multi.size() == 2 &&
  230. (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) &&
  231. (key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1)));
  232. }
  233. //-----------------------------------------------------------------------
  234. /**
  235. * Gets the value mapped to the specified multi-key.
  236. *
  237. * @param key1 the first key
  238. * @param key2 the second key
  239. * @param key3 the third key
  240. * @return the mapped value, null if no match
  241. */
  242. public Object get(Object key1, Object key2, Object key3) {
  243. int hashCode = hash(key1, key2, key3);
  244. AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(hashCode, map.data.length)];
  245. while (entry != null) {
  246. if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) {
  247. return entry.getValue();
  248. }
  249. entry = entry.next;
  250. }
  251. return null;
  252. }
  253. /**
  254. * Checks whether the map contains the specified multi-key.
  255. *
  256. * @param key1 the first key
  257. * @param key2 the second key
  258. * @param key3 the third key
  259. * @return true if the map contains the key
  260. */
  261. public boolean containsKey(Object key1, Object key2, Object key3) {
  262. int hashCode = hash(key1, key2, key3);
  263. AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(hashCode, map.data.length)];
  264. while (entry != null) {
  265. if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) {
  266. return true;
  267. }
  268. entry = entry.next;
  269. }
  270. return false;
  271. }
  272. /**
  273. * Stores the value against the specified multi-key.
  274. *
  275. * @param key1 the first key
  276. * @param key2 the second key
  277. * @param key3 the third key
  278. * @param value the value to store
  279. * @return the value previously mapped to this combined key, null if none
  280. */
  281. public Object put(Object key1, Object key2, Object key3, Object value) {
  282. int hashCode = hash(key1, key2, key3);
  283. int index = map.hashIndex(hashCode, map.data.length);
  284. AbstractHashedMap.HashEntry entry = map.data[index];
  285. while (entry != null) {
  286. if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) {
  287. Object oldValue = entry.getValue();
  288. map.updateEntry(entry, value);
  289. return oldValue;
  290. }
  291. entry = entry.next;
  292. }
  293. map.addMapping(index, hashCode, new MultiKey(key1, key2, key3), value);
  294. return null;
  295. }
  296. /**
  297. * Removes the specified multi-key from this map.
  298. *
  299. * @param key1 the first key
  300. * @param key2 the second key
  301. * @param key3 the third key
  302. * @return the value mapped to the removed key, null if key not in map
  303. */
  304. public Object remove(Object key1, Object key2, Object key3) {
  305. int hashCode = hash(key1, key2, key3);
  306. int index = map.hashIndex(hashCode, map.data.length);
  307. AbstractHashedMap.HashEntry entry = map.data[index];
  308. AbstractHashedMap.HashEntry previous = null;
  309. while (entry != null) {
  310. if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) {
  311. Object oldValue = entry.getValue();
  312. map.removeMapping(entry, index, previous);
  313. return oldValue;
  314. }
  315. previous = entry;
  316. entry = entry.next;
  317. }
  318. return null;
  319. }
  320. /**
  321. * Gets the hash code for the specified multi-key.
  322. *
  323. * @param key1 the first key
  324. * @param key2 the second key
  325. * @param key3 the third key
  326. * @return the hash code
  327. */
  328. protected int hash(Object key1, Object key2, Object key3) {
  329. int h = 0;
  330. if (key1 != null) {
  331. h ^= key1.hashCode();
  332. }
  333. if (key2 != null) {
  334. h ^= key2.hashCode();
  335. }
  336. if (key3 != null) {
  337. h ^= key3.hashCode();
  338. }
  339. h += ~(h << 9);
  340. h ^= (h >>> 14);
  341. h += (h << 4);
  342. h ^= (h >>> 10);
  343. return h;
  344. }
  345. /**
  346. * Is the key equal to the combined key.
  347. *
  348. * @param entry the entry to compare to
  349. * @param key1 the first key
  350. * @param key2 the second key
  351. * @param key3 the third key
  352. * @return true if the key matches
  353. */
  354. protected boolean isEqualKey(AbstractHashedMap.HashEntry entry, Object key1, Object key2, Object key3) {
  355. MultiKey multi = (MultiKey) entry.getKey();
  356. return
  357. multi.size() == 3 &&
  358. (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) &&
  359. (key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1))) &&
  360. (key3 == null ? multi.getKey(2) == null : key3.equals(multi.getKey(2)));
  361. }
  362. //-----------------------------------------------------------------------
  363. /**
  364. * Gets the value mapped to the specified multi-key.
  365. *
  366. * @param key1 the first key
  367. * @param key2 the second key
  368. * @param key3 the third key
  369. * @param key4 the fourth key
  370. * @return the mapped value, null if no match
  371. */
  372. public Object get(Object key1, Object key2, Object key3, Object key4) {
  373. int hashCode = hash(key1, key2, key3, key4);
  374. AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(hashCode, map.data.length)];
  375. while (entry != null) {
  376. if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) {
  377. return entry.getValue();
  378. }
  379. entry = entry.next;
  380. }
  381. return null;
  382. }
  383. /**
  384. * Checks whether the map contains the specified multi-key.
  385. *
  386. * @param key1 the first key
  387. * @param key2 the second key
  388. * @param key3 the third key
  389. * @param key4 the fourth key
  390. * @return true if the map contains the key
  391. */
  392. public boolean containsKey(Object key1, Object key2, Object key3, Object key4) {
  393. int hashCode = hash(key1, key2, key3, key4);
  394. AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(hashCode, map.data.length)];
  395. while (entry != null) {
  396. if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) {
  397. return true;
  398. }
  399. entry = entry.next;
  400. }
  401. return false;
  402. }
  403. /**
  404. * Stores the value against the specified multi-key.
  405. *
  406. * @param key1 the first key
  407. * @param key2 the second key
  408. * @param key3 the third key
  409. * @param key4 the fourth key
  410. * @param value the value to store
  411. * @return the value previously mapped to this combined key, null if none
  412. */
  413. public Object put(Object key1, Object key2, Object key3, Object key4, Object value) {
  414. int hashCode = hash(key1, key2, key3, key4);
  415. int index = map.hashIndex(hashCode, map.data.length);
  416. AbstractHashedMap.HashEntry entry = map.data[index];
  417. while (entry != null) {
  418. if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) {
  419. Object oldValue = entry.getValue();
  420. map.updateEntry(entry, value);
  421. return oldValue;
  422. }
  423. entry = entry.next;
  424. }
  425. map.addMapping(index, hashCode, new MultiKey(key1, key2, key3, key4), value);
  426. return null;
  427. }
  428. /**
  429. * Removes the specified multi-key from this map.
  430. *
  431. * @param key1 the first key
  432. * @param key2 the second key
  433. * @param key3 the third key
  434. * @param key4 the fourth key
  435. * @return the value mapped to the removed key, null if key not in map
  436. */
  437. public Object remove(Object key1, Object key2, Object key3, Object key4) {
  438. int hashCode = hash(key1, key2, key3, key4);
  439. int index = map.hashIndex(hashCode, map.data.length);
  440. AbstractHashedMap.HashEntry entry = map.data[index];
  441. AbstractHashedMap.HashEntry previous = null;
  442. while (entry != null) {
  443. if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) {
  444. Object oldValue = entry.getValue();
  445. map.removeMapping(entry, index, previous);
  446. return oldValue;
  447. }
  448. previous = entry;
  449. entry = entry.next;
  450. }
  451. return null;
  452. }
  453. /**
  454. * Gets the hash code for the specified multi-key.
  455. *
  456. * @param key1 the first key
  457. * @param key2 the second key
  458. * @param key3 the third key
  459. * @param key4 the fourth key
  460. * @return the hash code
  461. */
  462. protected int hash(Object key1, Object key2, Object key3, Object key4) {
  463. int h = 0;
  464. if (key1 != null) {
  465. h ^= key1.hashCode();
  466. }
  467. if (key2 != null) {
  468. h ^= key2.hashCode();
  469. }
  470. if (key3 != null) {
  471. h ^= key3.hashCode();
  472. }
  473. if (key4 != null) {
  474. h ^= key4.hashCode();
  475. }
  476. h += ~(h << 9);
  477. h ^= (h >>> 14);
  478. h += (h << 4);
  479. h ^= (h >>> 10);
  480. return h;
  481. }
  482. /**
  483. * Is the key equal to the combined key.
  484. *
  485. * @param entry the entry to compare to
  486. * @param key1 the first key
  487. * @param key2 the second key
  488. * @param key3 the third key
  489. * @param key4 the fourth key
  490. * @return true if the key matches
  491. */
  492. protected boolean isEqualKey(AbstractHashedMap.HashEntry entry, Object key1, Object key2, Object key3, Object key4) {
  493. MultiKey multi = (MultiKey) entry.getKey();
  494. return
  495. multi.size() == 4 &&
  496. (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) &&
  497. (key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1))) &&
  498. (key3 == null ? multi.getKey(2) == null : key3.equals(multi.getKey(2))) &&
  499. (key4 == null ? multi.getKey(3) == null : key4.equals(multi.getKey(3)));
  500. }
  501. //-----------------------------------------------------------------------
  502. /**
  503. * Gets the value mapped to the specified multi-key.
  504. *
  505. * @param key1 the first key
  506. * @param key2 the second key
  507. * @param key3 the third key
  508. * @param key4 the fourth key
  509. * @param key5 the fifth key
  510. * @return the mapped value, null if no match
  511. */
  512. public Object get(Object key1, Object key2, Object key3, Object key4, Object key5) {
  513. int hashCode = hash(key1, key2, key3, key4, key5);
  514. AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(hashCode, map.data.length)];
  515. while (entry != null) {
  516. if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) {
  517. return entry.getValue();
  518. }
  519. entry = entry.next;
  520. }
  521. return null;
  522. }
  523. /**
  524. * Checks whether the map contains the specified multi-key.
  525. *
  526. * @param key1 the first key
  527. * @param key2 the second key
  528. * @param key3 the third key
  529. * @param key4 the fourth key
  530. * @param key5 the fifth key
  531. * @return true if the map contains the key
  532. */
  533. public boolean containsKey(Object key1, Object key2, Object key3, Object key4, Object key5) {
  534. int hashCode = hash(key1, key2, key3, key4, key5);
  535. AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(hashCode, map.data.length)];
  536. while (entry != null) {
  537. if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) {
  538. return true;
  539. }
  540. entry = entry.next;
  541. }
  542. return false;
  543. }
  544. /**
  545. * Stores the value against the specified multi-key.
  546. *
  547. * @param key1 the first key
  548. * @param key2 the second key
  549. * @param key3 the third key
  550. * @param key4 the fourth key
  551. * @param key5 the fifth key
  552. * @param value the value to store
  553. * @return the value previously mapped to this combined key, null if none
  554. */
  555. public Object put(Object key1, Object key2, Object key3, Object key4, Object key5, Object value) {
  556. int hashCode = hash(key1, key2, key3, key4, key5);
  557. int index = map.hashIndex(hashCode, map.data.length);
  558. AbstractHashedMap.HashEntry entry = map.data[index];
  559. while (entry != null) {
  560. if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) {
  561. Object oldValue = entry.getValue();
  562. map.updateEntry(entry, value);
  563. return oldValue;
  564. }
  565. entry = entry.next;
  566. }
  567. map.addMapping(index, hashCode, new MultiKey(key1, key2, key3, key4, key5), value);
  568. return null;
  569. }
  570. /**
  571. * Removes the specified multi-key from this map.
  572. *
  573. * @param key1 the first key
  574. * @param key2 the second key
  575. * @param key3 the third key
  576. * @param key4 the fourth key
  577. * @param key5 the fifth key
  578. * @return the value mapped to the removed key, null if key not in map
  579. */
  580. public Object remove(Object key1, Object key2, Object key3, Object key4, Object key5) {
  581. int hashCode = hash(key1, key2, key3, key4, key5);
  582. int index = map.hashIndex(hashCode, map.data.length);
  583. AbstractHashedMap.HashEntry entry = map.data[index];
  584. AbstractHashedMap.HashEntry previous = null;
  585. while (entry != null) {
  586. if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) {
  587. Object oldValue = entry.getValue();
  588. map.removeMapping(entry, index, previous);
  589. return oldValue;
  590. }
  591. previous = entry;
  592. entry = entry.next;
  593. }
  594. return null;
  595. }
  596. /**
  597. * Gets the hash code for the specified multi-key.
  598. *
  599. * @param key1 the first key
  600. * @param key2 the second key
  601. * @param key3 the third key
  602. * @param key4 the fourth key
  603. * @param key5 the fifth key
  604. * @return the hash code
  605. */
  606. protected int hash(Object key1, Object key2, Object key3, Object key4, Object key5) {
  607. int h = 0;
  608. if (key1 != null) {
  609. h ^= key1.hashCode();
  610. }
  611. if (key2 != null) {
  612. h ^= key2.hashCode();
  613. }
  614. if (key3 != null) {
  615. h ^= key3.hashCode();
  616. }
  617. if (key4 != null) {
  618. h ^= key4.hashCode();
  619. }
  620. if (key5 != null) {
  621. h ^= key5.hashCode();
  622. }
  623. h += ~(h << 9);
  624. h ^= (h >>> 14);
  625. h += (h << 4);
  626. h ^= (h >>> 10);
  627. return h;
  628. }
  629. /**
  630. * Is the key equal to the combined key.
  631. *
  632. * @param entry the entry to compare to
  633. * @param key1 the first key
  634. * @param key2 the second key
  635. * @param key3 the third key
  636. * @param key4 the fourth key
  637. * @param key5 the fifth key
  638. * @return true if the key matches
  639. */
  640. protected boolean isEqualKey(AbstractHashedMap.HashEntry entry, Object key1, Object key2, Object key3, Object key4, Object key5) {
  641. MultiKey multi = (MultiKey) entry.getKey();
  642. return
  643. multi.size() == 5 &&
  644. (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) &&
  645. (key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1))) &&
  646. (key3 == null ? multi.getKey(2) == null : key3.equals(multi.getKey(2))) &&
  647. (key4 == null ? multi.getKey(3) == null : key4.equals(multi.getKey(3))) &&
  648. (key5 == null ? multi.getKey(4) == null : key5.equals(multi.getKey(4)));
  649. }
  650. //-----------------------------------------------------------------------
  651. /**
  652. * Removes all mappings where the first key is that specified.
  653. * <p>
  654. * This method removes all the mappings where the <code>MultiKey</code>
  655. * has one or more keys, and the first matches that specified.
  656. *
  657. * @param key1 the first key
  658. * @return true if any elements were removed
  659. */
  660. public boolean removeAll(Object key1) {
  661. boolean modified = false;
  662. MapIterator it = mapIterator();
  663. while (it.hasNext()) {
  664. MultiKey multi = (MultiKey) it.next();
  665. if (multi.size() >= 1 &&
  666. (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0)))) {
  667. it.remove();
  668. modified = true;
  669. }
  670. }
  671. return modified;
  672. }
  673. /**
  674. * Removes all mappings where the first two keys are those specified.
  675. * <p>
  676. * This method removes all the mappings where the <code>MultiKey</code>
  677. * has two or more keys, and the first two match those specified.
  678. *
  679. * @param key1 the first key
  680. * @param key2 the second key
  681. * @return true if any elements were removed
  682. */
  683. public boolean removeAll(Object key1, Object key2) {
  684. boolean modified = false;
  685. MapIterator it = mapIterator();
  686. while (it.hasNext()) {
  687. MultiKey multi = (MultiKey) it.next();
  688. if (multi.size() >= 2 &&
  689. (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) &&
  690. (key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1)))) {
  691. it.remove();
  692. modified = true;
  693. }
  694. }
  695. return modified;
  696. }
  697. /**
  698. * Removes all mappings where the first three keys are those specified.
  699. * <p>
  700. * This method removes all the mappings where the <code>MultiKey</code>
  701. * has three or more keys, and the first three match those specified.
  702. *
  703. * @param key1 the first key
  704. * @param key2 the second key
  705. * @param key3 the third key
  706. * @return true if any elements were removed
  707. */
  708. public boolean removeAll(Object key1, Object key2, Object key3) {
  709. boolean modified = false;
  710. MapIterator it = mapIterator();
  711. while (it.hasNext()) {
  712. MultiKey multi = (MultiKey) it.next();
  713. if (multi.size() >= 3 &&
  714. (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) &&
  715. (key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1))) &&
  716. (key3 == null ? multi.getKey(2) == null : key3.equals(multi.getKey(2)))) {
  717. it.remove();
  718. modified = true;
  719. }
  720. }
  721. return modified;
  722. }
  723. /**
  724. * Removes all mappings where the first four keys are those specified.
  725. * <p>
  726. * This method removes all the mappings where the <code>MultiKey</code>
  727. * has four or more keys, and the first four match those specified.
  728. *
  729. * @param key1 the first key
  730. * @param key2 the second key
  731. * @param key3 the third key
  732. * @param key4 the fourth key
  733. * @return true if any elements were removed
  734. */
  735. public boolean removeAll(Object key1, Object key2, Object key3, Object key4) {
  736. boolean modified = false;
  737. MapIterator it = mapIterator();
  738. while (it.hasNext()) {
  739. MultiKey multi = (MultiKey) it.next();
  740. if (multi.size() >= 4 &&
  741. (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) &&
  742. (key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1))) &&
  743. (key3 == null ? multi.getKey(2) == null : key3.equals(multi.getKey(2))) &&
  744. (key4 == null ? multi.getKey(3) == null : key4.equals(multi.getKey(3)))) {
  745. it.remove();
  746. modified = true;
  747. }
  748. }
  749. return modified;
  750. }
  751. //-----------------------------------------------------------------------
  752. /**
  753. * Check to ensure that input keys are valid MultiKey objects.
  754. *
  755. * @param key the key to check
  756. */
  757. protected void checkKey(Object key) {
  758. if (key == null) {
  759. throw new NullPointerException("Key must not be null");
  760. }
  761. if (key instanceof MultiKey == false) {
  762. throw new ClassCastException("Key must be a MultiKey");
  763. }
  764. }
  765. /**
  766. * Clones the map without cloning the keys or values.
  767. *
  768. * @return a shallow clone
  769. */
  770. public Object clone() {
  771. return new MultiKeyMap((AbstractHashedMap) map.clone());
  772. }
  773. /**
  774. * Puts the key and value into the map, where the key must be a non-null
  775. * MultiKey object.
  776. *
  777. * @param key the non-null MultiKey object
  778. * @param value the value to store
  779. * @return the previous value for the key
  780. * @throws NullPointerException if the key is null
  781. * @throws ClassCastException if the key is not a MultiKey
  782. */
  783. public Object put(Object key, Object value) {
  784. checkKey(key);
  785. return map.put(key, value);
  786. }
  787. /**
  788. * Puts all the keys and values into this map.
  789. * Each key must be non-null and a MultiKey object.
  790. *
  791. * @param key the non-null MultiKey object
  792. * @param value the value to store
  793. * @return the previous value for the key
  794. * @throws NullPointerException if the mapToCopy or any key within is null
  795. * @throws ClassCastException if any key is not a MultiKey
  796. */
  797. public void putAll(Map mapToCopy) {
  798. for (Iterator it = mapToCopy.keySet().iterator(); it.hasNext();) {
  799. Object key = it.next();
  800. checkKey(key);
  801. }
  802. map.putAll(mapToCopy);
  803. }
  804. //-----------------------------------------------------------------------
  805. public MapIterator mapIterator() {
  806. return map.mapIterator();
  807. }
  808. public int size() {
  809. return map.size();
  810. }
  811. public boolean isEmpty() {
  812. return map.isEmpty();
  813. }
  814. public boolean containsKey(Object key) {
  815. return map.containsKey(key);
  816. }
  817. public boolean containsValue(Object value) {
  818. return map.containsValue(value);
  819. }
  820. public Object get(Object key) {
  821. return map.get(key);
  822. }
  823. public Object remove(Object key) {
  824. return map.remove(key);
  825. }
  826. public void clear() {
  827. map.clear();
  828. }
  829. public Set keySet() {
  830. return map.keySet();
  831. }
  832. public Collection values() {
  833. return map.values();
  834. }
  835. public Set entrySet() {
  836. return map.entrySet();
  837. }
  838. public boolean equals(Object obj) {
  839. if (obj == this) {
  840. return true;
  841. }
  842. return map.equals(obj);
  843. }
  844. public int hashCode() {
  845. return map.hashCode();
  846. }
  847. public String toString() {
  848. return map.toString();
  849. }
  850. }