/*******************************************************************************
* Copyright (c) 2009 MATERNA Information & Communications. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html. For further
* project-related information visit http://www.ws4d.org. The most recent
* version of the JMEDS framework can be obtained from
* http://sourceforge.net/projects/ws4d-javame.
******************************************************************************/
package org.ws4d.java.structures;
import java.util.NoSuchElementException;
import org.ws4d.java.util.WS4DIllegalStateException;
/**
* Class memorizes the order in which objects were inserted. Iterators of the
* sets returned by this map will have the same sequence. Inserting an already
* existing entry into the map won't change the order of the elements. Behavior
* can be extended so that accessing a entry of the map will move this entry to
* the end of the sequence.
*/
public class LinkedMap extends HashMap {
/**
* Accessing a map entry will move this entry at the end of the iterator
* sequence
*/
boolean accessOrdering = false;
/** head of list */
Entry header;
// -------------- CONSTRUCTORS -------------------------
/**
* Constructor.
*/
public LinkedMap() {
super();
}
/**
* Constructor. If accessOrdering is <code>true</code>, then accessing
* entries by the get()-method will moved it to the end of the iterator
* sequences.
*
* @param initialCapacity initial capacity to reserve
* @param accessOrdering
*/
public LinkedMap(boolean accessOrdering) {
super();
this.accessOrdering = accessOrdering;
}
/**
* Constructor with initial capacity to specify.
*
* @param initialCapacity
*/
public LinkedMap(int initialCapacity) {
super(initialCapacity);
}
/**
* Constructor with initial capacity to specify. If accessOrdering is
* <code>true</code>, then accessing entries by the get()-method will moved
* it to the end of the iterator sequences.
*
* @param initialCapacity initial capacity to reserve
* @param accessOrdering
*/
public LinkedMap(int initialCapacity, boolean accessOrdering) {
super(initialCapacity);
this.accessOrdering = accessOrdering;
}
/**
* Constructor will create a clone of the specified map.
*
* @param m map to clone.
*/
public LinkedMap(HashMap m) {
super(m);
}
// --------------------------- PRIVATE ---------------------------------
/**
* Initializes map, overrides and calls HashMap.init().
*/
void init(final int tableLength) {
super.init(tableLength);
header = new Entry();
}
protected HashMap.Entry addEntry(final int bucketIndex, final int hash, final Object key, final Object value) {
Entry newEntry = (Entry) super.addEntry(bucketIndex, hash, key, value);
header.addLinkedPrevious(newEntry);
return newEntry;
}
protected HashMap.Entry createEntry(int hash, Object key, Object value, HashMap.Entry next) {
return new Entry(hash, key, value, null, next);
}
protected void removeEntry(final int bucketIndex, final HashMap.Entry entry) {
super.removeEntry(bucketIndex, entry);
removeEntryFromLinking((LinkedMap.Entry) entry);
}
private void removeEntryFromLinking(Entry entry) {
entry.prevLinked.nextLinked = entry.nextLinked;
entry.nextLinked.prevLinked = entry.prevLinked;
}
// ------------------------------------------------------------------------
public void clear() {
super.clear();
header = new Entry();
}
public boolean containsValue(Object value) {
if (value != null) {
for (Entry entry = header.nextLinked; entry != header; entry = entry.nextLinked) {
if (value.equals(entry.value)) {
return true;
}
}
} else {
for (Entry entry = header.nextLinked; entry != header; entry = entry.nextLinked) {
if (entry.value == null) {
return true;
}
}
}
return false;
}
public Object get(Object key) {
int hash = 0;
int bucketIndex = 0;
if (key != null) {
hash = key.hashCode();
bucketIndex = hash & mask;
}
Entry entry = (Entry) getEntry(bucketIndex, hash, key);
if (entry != null) {
if (accessOrdering) doAccessOrdering(entry);
return entry.value;
}
return null;
}
public Object get(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
Entry resultEntry = getInternal(index);
if (accessOrdering) doAccessOrdering(resultEntry);
return resultEntry.value;
}
private void doAccessOrdering(Entry entry) {
removeEntryFromLinking(entry);
header.addLinkedPrevious(entry);
changes++;
}
private Entry getInternal(int index) {
Entry entry;
if (index < (size >> 1)) {
entry = header.nextLinked;
for (int i = 0; i < index; i++) {
entry = entry.nextLinked;
}
return entry;
} else {
entry = header;
for (int i = size; i > index; i--) {
entry = entry.prevLinked;
}
return entry;
}
}
public Object remove(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
return remove(getInternal(index));
}
public Object removeFirst() {
if (size == 0) {
throw new NoSuchElementException("LinkedMap is empty");
}
return remove(header.nextLinked.key);
}
public Object removeLast() {
if (size == 0) {
throw new NoSuchElementException("LinkedMap is empty");
}
return remove(header.prevLinked.key);
}
// ---------------------------- CREATE VIEWS -------------------------------
public Set entrySet() {
return (entrySet != null) ? entrySet : (entrySet = new EntrySet());
}
public Set keySet() {
return (keySet != null) ? keySet : (keySet = new KeySet());
}
public DataStructure values() {
return (values != null) ? values : (values = new Values());
}
// =============================== INNER CLASSES
// =============================
// ----------------------- CLASS ENTRY --------------------------------
protected static class Entry extends HashMap.Entry {
Entry prevLinked = null;
Entry nextLinked = null;
Entry(int hash, Object key, Object value, HashMap.Entry previous, HashMap.Entry next) {
super(hash, key, value, previous, next);
}
Entry() {
super(-1, null, null, null, null);
prevLinked = this;
nextLinked = this;
}
/**
* Adds Entry linked previous to this.
*
* @param newEntry
*/
private void addLinkedPrevious(Entry newEntry) {
newEntry.prevLinked = this.prevLinked;
newEntry.nextLinked = this;
this.prevLinked.nextLinked = newEntry;
this.prevLinked = newEntry;
}
}
// ----------------- CLASS ABSTRACT LINKED MAP ITERATOR
// ---------------------
private abstract class AbstractLinkedMapIterator implements Iterator {
Entry current;
Entry nextLinked;
int changesIt;
/**
* Constructor.
*/
AbstractLinkedMapIterator() {
changesIt = changes;
current = header;
nextLinked = header.nextLinked;
}
public void remove() {
checkChanges();
if (current == null || current == header) {
throw new WS4DIllegalStateException();
}
nextLinked = current.nextLinked;
LinkedMap.this.remove(current.key);
current = null;
changesIt = changes;
}
public boolean hasNext() {
return (nextLinked != header);
}
/**
* @return
*/
Entry nextEntry() {
checkChanges();
if (nextLinked == header) {
throw new NoSuchElementException();
}
current = nextLinked;
nextLinked = current.nextLinked;
return current;
}
protected final void checkChanges() {
if (changes != changesIt) {
throw new ConcurrentChangeException();
}
}
}
// -------------------- CLASS ENTRY SET -------------------------
protected class EntrySet extends HashMap.EntrySet {
public Iterator iterator() {
return new AbstractLinkedMapIterator() {
public Object next() {
return nextEntry();
}
};
}
}
// -------------------- CLASS KEY SET -------------------------
private class KeySet extends HashMap.KeySet {
public Iterator iterator() {
return new AbstractLinkedMapIterator() {
public Object next() {
return nextEntry().key;
}
};
}
}
// -------------------- CLASS VALUES -------------------------
private class Values extends HashMap.Values {
public Iterator iterator() {
return new AbstractLinkedMapIterator() {
public Object next() {
return nextEntry().value;
}
};
}
}
}