/*
* regain/Thumbnailer - A file search engine providing plenty of formats (Plugin)
* Copyright (C) 2011 Come_IN Computerclubs (University of Siegen)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Contact: Come_IN-Team <come_in-team@listserv.uni-siegen.de>
*/
package de.uni_siegen.wineme.come_in.thumbnailer.util;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* Hashtable that can contain several entries per key.
* (Helper Class)
*
* Contract:
* <li>It is possible to put several identical key-value pairs (i.e. where key and value is equal)
* <li>entrySet is not supported. Instead, it can be iterated over all entries.
*
* @param <K> Key
* @param <V> Value
*/
public class ChainedHashMap<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>> {
private static final int DEFAULT_HASHTABLE_SIZE = 20;
private static final int DEFAULT_LIST_SIZE = 10;
private int listSize;
HashMap<K, List<V>> hashtable;
int size;
public ChainedHashMap()
{
this(DEFAULT_HASHTABLE_SIZE);
}
public ChainedHashMap(int hashtableSize) {
this(hashtableSize, DEFAULT_LIST_SIZE);
}
public ChainedHashMap(int hashtableSize, int chainSize) {
hashtable = new HashMap<K, List<V>>(hashtableSize);
listSize = chainSize;
size = 0;
}
public ChainedHashMap(Map<? extends K, ? extends V> map)
{
this();
if (map instanceof ChainedHashMap)
{
// Copy-constructor
ChainedHashMap<? extends K, ? extends V> hashtable = (ChainedHashMap<? extends K, ? extends V>) map;
for (K key : hashtable.keySet())
{
for (V value: hashtable.getList(key))
{
put(key, value);
}
}
}
else
putAll(map);
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public boolean containsKey(Object key) {
return hashtable.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
if (isEmpty())
return false;
Collection<List<V>> elements = hashtable.values();
for (List<V> list: elements)
{
if (list.contains(value))
return true;
}
return false;
}
@Override
/**
* Get first of the linked objects by this key.
*/
public V get(Object key) {
List<V> list = hashtable.get(key);
if (list == null)
return null;
else
return list.get(0);
}
/**
* Get all objects linked by this key
* as an Iterable usable an foreach loop.
*
* @param key
* @return Iterable
* @throws NullPointerException (if key null)
*/
public Iterable<V> getIterable(Object key) {
final List<V> list = hashtable.get(key);
if (list == null)
{
// Empty Iterator
return new Iterable<V>() {
public Iterator<V> iterator() {
return new Iterator<V>() {
public boolean hasNext() { return false; }
public V next() { throw new NoSuchElementException("Empty"); }
public void remove() { }
};
}
};
}
else
{
// Iterator of list
return new Iterable<V>() {
public Iterator<V> iterator() {
return list.iterator();
}
};
}
}
public List<V> getList(Object key)
{
List<V> list = hashtable.get(key);
if (list == null)
list = new ArrayList<V>();
return list;
}
/**
* Iterate over all elements in the table.
* Note that this currently copies them into a collection,
* so concurrent modification will not be taken into account
* (there will be no ConcurrentModificationException, either).
*/
@Override
public Iterator<Map.Entry<K, V>> iterator() {
if (size == 0)
{
return new Iterator<Map.Entry<K, V>>() {
public boolean hasNext() { return false; }
public Map.Entry<K, V> next() { throw new NoSuchElementException("Empty"); }
public void remove() { }
};
}
else
{
Collection<Map.Entry<K, V>> entries = new ArrayList<Map.Entry<K, V>>();
for (K key : hashtable.keySet())
{
List<V> values = hashtable.get(key);
for (V value : values)
entries.add(new AbstractMap.SimpleEntry<K,V>(key, value));
}
return entries.iterator();
}
}
@Override
/**
* Add this Value at the end of this key.
*
* @return As the value is never replaced, this will always return null.
*/
public V put(K key, V value) {
boolean success;
List<V> list = hashtable.get(key);
if (list == null)
{
list = new ArrayList<V>(listSize);
success = list.add(value);
hashtable.put(key, list);
}
else
{
success = list.add(value);
}
if (success)
size++;
return null;
}
@Override
/**
* Remove all objects linked to this key.
*
* @param key Key
* @return First of linked objects (or null).
*/
public V remove(Object key) {
List<V> list = hashtable.remove(key);
if (list == null)
return null;
else
{
V element = list.get(0);
size -= list.size();
return element;
}
}
public boolean remove(Object key, Object value)
{
List<V> list = hashtable.get(key);
if (list == null)
return false;
boolean removed = list.remove(value);
if (removed)
{
if (list.isEmpty())
hashtable.remove(key);
size--;
}
return removed;
}
@Override
public void putAll(Map<? extends K, ? extends V> map) {
for (Entry<? extends K, ? extends V> entry : map.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
@Override
public void clear() {
hashtable.clear();
size = 0;
}
@Override
public Set<K> keySet() {
return hashtable.keySet();
}
@Override
// TODO "The set is backed by the map, so changes to the map are reflected in the set, and vice-versa."
public Collection<V> values() {
List<V> newList = new ArrayList<V>();
if (isEmpty())
return newList;
Collection<List<V>> values = hashtable.values();
for(List<V> list : values )
newList.addAll(list);
return newList;
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
throw new UnsupportedOperationException("entrySet is not implemented, as identical entries are allowed (conflict with Set contract). Instead, use .iterator() to iterate through all entries.");
}
public String toString()
{
StringBuffer str = new StringBuffer(200);
for (K key : hashtable.keySet())
{
str.append(key).append(":\n");
List<V> values = hashtable.get(key);
for (V value : values)
str.append("\t").append(value).append("\n");
str.append("\n");
}
return str.toString();
}
}