/*
* HashMapMap.java - This file is part of the Jakstab project.
* Copyright 2007-2015 Johannes Kinder <jk@jakstab.org>
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, see <http://www.gnu.org/licenses/>.
*/
package org.jakstab.util;
import java.util.*;
import org.jakstab.util.Logger;
public class HashMapMap<K, L, V> implements MapMap<K, L, V> {
@SuppressWarnings("unused")
private static final Logger logger = Logger.getLogger(HashMapMap.class);
public static <K, L, V> HashMapMap<K, L, V> create() {
return new HashMapMap<K, L, V>();
}
private Map<K, Map<L,V>> map;
protected Map<L, V> createSubMap() {
return new TreeMap<L, V>();
}
protected Map<L, V> createSubMap(Map<L, V> proto) {
return new TreeMap<L, V>(proto);
}
public HashMapMap() {
map = new HashMap<K, Map<L, V>>();
}
public HashMapMap(MapMap<K, L, V> proto) {
this();
for (K key : proto.leftKeySet()) {
map.put(key, createSubMap(proto.getSubMap(key)));
}
}
public boolean containsKey(K keyLeft, L keyRight) {
if (map.containsKey(keyLeft))
return map.get(keyLeft).containsKey(keyRight);
else return false;
}
public boolean containsLeftKey(K keyLeft) {
return map.containsKey(keyLeft);
}
@Override
public V get(K keyLeft, L keyRight) {
Map<L, V> subMap = map.get(keyLeft);
if (subMap != null) return subMap.get(keyRight);
else return null;
}
@Override
public V put(K keyLeft, L keyRight, V value) {
Map<L, V> subMap = map.get(keyLeft);
if (subMap == null) {
subMap = createSubMap();
map.put(keyLeft, subMap);
}
V oldValue = subMap.put(keyRight, value);
return oldValue;
}
@Override
public void putAll(MapMap<K, L, V> other) {
for (EntryIterator<K, L, V> it = entryIterator(); it.hasEntry(); it.next()) {
put(it.getLeftKey(), it.getRightKey(), it.getValue());
}
}
@Override
public V remove(K keyLeft, L keyRight) {
Map<L, V> subMap = map.get(keyLeft);
if (subMap != null) {
V oldValue = subMap.remove(keyRight);
if (subMap.isEmpty()) map.remove(keyLeft);
return oldValue;
}
else return null;
}
@Override
public void remove(K keyLeft) {
map.remove(keyLeft);
}
@Override
public Map<L, V> getSubMap(K keyLeft) {
return map.get(keyLeft);
}
@Override
public void clear() {
map.clear();
}
@Override
public Set<K> leftKeySet() {
return map.keySet();
}
@Override
public EntryIterator<K, L, V> entryIterator() {
return new DoubleIterator();
}
private final class DoubleIterator implements EntryIterator<K, L, V> {
private Iterator<K> it1;
private K currentLeftKey;
private Map.Entry<L, V> currentSubMapEntry;
private Iterator<Map.Entry<L, V>> it2;
public DoubleIterator() {
if (map.isEmpty()) {
it1 = null;
it2 = null;
currentLeftKey = null;
currentSubMapEntry = null;
} else {
it1 = map.keySet().iterator();
currentLeftKey = it1.next();
it2 = map.get(currentLeftKey).entrySet().iterator();
next();
}
}
@Override
public K getLeftKey() {
return currentLeftKey;
}
@Override
public L getRightKey() {
return currentSubMapEntry.getKey();
}
@Override
public V getValue() {
return currentSubMapEntry.getValue();
}
@Override
public boolean hasEntry() {
return (currentLeftKey != null);
}
@Override
public void next() {
if (!hasEntry()) throw new NoSuchElementException();
// If it2 has no next element, iterate it1 as long
// as we find a map that has elements
while (!it2.hasNext()) {
if (!it1.hasNext()) {
currentSubMapEntry = null;
currentLeftKey = null;
return;
}
currentLeftKey = it1.next();
it2 = map.get(currentLeftKey).entrySet().iterator();
}
currentSubMapEntry = it2.next();
}
}
@Override
public int size() {
int res = 0;
for (Map.Entry<K, Map<L, V>> subEntry : map.entrySet()) {
res += subEntry.getValue().size();
}
return res;
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public int hashCode() {
return map.hashCode();
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
HashMapMap<K, L, V> other = (HashMapMap<K, L, V>) obj;
if (map == null) {
return other.map == null;
} else {
// MapMaps are equal even if one has empty submaps which
// the other does not have, so we cannot simply use equals()
if (size() != other.size()) return false;
// Check equality of submaps only one way - if this one lacks some
// maps of the other, sizes will be different
for (K leftKey : leftKeySet()) {
Map<L,V> otherSubMap = other.getSubMap(leftKey);
if (otherSubMap == null) {
if (!getSubMap(leftKey).isEmpty())
return false;
// else getSubMap is empty and otherSubMap == null, so check next submap
} else {
if (!otherSubMap.equals(getSubMap(leftKey)))
return false;
}
}
return true;
}
}
}