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.ConcurrentModificationException;
  18. import java.util.Iterator;
  19. import java.util.Map;
  20. import java.util.NoSuchElementException;
  21. import org.apache.commons.collections.MapIterator;
  22. import org.apache.commons.collections.OrderedIterator;
  23. import org.apache.commons.collections.OrderedMap;
  24. import org.apache.commons.collections.OrderedMapIterator;
  25. import org.apache.commons.collections.ResettableIterator;
  26. import org.apache.commons.collections.iterators.EmptyOrderedIterator;
  27. import org.apache.commons.collections.iterators.EmptyOrderedMapIterator;
  28. /**
  29. * An abstract implementation of a hash-based map that links entries to create an
  30. * ordered map and which provides numerous points for subclasses to override.
  31. * <p>
  32. * This class implements all the features necessary for a subclass linked
  33. * hash-based map. Key-value entries are stored in instances of the
  34. * <code>LinkEntry</code> class which can be overridden and replaced.
  35. * The iterators can similarly be replaced, without the need to replace the KeySet,
  36. * EntrySet and Values view classes.
  37. * <p>
  38. * Overridable methods are provided to change the default hashing behaviour, and
  39. * to change how entries are added to and removed from the map. Hopefully, all you
  40. * need for unusual subclasses is here.
  41. * <p>
  42. * This implementation maintains order by original insertion, but subclasses
  43. * may work differently. The <code>OrderedMap</code> interface is implemented
  44. * to provide access to bidirectional iteration and extra convenience methods.
  45. * <p>
  46. * The <code>orderedMapIterator()</code> method provides direct access to a
  47. * bidirectional iterator. The iterators from the other views can also be cast
  48. * to <code>OrderedIterator</code> if required.
  49. * <p>
  50. * All the available iterators can be reset back to the start by casting to
  51. * <code>ResettableIterator</code> and calling <code>reset()</code>.
  52. * <p>
  53. * The implementation is also designed to be subclassed, with lots of useful
  54. * methods exposed.
  55. *
  56. * @since Commons Collections 3.0
  57. * @version $Revision: 1.12 $ $Date: 2004/05/26 21:56:05 $
  58. *
  59. * @author java util LinkedHashMap
  60. * @author Stephen Colebourne
  61. */
  62. public class AbstractLinkedMap extends AbstractHashedMap implements OrderedMap {
  63. /** Header in the linked list */
  64. protected transient LinkEntry header;
  65. /**
  66. * Constructor only used in deserialization, do not use otherwise.
  67. */
  68. protected AbstractLinkedMap() {
  69. super();
  70. }
  71. /**
  72. * Constructor which performs no validation on the passed in parameters.
  73. *
  74. * @param initialCapacity the initial capacity, must be a power of two
  75. * @param loadFactor the load factor, must be > 0.0f and generally < 1.0f
  76. * @param threshold the threshold, must be sensible
  77. */
  78. protected AbstractLinkedMap(int initialCapacity, float loadFactor, int threshold) {
  79. super(initialCapacity, loadFactor, threshold);
  80. }
  81. /**
  82. * Constructs a new, empty map with the specified initial capacity.
  83. *
  84. * @param initialCapacity the initial capacity
  85. * @throws IllegalArgumentException if the initial capacity is less than one
  86. */
  87. protected AbstractLinkedMap(int initialCapacity) {
  88. super(initialCapacity);
  89. }
  90. /**
  91. * Constructs a new, empty map with the specified initial capacity and
  92. * load factor.
  93. *
  94. * @param initialCapacity the initial capacity
  95. * @param loadFactor the load factor
  96. * @throws IllegalArgumentException if the initial capacity is less than one
  97. * @throws IllegalArgumentException if the load factor is less than zero
  98. */
  99. protected AbstractLinkedMap(int initialCapacity, float loadFactor) {
  100. super(initialCapacity, loadFactor);
  101. }
  102. /**
  103. * Constructor copying elements from another map.
  104. *
  105. * @param map the map to copy
  106. * @throws NullPointerException if the map is null
  107. */
  108. protected AbstractLinkedMap(Map map) {
  109. super(map);
  110. }
  111. /**
  112. * Initialise this subclass during construction.
  113. */
  114. protected void init() {
  115. header = new LinkEntry(null, -1, null, null);
  116. header.before = header.after = header;
  117. }
  118. //-----------------------------------------------------------------------
  119. /**
  120. * Checks whether the map contains the specified value.
  121. *
  122. * @param value the value to search for
  123. * @return true if the map contains the value
  124. */
  125. public boolean containsValue(Object value) {
  126. // override uses faster iterator
  127. if (value == null) {
  128. for (LinkEntry entry = header.after; entry != header; entry = entry.after) {
  129. if (entry.getValue() == null) {
  130. return true;
  131. }
  132. }
  133. } else {
  134. for (LinkEntry entry = header.after; entry != header; entry = entry.after) {
  135. if (isEqualValue(value, entry.getValue())) {
  136. return true;
  137. }
  138. }
  139. }
  140. return false;
  141. }
  142. /**
  143. * Clears the map, resetting the size to zero and nullifying references
  144. * to avoid garbage collection issues.
  145. */
  146. public void clear() {
  147. // override to reset the linked list
  148. super.clear();
  149. header.before = header.after = header;
  150. }
  151. //-----------------------------------------------------------------------
  152. /**
  153. * Gets the first key in the map, which is the most recently inserted.
  154. *
  155. * @return the most recently inserted key
  156. */
  157. public Object firstKey() {
  158. if (size == 0) {
  159. throw new NoSuchElementException("Map is empty");
  160. }
  161. return header.after.getKey();
  162. }
  163. /**
  164. * Gets the last key in the map, which is the first inserted.
  165. *
  166. * @return the eldest key
  167. */
  168. public Object lastKey() {
  169. if (size == 0) {
  170. throw new NoSuchElementException("Map is empty");
  171. }
  172. return header.before.getKey();
  173. }
  174. /**
  175. * Gets the next key in sequence.
  176. *
  177. * @param key the key to get after
  178. * @return the next key
  179. */
  180. public Object nextKey(Object key) {
  181. LinkEntry entry = (LinkEntry) getEntry(key);
  182. return (entry == null || entry.after == header ? null : entry.after.getKey());
  183. }
  184. /**
  185. * Gets the previous key in sequence.
  186. *
  187. * @param key the key to get before
  188. * @return the previous key
  189. */
  190. public Object previousKey(Object key) {
  191. LinkEntry entry = (LinkEntry) getEntry(key);
  192. return (entry == null || entry.before == header ? null : entry.before.getKey());
  193. }
  194. //-----------------------------------------------------------------------
  195. /**
  196. * Gets the key at the specified index.
  197. *
  198. * @param index the index to retrieve
  199. * @return the key at the specified index
  200. * @throws IndexOutOfBoundsException if the index is invalid
  201. */
  202. protected LinkEntry getEntry(int index) {
  203. if (index < 0) {
  204. throw new IndexOutOfBoundsException("Index " + index + " is less than zero");
  205. }
  206. if (index >= size) {
  207. throw new IndexOutOfBoundsException("Index " + index + " is invalid for size " + size);
  208. }
  209. LinkEntry entry;
  210. if (index < (size / 2)) {
  211. // Search forwards
  212. entry = header.after;
  213. for (int currentIndex = 0; currentIndex < index; currentIndex++) {
  214. entry = entry.after;
  215. }
  216. } else {
  217. // Search backwards
  218. entry = header;
  219. for (int currentIndex = size; currentIndex > index; currentIndex--) {
  220. entry = entry.before;
  221. }
  222. }
  223. return entry;
  224. }
  225. /**
  226. * Adds an entry into this map, maintaining insertion order.
  227. * <p>
  228. * This implementation adds the entry to the data storage table and
  229. * to the end of the linked list.
  230. *
  231. * @param entry the entry to add
  232. * @param hashIndex the index into the data array to store at
  233. */
  234. protected void addEntry(HashEntry entry, int hashIndex) {
  235. LinkEntry link = (LinkEntry) entry;
  236. link.after = header;
  237. link.before = header.before;
  238. header.before.after = link;
  239. header.before = link;
  240. data[hashIndex] = entry;
  241. }
  242. /**
  243. * Creates an entry to store the data.
  244. * <p>
  245. * This implementation creates a new LinkEntry instance.
  246. *
  247. * @param next the next entry in sequence
  248. * @param hashCode the hash code to use
  249. * @param key the key to store
  250. * @param value the value to store
  251. * @return the newly created entry
  252. */
  253. protected HashEntry createEntry(HashEntry next, int hashCode, Object key, Object value) {
  254. return new LinkEntry(next, hashCode, key, value);
  255. }
  256. /**
  257. * Removes an entry from the map and the linked list.
  258. * <p>
  259. * This implementation removes the entry from the linked list chain, then
  260. * calls the superclass implementation.
  261. *
  262. * @param entry the entry to remove
  263. * @param hashIndex the index into the data structure
  264. * @param previous the previous entry in the chain
  265. */
  266. protected void removeEntry(HashEntry entry, int hashIndex, HashEntry previous) {
  267. LinkEntry link = (LinkEntry) entry;
  268. link.before.after = link.after;
  269. link.after.before = link.before;
  270. link.after = null;
  271. link.before = null;
  272. super.removeEntry(entry, hashIndex, previous);
  273. }
  274. //-----------------------------------------------------------------------
  275. /**
  276. * Gets the <code>before</code> field from a <code>LinkEntry</code>.
  277. * Used in subclasses that have no visibility of the field.
  278. *
  279. * @param entry the entry to query, must not be null
  280. * @return the <code>before</code> field of the entry
  281. * @throws NullPointerException if the entry is null
  282. * @since Commons Collections 3.1
  283. */
  284. protected LinkEntry entryBefore(LinkEntry entry) {
  285. return entry.before;
  286. }
  287. /**
  288. * Gets the <code>after</code> field from a <code>LinkEntry</code>.
  289. * Used in subclasses that have no visibility of the field.
  290. *
  291. * @param entry the entry to query, must not be null
  292. * @return the <code>after</code> field of the entry
  293. * @throws NullPointerException if the entry is null
  294. * @since Commons Collections 3.1
  295. */
  296. protected LinkEntry entryAfter(LinkEntry entry) {
  297. return entry.after;
  298. }
  299. //-----------------------------------------------------------------------
  300. /**
  301. * Gets an iterator over the map.
  302. * Changes made to the iterator affect this map.
  303. * <p>
  304. * A MapIterator returns the keys in the map. It also provides convenient
  305. * methods to get the key and value, and set the value.
  306. * It avoids the need to create an entrySet/keySet/values object.
  307. *
  308. * @return the map iterator
  309. */
  310. public MapIterator mapIterator() {
  311. if (size == 0) {
  312. return EmptyOrderedMapIterator.INSTANCE;
  313. }
  314. return new LinkMapIterator(this);
  315. }
  316. /**
  317. * Gets a bidirectional iterator over the map.
  318. * Changes made to the iterator affect this map.
  319. * <p>
  320. * A MapIterator returns the keys in the map. It also provides convenient
  321. * methods to get the key and value, and set the value.
  322. * It avoids the need to create an entrySet/keySet/values object.
  323. *
  324. * @return the map iterator
  325. */
  326. public OrderedMapIterator orderedMapIterator() {
  327. if (size == 0) {
  328. return EmptyOrderedMapIterator.INSTANCE;
  329. }
  330. return new LinkMapIterator(this);
  331. }
  332. /**
  333. * MapIterator implementation.
  334. */
  335. protected static class LinkMapIterator extends LinkIterator implements OrderedMapIterator {
  336. protected LinkMapIterator(AbstractLinkedMap parent) {
  337. super(parent);
  338. }
  339. public Object next() {
  340. return super.nextEntry().getKey();
  341. }
  342. public Object previous() {
  343. return super.previousEntry().getKey();
  344. }
  345. public Object getKey() {
  346. HashEntry current = currentEntry();
  347. if (current == null) {
  348. throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
  349. }
  350. return current.getKey();
  351. }
  352. public Object getValue() {
  353. HashEntry current = currentEntry();
  354. if (current == null) {
  355. throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
  356. }
  357. return current.getValue();
  358. }
  359. public Object setValue(Object value) {
  360. HashEntry current = currentEntry();
  361. if (current == null) {
  362. throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
  363. }
  364. return current.setValue(value);
  365. }
  366. }
  367. //-----------------------------------------------------------------------
  368. /**
  369. * Creates an entry set iterator.
  370. * Subclasses can override this to return iterators with different properties.
  371. *
  372. * @return the entrySet iterator
  373. */
  374. protected Iterator createEntrySetIterator() {
  375. if (size() == 0) {
  376. return EmptyOrderedIterator.INSTANCE;
  377. }
  378. return new EntrySetIterator(this);
  379. }
  380. /**
  381. * EntrySet iterator.
  382. */
  383. protected static class EntrySetIterator extends LinkIterator {
  384. protected EntrySetIterator(AbstractLinkedMap parent) {
  385. super(parent);
  386. }
  387. public Object next() {
  388. return super.nextEntry();
  389. }
  390. public Object previous() {
  391. return super.previousEntry();
  392. }
  393. }
  394. //-----------------------------------------------------------------------
  395. /**
  396. * Creates a key set iterator.
  397. * Subclasses can override this to return iterators with different properties.
  398. *
  399. * @return the keySet iterator
  400. */
  401. protected Iterator createKeySetIterator() {
  402. if (size() == 0) {
  403. return EmptyOrderedIterator.INSTANCE;
  404. }
  405. return new KeySetIterator(this);
  406. }
  407. /**
  408. * KeySet iterator.
  409. */
  410. protected static class KeySetIterator extends EntrySetIterator {
  411. protected KeySetIterator(AbstractLinkedMap parent) {
  412. super(parent);
  413. }
  414. public Object next() {
  415. return super.nextEntry().getKey();
  416. }
  417. public Object previous() {
  418. return super.previousEntry().getKey();
  419. }
  420. }
  421. //-----------------------------------------------------------------------
  422. /**
  423. * Creates a values iterator.
  424. * Subclasses can override this to return iterators with different properties.
  425. *
  426. * @return the values iterator
  427. */
  428. protected Iterator createValuesIterator() {
  429. if (size() == 0) {
  430. return EmptyOrderedIterator.INSTANCE;
  431. }
  432. return new ValuesIterator(this);
  433. }
  434. /**
  435. * Values iterator.
  436. */
  437. protected static class ValuesIterator extends LinkIterator {
  438. protected ValuesIterator(AbstractLinkedMap parent) {
  439. super(parent);
  440. }
  441. public Object next() {
  442. return super.nextEntry().getValue();
  443. }
  444. public Object previous() {
  445. return super.previousEntry().getValue();
  446. }
  447. }
  448. //-----------------------------------------------------------------------
  449. /**
  450. * LinkEntry that stores the data.
  451. * <p>
  452. * If you subclass <code>AbstractLinkedMap</code> but not <code>LinkEntry</code>
  453. * then you will not be able to access the protected fields.
  454. * The <code>entryXxx()</code> methods on <code>AbstractLinkedMap</code> exist
  455. * to provide the necessary access.
  456. */
  457. protected static class LinkEntry extends HashEntry {
  458. /** The entry before this one in the order */
  459. protected LinkEntry before;
  460. /** The entry after this one in the order */
  461. protected LinkEntry after;
  462. /**
  463. * Constructs a new entry.
  464. *
  465. * @param next the next entry in the hash bucket sequence
  466. * @param hashCode the hash code
  467. * @param key the key
  468. * @param value the value
  469. */
  470. protected LinkEntry(HashEntry next, int hashCode, Object key, Object value) {
  471. super(next, hashCode, key, value);
  472. }
  473. }
  474. /**
  475. * Base Iterator that iterates in link order.
  476. */
  477. protected static abstract class LinkIterator
  478. implements OrderedIterator, ResettableIterator {
  479. /** The parent map */
  480. protected final AbstractLinkedMap parent;
  481. /** The current (last returned) entry */
  482. protected LinkEntry last;
  483. /** The next entry */
  484. protected LinkEntry next;
  485. /** The modification count expected */
  486. protected int expectedModCount;
  487. protected LinkIterator(AbstractLinkedMap parent) {
  488. super();
  489. this.parent = parent;
  490. this.next = parent.header.after;
  491. this.expectedModCount = parent.modCount;
  492. }
  493. public boolean hasNext() {
  494. return (next != parent.header);
  495. }
  496. public boolean hasPrevious() {
  497. return (next.before != parent.header);
  498. }
  499. protected LinkEntry nextEntry() {
  500. if (parent.modCount != expectedModCount) {
  501. throw new ConcurrentModificationException();
  502. }
  503. if (next == parent.header) {
  504. throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY);
  505. }
  506. last = next;
  507. next = next.after;
  508. return last;
  509. }
  510. protected LinkEntry previousEntry() {
  511. if (parent.modCount != expectedModCount) {
  512. throw new ConcurrentModificationException();
  513. }
  514. LinkEntry previous = next.before;
  515. if (previous == parent.header) {
  516. throw new NoSuchElementException(AbstractHashedMap.NO_PREVIOUS_ENTRY);
  517. }
  518. next = previous;
  519. last = previous;
  520. return last;
  521. }
  522. protected LinkEntry currentEntry() {
  523. return last;
  524. }
  525. public void remove() {
  526. if (last == null) {
  527. throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID);
  528. }
  529. if (parent.modCount != expectedModCount) {
  530. throw new ConcurrentModificationException();
  531. }
  532. parent.remove(last.getKey());
  533. last = null;
  534. expectedModCount = parent.modCount;
  535. }
  536. public void reset() {
  537. last = null;
  538. next = parent.header.after;
  539. }
  540. public String toString() {
  541. if (last != null) {
  542. return "Iterator[" + last.getKey() + "=" + last.getValue() + "]";
  543. } else {
  544. return "Iterator[]";
  545. }
  546. }
  547. }
  548. }