/*
* LazyTreeMap.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;
/**
* @author Johannes Kinder
*/
public class LazyTreeMap<K, V> extends AbstractMap<K, V> implements Map<K, V> {
@SuppressWarnings("unused")
private static final Logger logger = Logger.getLogger(LazyTreeMap.class);
private class ReferenceCountingTreeMap<L, W> extends TreeMap<L, W> {
private static final long serialVersionUID = -6043771577607149364L;
private int referenceCount;
private int hashCode = 0;
public ReferenceCountingTreeMap() {
super();
referenceCount = 1;
}
public ReferenceCountingTreeMap(ReferenceCountingTreeMap<L, W> innerMap) {
super(innerMap);
referenceCount = 1;
}
public void addRef() {
referenceCount++;
}
public void release() {
referenceCount--;
}
public boolean isShared() {
return referenceCount > 1;
}
}
private ReferenceCountingTreeMap<K, V> innerMap;
private transient Set<Map.Entry<K, V>> entries;
public LazyTreeMap() {
innerMap = new ReferenceCountingTreeMap<K, V>();
}
public LazyTreeMap(LazyTreeMap<K, V> other) {
innerMap = other.innerMap;
innerMap.addRef();
}
private boolean makeExclusive() {
// makeExclsusive is called before all updates to inner map,
// so reset cached hashcode
innerMap.hashCode = 0;
if (innerMap.isShared()) {
innerMap.release();
innerMap = new ReferenceCountingTreeMap<K, V>(innerMap);
assert !innerMap.isShared();
return true;
}
return false;
}
@Override
public void clear() {
makeExclusive();
innerMap.clear();
}
@Override
public V get(Object key) {
return innerMap.get(key);
}
@Override
public V put(K key, V value) {
makeExclusive();
return innerMap.put(key, value);
}
@Override
public V remove(Object key) {
makeExclusive();
return innerMap.remove(key);
}
@Override
public int size() {
return innerMap.size();
}
@Override
public boolean containsKey(Object key) {
return innerMap.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return innerMap.containsValue(value);
}
@Override
public boolean equals(Object o) {
if (o == null)
return false;
LazyTreeMap<?, ?> other = (LazyTreeMap<?, ?>)o;
return innerMap.equals(other.innerMap);
}
@Override
public int hashCode() {
if (innerMap.hashCode == 0)
innerMap.hashCode = innerMap.hashCode();
return innerMap.hashCode;
}
@Override
public boolean isEmpty() {
return innerMap.isEmpty();
}
@Override
protected void finalize() throws Throwable {
innerMap.release();
super.finalize();
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
if (entries == null)
entries = new EntrySet();
return entries;
}
private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
@Override
public Iterator<Map.Entry<K, V>> iterator() {
return new MapEntryIterator();
}
@Override
public boolean contains(Object o) {
Map.Entry<?, ?> entry = (Map.Entry<?, ?>)o;
V value = get(entry.getKey());
if (value == null)
return entry.getValue() == null;
else
return value.equals(entry.getValue());
}
@Override
public int size() {
return innerMap.size();
}
}
private final class MapEntryIterator implements Iterator<Map.Entry<K,V>> {
private Iterator<Map.Entry<K,V>> innerIt =
innerMap.entrySet().iterator();
private Map.Entry<K, V> last = null;
@Override
public boolean hasNext() {
return innerIt.hasNext();
}
@Override
public Map.Entry<K, V> next() {
last = innerIt.next();
return last;
}
@Override
public void remove() {
if (makeExclusive()) {
// yes, we copied the map, so refresh iterator
innerIt = innerMap.entrySet().iterator();
// fast forward to old position
if (last != null) {
while (!last.equals(innerIt.next()))
assert innerIt.hasNext();
}
}
innerIt.remove();
}
}
}