/* * AbstractMultiMap.java * * Copyright (C) 2010 Leo Osvald <leo.osvald@gmail.com> * * This file is part of SGLJ. * * SGLJ 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 3 of the License, or * (at your option) any later version. * * SGLJ 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, see <http://www.gnu.org/licenses/>. */ package org.sglj.util; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Abstract implementation of the {@link MultiMap} interface.<br> * Multiple values associated to the same key are stored using * the {@link HashSet} class. Therefore, values are compared * using their {@link #hashCode()} and {@link #equals(Object)} method * which means that no two distinct values can be mapped to the same key.<br> * * @param <K> type of the key * @param <V> type of values stored by the map * @param <M> type of the underlying map which this multimap uses * * @author Leo Osvald * @version 1.0 */ public class AbstractMultiMap<K, V, M extends Map<K, Set<V>>> implements MultiMap<K, V>, Serializable { private static final long serialVersionUID = 1285863430479515409L; protected static final int DEFAULT_INNER_SET_INITIAL_CAPACITY = 4; private int size; private final Map<K, Set<V>> map; public AbstractMultiMap(M map) { this.map = map; for(Set<V> s : map.values()) { size += s.size(); } } @Override public Set<V> getAll(K key) { return map.get(key); } @Override public Set<V> removeAll(K key) { if(map.containsKey(key)) map.get(key).clear(); return map.remove(key); } public int getValueCount(K key) { if(!map.containsKey(key)) return 0; return map.get(key).size(); } public boolean containsEntry(K key, V value) { return containsKey(key) && getAll(key).contains(value); } public boolean removeEntry(K key, V value) { if(!containsEntry(key, value)) return false; getAll(key).remove(value); return true; } @Override public void clear() { map.clear(); size = 0; } @Override public boolean containsKey(Object key) { return map.containsKey(key); } @Override public boolean containsValue(Object value) { for(Set<V> s : map.values()) if(s.contains(value)) return true; return false; } @Override public Set<java.util.Map.Entry<K, V>> entrySet() throws IllegalStateException { throw new IllegalStateException("Operation not yet implemented!"); } @Override public V get(Object key) { if(map.containsKey(key)) return firstVal(map.get(key)); return null; } @Override public boolean isEmpty() { return map.isEmpty(); } @Override public Set<K> keySet() { return map.keySet(); } @Override public V put(K key, V value) { if(map.containsKey(key)) { map.get(key).add(value); return value; } else { map.put(key, createInnerSet(value, initialSetCapacity())); return null; } } @Override public void putAll(Map<? extends K, ? extends V> m) { for(Entry<? extends K, ? extends V> e : m.entrySet()) { put(e.getKey(), e.getValue()); } } @Override public V remove(Object key) { if(map.containsKey(key)) { V someVal = firstVal(map.get(key)); if(map.get(key).remove(someVal)) { if(map.get(key).isEmpty()) map.remove(key); return someVal; } return null; } return null; } @Override public int size() { return size; } @Override public Collection<V> values() { ArrayList<V> vals = new ArrayList<V>(); for(Set<V> s : map.values()) { vals.addAll(s); } return vals; } protected Set<V> createInnerSet(V value, int initialCapacity) { return new InnerSet(value, initialCapacity); } protected int initialSetCapacity() { return DEFAULT_INNER_SET_INITIAL_CAPACITY; } protected V firstVal(Set<V> set) { for(V val : set) return val; return null; } @Override public String toString() { if(map.isEmpty()) return "{}"; StringBuffer sb = new StringBuffer("{"); boolean first = true; for(Entry<K, Set<V>> e : map.entrySet()) { if(!e.getValue().isEmpty()) { if(!first) { sb.append(", "); } else { first = false; } sb.append(e.getKey()).append("=>").append(e.getValue()); } } sb.append("}"); return sb.toString(); } private class InnerSet extends HashSet<V> { private static final long serialVersionUID = -7694980022010327893L; public InnerSet(V value, int initialCapacity) { super(initialCapacity); add(value); } public boolean add(V e) { if(super.add(e)) { ++size; return true; } return false; } @Override public boolean addAll(Collection<? extends V> c) { int oldSize = super.size(); boolean changed = super.addAll(c); size += super.size() - oldSize; return changed; } @Override public boolean remove(Object o) { if(super.remove(o)) { --size; return true; } return false; } @Override public boolean removeAll(Collection<?> c) { int oldSize = super.size(); boolean changed = super.removeAll(c); size += super.size() - oldSize; return changed; } @Override public boolean retainAll(Collection<?> c) { int oldSize = super.size(); boolean changed = super.retainAll(c); size += super.size() - oldSize; return changed; } @Override public void clear() { super.clear(); size = 0; } } }