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.io.IOException;
  18. import java.io.ObjectInputStream;
  19. import java.io.ObjectOutputStream;
  20. import java.io.Serializable;
  21. import java.util.AbstractCollection;
  22. import java.util.AbstractSet;
  23. import java.util.ArrayList;
  24. import java.util.Collection;
  25. import java.util.HashMap;
  26. import java.util.Iterator;
  27. import java.util.List;
  28. import java.util.ListIterator;
  29. import java.util.Map;
  30. import java.util.NoSuchElementException;
  31. import java.util.Set;
  32. import org.apache.commons.collections.MapIterator;
  33. import org.apache.commons.collections.OrderedMap;
  34. import org.apache.commons.collections.OrderedMapIterator;
  35. import org.apache.commons.collections.ResettableIterator;
  36. import org.apache.commons.collections.iterators.AbstractIteratorDecorator;
  37. import org.apache.commons.collections.keyvalue.AbstractMapEntry;
  38. import org.apache.commons.collections.list.UnmodifiableList;
  39. /**
  40. * Decorates a <code>Map</code> to ensure that the order of addition is retained
  41. * using a <code>List</code> to maintain order.
  42. * <p>
  43. * The order will be used via the iterators and toArray methods on the views.
  44. * The order is also returned by the <code>MapIterator</code>.
  45. * The <code>orderedMapIterator()</code> method accesses an iterator that can
  46. * iterate both forwards and backwards through the map.
  47. * In addition, non-interface methods are provided to access the map by index.
  48. * <p>
  49. * If an object is added to the Map for a second time, it will remain in the
  50. * original position in the iteration.
  51. * <p>
  52. * This class is Serializable from Commons Collections 3.1.
  53. *
  54. * @since Commons Collections 3.0
  55. * @version $Revision: 1.16 $ $Date: 2004/06/07 21:51:39 $
  56. *
  57. * @author Henri Yandell
  58. * @author Stephen Colebourne
  59. */
  60. public class ListOrderedMap
  61. extends AbstractMapDecorator
  62. implements OrderedMap, Serializable {
  63. /** Serialization version */
  64. private static final long serialVersionUID = 2728177751851003750L;
  65. /** Internal list to hold the sequence of objects */
  66. protected final List insertOrder = new ArrayList();
  67. /**
  68. * Factory method to create an ordered map.
  69. * <p>
  70. * An <code>ArrayList</code> is used to retain order.
  71. *
  72. * @param map the map to decorate, must not be null
  73. * @throws IllegalArgumentException if map is null
  74. */
  75. public static OrderedMap decorate(Map map) {
  76. return new ListOrderedMap(map);
  77. }
  78. //-----------------------------------------------------------------------
  79. /**
  80. * Constructs a new empty <code>ListOrderedMap</code> that decorates
  81. * a <code>HashMap</code>.
  82. *
  83. * @since Commons Collections 3.1
  84. */
  85. public ListOrderedMap() {
  86. this(new HashMap());
  87. }
  88. /**
  89. * Constructor that wraps (not copies).
  90. *
  91. * @param map the map to decorate, must not be null
  92. * @throws IllegalArgumentException if map is null
  93. */
  94. protected ListOrderedMap(Map map) {
  95. super(map);
  96. insertOrder.addAll(getMap().keySet());
  97. }
  98. //-----------------------------------------------------------------------
  99. /**
  100. * Write the map out using a custom routine.
  101. *
  102. * @param out the output stream
  103. * @throws IOException
  104. * @since Commons Collections 3.1
  105. */
  106. private void writeObject(ObjectOutputStream out) throws IOException {
  107. out.defaultWriteObject();
  108. out.writeObject(map);
  109. }
  110. /**
  111. * Read the map in using a custom routine.
  112. *
  113. * @param in the input stream
  114. * @throws IOException
  115. * @throws ClassNotFoundException
  116. * @since Commons Collections 3.1
  117. */
  118. private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
  119. in.defaultReadObject();
  120. map = (Map) in.readObject();
  121. }
  122. // Implement OrderedMap
  123. //-----------------------------------------------------------------------
  124. public MapIterator mapIterator() {
  125. return orderedMapIterator();
  126. }
  127. public OrderedMapIterator orderedMapIterator() {
  128. return new ListOrderedMapIterator(this);
  129. }
  130. /**
  131. * Gets the first key in this map by insert order.
  132. *
  133. * @return the first key currently in this map
  134. * @throws NoSuchElementException if this map is empty
  135. */
  136. public Object firstKey() {
  137. if (size() == 0) {
  138. throw new NoSuchElementException("Map is empty");
  139. }
  140. return insertOrder.get(0);
  141. }
  142. /**
  143. * Gets the last key in this map by insert order.
  144. *
  145. * @return the last key currently in this map
  146. * @throws NoSuchElementException if this map is empty
  147. */
  148. public Object lastKey() {
  149. if (size() == 0) {
  150. throw new NoSuchElementException("Map is empty");
  151. }
  152. return insertOrder.get(size() - 1);
  153. }
  154. /**
  155. * Gets the next key to the one specified using insert order.
  156. * This method performs a list search to find the key and is O(n).
  157. *
  158. * @param key the key to find previous for
  159. * @return the next key, null if no match or at start
  160. */
  161. public Object nextKey(Object key) {
  162. int index = insertOrder.indexOf(key);
  163. if (index >= 0 && index < size() - 1) {
  164. return insertOrder.get(index + 1);
  165. }
  166. return null;
  167. }
  168. /**
  169. * Gets the previous key to the one specified using insert order.
  170. * This method performs a list search to find the key and is O(n).
  171. *
  172. * @param key the key to find previous for
  173. * @return the previous key, null if no match or at start
  174. */
  175. public Object previousKey(Object key) {
  176. int index = insertOrder.indexOf(key);
  177. if (index > 0) {
  178. return insertOrder.get(index - 1);
  179. }
  180. return null;
  181. }
  182. //-----------------------------------------------------------------------
  183. public Object put(Object key, Object value) {
  184. if (getMap().containsKey(key)) {
  185. // re-adding doesn't change order
  186. return getMap().put(key, value);
  187. } else {
  188. // first add, so add to both map and list
  189. Object result = getMap().put(key, value);
  190. insertOrder.add(key);
  191. return result;
  192. }
  193. }
  194. public void putAll(Map map) {
  195. for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
  196. Map.Entry entry = (Map.Entry) it.next();
  197. put(entry.getKey(), entry.getValue());
  198. }
  199. }
  200. public Object remove(Object key) {
  201. Object result = getMap().remove(key);
  202. insertOrder.remove(key);
  203. return result;
  204. }
  205. public void clear() {
  206. getMap().clear();
  207. insertOrder.clear();
  208. }
  209. //-----------------------------------------------------------------------
  210. public Set keySet() {
  211. return new KeySetView(this);
  212. }
  213. public Collection values() {
  214. return new ValuesView(this);
  215. }
  216. public Set entrySet() {
  217. return new EntrySetView(this, this.insertOrder);
  218. }
  219. //-----------------------------------------------------------------------
  220. /**
  221. * Returns the Map as a string.
  222. *
  223. * @return the Map as a String
  224. */
  225. public String toString() {
  226. if (isEmpty()) {
  227. return "{}";
  228. }
  229. StringBuffer buf = new StringBuffer();
  230. buf.append('{');
  231. boolean first = true;
  232. Iterator it = entrySet().iterator();
  233. while (it.hasNext()) {
  234. Map.Entry entry = (Map.Entry) it.next();
  235. Object key = entry.getKey();
  236. Object value = entry.getValue();
  237. if (first) {
  238. first = false;
  239. } else {
  240. buf.append(", ");
  241. }
  242. buf.append(key == this ? "(this Map)" : key);
  243. buf.append('=');
  244. buf.append(value == this ? "(this Map)" : value);
  245. }
  246. buf.append('}');
  247. return buf.toString();
  248. }
  249. //-----------------------------------------------------------------------
  250. /**
  251. * Gets the key at the specified index.
  252. *
  253. * @param index the index to retrieve
  254. * @return the key at the specified index
  255. * @throws IndexOutOfBoundsException if the index is invalid
  256. */
  257. public Object get(int index) {
  258. return insertOrder.get(index);
  259. }
  260. /**
  261. * Gets the value at the specified index.
  262. *
  263. * @param index the index to retrieve
  264. * @return the key at the specified index
  265. * @throws IndexOutOfBoundsException if the index is invalid
  266. */
  267. public Object getValue(int index) {
  268. return get(insertOrder.get(index));
  269. }
  270. /**
  271. * Gets the index of the specified key.
  272. *
  273. * @param key the key to find the index of
  274. * @return the index, or -1 if not found
  275. */
  276. public int indexOf(Object key) {
  277. return insertOrder.indexOf(key);
  278. }
  279. /**
  280. * Removes the element at the specified index.
  281. *
  282. * @param index the index of the object to remove
  283. * @return the previous value corresponding the <code>key</code>,
  284. * or <code>null</code> if none existed
  285. * @throws IndexOutOfBoundsException if the index is invalid
  286. */
  287. public Object remove(int index) {
  288. return remove(get(index));
  289. }
  290. /**
  291. * Gets an unmodifiable List view of the keys which changes as the map changes.
  292. * <p>
  293. * The returned list is unmodifiable because changes to the values of
  294. * the list (using {@link java.util.ListIterator#set(Object)}) will
  295. * effectively remove the value from the list and reinsert that value at
  296. * the end of the list, which is an unexpected side effect of changing the
  297. * value of a list. This occurs because changing the key, changes when the
  298. * mapping is added to the map and thus where it appears in the list.
  299. * <p>
  300. * An alternative to this method is to use {@link #keySet()}.
  301. *
  302. * @see #keySet()
  303. * @return The ordered list of keys.
  304. */
  305. public List asList() {
  306. return UnmodifiableList.decorate(insertOrder);
  307. }
  308. //-----------------------------------------------------------------------
  309. static class ValuesView extends AbstractCollection {
  310. private final ListOrderedMap parent;
  311. ValuesView(ListOrderedMap parent) {
  312. super();
  313. this.parent = parent;
  314. }
  315. public int size() {
  316. return this.parent.size();
  317. }
  318. public boolean contains(Object value) {
  319. return this.parent.containsValue(value);
  320. }
  321. public void clear() {
  322. this.parent.clear();
  323. }
  324. public Iterator iterator() {
  325. return new AbstractIteratorDecorator(parent.entrySet().iterator()) {
  326. public Object next() {
  327. return ((Map.Entry) iterator.next()).getValue();
  328. }
  329. };
  330. }
  331. }
  332. //-----------------------------------------------------------------------
  333. static class KeySetView extends AbstractSet {
  334. private final ListOrderedMap parent;
  335. KeySetView(ListOrderedMap parent) {
  336. super();
  337. this.parent = parent;
  338. }
  339. public int size() {
  340. return this.parent.size();
  341. }
  342. public boolean contains(Object value) {
  343. return this.parent.containsKey(value);
  344. }
  345. public void clear() {
  346. this.parent.clear();
  347. }
  348. public Iterator iterator() {
  349. return new AbstractIteratorDecorator(parent.entrySet().iterator()) {
  350. public Object next() {
  351. return ((Map.Entry) super.next()).getKey();
  352. }
  353. };
  354. }
  355. }
  356. //-----------------------------------------------------------------------
  357. static class EntrySetView extends AbstractSet {
  358. private final ListOrderedMap parent;
  359. private final List insertOrder;
  360. private Set entrySet;
  361. public EntrySetView(ListOrderedMap parent, List insertOrder) {
  362. super();
  363. this.parent = parent;
  364. this.insertOrder = insertOrder;
  365. }
  366. private Set getEntrySet() {
  367. if (entrySet == null) {
  368. entrySet = parent.getMap().entrySet();
  369. }
  370. return entrySet;
  371. }
  372. public int size() {
  373. return this.parent.size();
  374. }
  375. public boolean isEmpty() {
  376. return this.parent.isEmpty();
  377. }
  378. public boolean contains(Object obj) {
  379. return getEntrySet().contains(obj);
  380. }
  381. public boolean containsAll(Collection coll) {
  382. return getEntrySet().containsAll(coll);
  383. }
  384. public boolean remove(Object obj) {
  385. if (obj instanceof Map.Entry == false) {
  386. return false;
  387. }
  388. if (getEntrySet().contains(obj)) {
  389. Object key = ((Map.Entry) obj).getKey();
  390. parent.remove(key);
  391. return true;
  392. }
  393. return false;
  394. }
  395. public void clear() {
  396. this.parent.clear();
  397. }
  398. public boolean equals(Object obj) {
  399. if (obj == this) {
  400. return true;
  401. }
  402. return getEntrySet().equals(obj);
  403. }
  404. public int hashCode() {
  405. return getEntrySet().hashCode();
  406. }
  407. public String toString() {
  408. return getEntrySet().toString();
  409. }
  410. public Iterator iterator() {
  411. return new ListOrderedIterator(parent, insertOrder);
  412. }
  413. }
  414. //-----------------------------------------------------------------------
  415. static class ListOrderedIterator extends AbstractIteratorDecorator {
  416. private final ListOrderedMap parent;
  417. private Object last = null;
  418. ListOrderedIterator(ListOrderedMap parent, List insertOrder) {
  419. super(insertOrder.iterator());
  420. this.parent = parent;
  421. }
  422. public Object next() {
  423. last = super.next();
  424. return new ListOrderedMapEntry(parent, last);
  425. }
  426. public void remove() {
  427. super.remove();
  428. parent.getMap().remove(last);
  429. }
  430. }
  431. //-----------------------------------------------------------------------
  432. static class ListOrderedMapEntry extends AbstractMapEntry {
  433. private final ListOrderedMap parent;
  434. ListOrderedMapEntry(ListOrderedMap parent, Object key) {
  435. super(key, null);
  436. this.parent = parent;
  437. }
  438. public Object getValue() {
  439. return parent.get(key);
  440. }
  441. public Object setValue(Object value) {
  442. return parent.getMap().put(key, value);
  443. }
  444. }
  445. //-----------------------------------------------------------------------
  446. static class ListOrderedMapIterator implements OrderedMapIterator, ResettableIterator {
  447. private final ListOrderedMap parent;
  448. private ListIterator iterator;
  449. private Object last = null;
  450. private boolean readable = false;
  451. ListOrderedMapIterator(ListOrderedMap parent) {
  452. super();
  453. this.parent = parent;
  454. this.iterator = parent.insertOrder.listIterator();
  455. }
  456. public boolean hasNext() {
  457. return iterator.hasNext();
  458. }
  459. public Object next() {
  460. last = iterator.next();
  461. readable = true;
  462. return last;
  463. }
  464. public boolean hasPrevious() {
  465. return iterator.hasPrevious();
  466. }
  467. public Object previous() {
  468. last = iterator.previous();
  469. readable = true;
  470. return last;
  471. }
  472. public void remove() {
  473. if (readable == false) {
  474. throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID);
  475. }
  476. iterator.remove();
  477. parent.map.remove(last);
  478. readable = false;
  479. }
  480. public Object getKey() {
  481. if (readable == false) {
  482. throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
  483. }
  484. return last;
  485. }
  486. public Object getValue() {
  487. if (readable == false) {
  488. throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
  489. }
  490. return parent.get(last);
  491. }
  492. public Object setValue(Object value) {
  493. if (readable == false) {
  494. throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
  495. }
  496. return parent.map.put(last, value);
  497. }
  498. public void reset() {
  499. iterator = parent.insertOrder.listIterator();
  500. last = null;
  501. readable = false;
  502. }
  503. public String toString() {
  504. if (readable == true) {
  505. return "Iterator[" + getKey() + "=" + getValue() + "]";
  506. } else {
  507. return "Iterator[]";
  508. }
  509. }
  510. }
  511. }