package permafrost.tundra.data; import com.wm.data.DataException; import com.wm.data.IData; import com.wm.data.IDataCursor; import com.wm.data.IDataFactory; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Wraps a Map in an IDataCursor interface. * * @param <K> The class of keys in the Map. * @param <V> The class of values in the Map. */ class MapCursor<K, V> implements IDataCursor { /** * Position value used when cursor is unpositioned. */ protected static final int UNPOSITIONED = -1; /** * The map the cursor iterates over. */ protected Map<K, V> map; /** * The key list used for cursor traversal. */ protected List<K> keys; /** * The current position of the traversal. */ protected int position; /** * The current key. */ protected K key; /** * Whether the current position needs to be recalculated. */ protected boolean dirty = false; /** * Constructs a new MapCursor. */ MapCursor(Map<K, V> map) { this(map, UNPOSITIONED); } /** * Constructs a new MapCursor. * * @param position The initial position of the cursor. */ MapCursor(Map<K, V> map, int position) { this.map = map; initialize(position); } /** * Initializes the state of the cursor. */ protected void initialize() { initialize(UNPOSITIONED); } /** * Initializes the state of the cursor. * * @param position The initial position of the cursor. */ protected void initialize(int position) { keys = new ArrayList<K>(map.keySet()); this.position = Math.max(UNPOSITIONED, position); } /** * Returns the current position of the cursor. * * @return The current position of the cursor. */ protected int getPosition() { if (dirty) { dirty = false; position = key == null ? UNPOSITIONED : keys.indexOf(key); } return position; } /** * Sets the current position of the cursor. * * @param position The new position of the cursor. * @return The new position of the cursor. */ protected int setPosition(int position) { this.position = Math.max(position, UNPOSITIONED); dirty = false; return this.position; } /** * Deletes the element at the cursor's current position. * * @return True if the element was deleted. */ public boolean delete() { int position = getPosition(); if (position <= UNPOSITIONED) { return false; } else { map.remove(keys.remove(position)); if (position > keys.size()) { setPosition(UNPOSITIONED); return false; } return true; } } /** * Destroys the cursor. */ public void destroy() { setPosition(UNPOSITIONED); } /** * Positions the cursor at the first element. * * @return True if the cursor was repositioned. */ public boolean first() { if (keys.size() > 0) { setPosition(0); return true; } else { setPosition(UNPOSITIONED); return false; } } /** * Positions the cursor at the first element with the given key. * * @param key The key to position the cursor at. * @return True if the cursor was repositioned. */ @SuppressWarnings("unchecked") public final boolean first(String key) { if (map.containsKey((K)key)) { this.key = (K)key; dirty = true; return true; } return false; } /** * Returns a clone of this cursor. * * @return A clone of this cursor. */ public IDataCursor getCursorClone() { return new MapCursor<K, V>(map, getPosition()); } /** * Returns the key of the element at the cursor's current position. * * @return The key of the element at the cursor's current position. */ public String getKey() { if (key != null) { return key.toString(); } else { int position = getPosition(); if (position <= UNPOSITIONED) { return null; } else { K key = keys.get(position); return key == null ? null : key.toString(); } } } /** * Not implemented; returns null. * * @return Null. */ public DataException getLastError() { return null; } /** * Returns the value of the element at the cursor's current position. * * @return The value of the element at the cursor's current position. */ public Object getValue() { if (key != null) { return map.get(key); } else { int position = getPosition(); if (position <= UNPOSITIONED) { return null; } else { return map.get(keys.get(position)); } } } /** * Returns true if the cursor has more elements yet to be traversed. * * @return True if the cursor has more elements yet to be traversed. */ public boolean hasMoreData() { int size = keys.size(); return size > 0 && (getPosition() + 1) < size; } /** * Not implemented; returns false. * * @return False. */ public boolean hasMoreErrors() { return false; } /** * Initializes the cursor. */ public void home() { initialize(); } /** * Inserts the given key value pair after the element at the cursor's current position. * * @param key The key to insert. * @param value The value to insert. */ @SuppressWarnings("unchecked") public void insertAfter(String key, Object value) { int position = setPosition(Math.min(getPosition() + 1, keys.size())); keys.add(position, (K)key); map.put((K)key, (V)value); } /** * Inserts the given key value pair before the element at the cursor's current position. * * @param key The key to insert. * @param value The value to insert. */ @SuppressWarnings("unchecked") public void insertBefore(String key, Object value) { int position = setPosition(Math.max(getPosition(), 0)); keys.add(position, (K)key); map.put((K)key, (V)value); } /** * Inserts a new IData document after the element at the cursor's current position. * * @param key The key to be inserted. * @return The newly created IData document that was inserted. */ public IData insertDataAfter(String key) { IData document = IDataFactory.create(); insertAfter(key, document); return document; } /** * Inserts a new IData document before the element at the cursor's current position. * * @param key The key to be inserted. * @return The newly created IData document that was inserted. */ public IData insertDataBefore(String key) { IData document = IDataFactory.create(); insertBefore(key, document); return document; } /** * Positions the cursor on the last element in the map. * * @return True if the cursor was repositioned. */ public boolean last() { int size = keys.size(); if (size > 0) { setPosition(size - 1); return true; } else { setPosition(UNPOSITIONED); return false; } } /** * Positions the cursor on the last occurrence of the given key. * * @param key The key to position the cursor at. * @return True if the cursor was repositioned. */ public boolean last(String key) { // because keys can only occur once in the map return first(key); } /** * Positions the cursor on the next element following the current position. * * @return True if the cursor was repositioned. */ public boolean next() { boolean hasMoreData = hasMoreData(); if (hasMoreData) { setPosition(getPosition() + 1); } else { setPosition(UNPOSITIONED); } return hasMoreData; } /** * Positions the cursor on the next element with the given key. * * @param key The key to position the cursor at. * @return True if the cursor was repositioned. */ public boolean next(String key) { // because keys can only occur once in the map return first(key); } /** * Positions the cursor on the previous element to the current position. * * @return True if the cursor was repositioned. */ public boolean previous() { int size = keys.size(); if (size > 0) { int position = getPosition(); if (position <= UNPOSITIONED) { setPosition(size - 1); return true; } else if (position == 0){ return false; } else { setPosition(position - 1); return true; } } else { return false; } } /** * Positions the cursor on the previous occurrence of the given key. * * @param key The key to position the cursor at. * @return True if the cursor was repositioned. */ public boolean previous(String key) { // because keys can only occur once in the map return first(key); } /** * Not implemented; does nothing. * * @param mode Not used. */ public void setErrorMode(int mode) { // do nothing } /** * Sets the key of the element at the cursor's current position. * * @param key The key to be set. */ @SuppressWarnings("unchecked") public void setKey(String key) { int position = getPosition(); if (position > UNPOSITIONED) { K oldKey = keys.set(position, (K)key); map.put((K)key, map.remove(oldKey)); } } /** * Sets the value of the element at the cursor's current position. * * @param value The value to be set. */ @SuppressWarnings("unchecked") public void setValue(Object value) { int position = getPosition(); if (position > UNPOSITIONED) map.put(keys.get(position), (V)value); } }