io.permazen.util.ConvertedNavigableMap Maven / Gradle / Ivy
Show all versions of permazen-util Show documentation
/*
* Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
*/
package io.permazen.util;
import com.google.common.base.Converter;
import com.google.common.base.Preconditions;
import java.util.Comparator;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* Provides a transformed view of a wrapped {@link NavigableMap} using a strictly invertible {@link Converter}.
*
*
* Supplied {@link Converter}s may throw {@link ClassCastException} or {@link IllegalArgumentException}
* if given an objects whose runtime type does not match the expected type.
*
* @param key type of this map
* @param value type of this map
* @param key type of wrapped map
* @param value type of wrapped map
*/
public class ConvertedNavigableMap extends AbstractNavigableMap {
private final NavigableMap map;
private final Converter keyConverter;
private final Converter valueConverter;
/**
* Constructor.
*
* @param map wrapped map
* @param keyConverter key converter
* @param valueConverter value converter
* @throws IllegalArgumentException if any parameter is null
*/
public ConvertedNavigableMap(NavigableMap map, Converter keyConverter, Converter valueConverter) {
this(map, keyConverter, valueConverter, new Bounds<>());
}
/**
* Internal constructor.
*
* @param map wrapped map
* @param keyConverter key converter
* @param valueConverter value converter
* @throws IllegalArgumentException if any parameter is null
*/
ConvertedNavigableMap(NavigableMap map,
Converter keyConverter, Converter valueConverter, Bounds bounds) {
super(bounds);
Preconditions.checkArgument(map != null, "null map");
Preconditions.checkArgument(keyConverter != null, "null keyConverter");
Preconditions.checkArgument(valueConverter != null, "null valueConverter");
this.map = map;
this.keyConverter = keyConverter;
this.valueConverter = valueConverter;
}
public Converter getKeyConverter() {
return this.keyConverter;
}
public Converter getValueConverter() {
return this.valueConverter;
}
@Override
public Comparator super K> comparator() {
return new ConvertedComparator(this.map.comparator(), this.keyConverter);
}
@Override
@SuppressWarnings("unchecked")
public V get(Object key) {
WK wkey = null;
if (key != null) {
try {
wkey = this.keyConverter.convert((K)key);
} catch (IllegalArgumentException | ClassCastException e) {
return null;
}
}
final WV wvalue = this.map.get(wkey);
return wvalue != null ? this.valueConverter.reverse().convert(wvalue) : null;
}
@Override
public Set> entrySet() {
return new ConvertedEntrySet<>(this.map, this.keyConverter, this.valueConverter);
}
@Override
public NavigableSet navigableKeySet() {
return new ConvertedNavigableSet<>(this.map.navigableKeySet(), this.keyConverter);
}
@Override
public V put(K key, V value) {
final WK wkey = key != null ? this.keyConverter.convert(key) : null;
final WV wvalue = value != null ? this.valueConverter.convert(value) : null;
final WV wprev = this.map.put(wkey, wvalue);
return wprev != null ? this.valueConverter.reverse().convert(wprev) : null;
}
@Override
@SuppressWarnings("unchecked")
public V remove(Object key) {
WK wkey = null;
if (key != null) {
try {
wkey = this.keyConverter.convert((K)key);
} catch (IllegalArgumentException | ClassCastException e) {
return null;
}
}
final WV wvalue = this.map.remove(wkey);
return wvalue != null ? this.valueConverter.reverse().convert(wvalue) : null;
}
@Override
public void clear() {
this.map.clear();
}
@Override
public boolean isEmpty() {
return this.map.isEmpty();
}
@Override
public int size() {
return this.map.size();
}
@Override
protected Map.Entry searchBelow(K maxKey, boolean inclusive) {
try {
return super.searchBelow(maxKey, inclusive);
} catch (IllegalArgumentException e) { // handle case where elem is out of bounds
final Map.Entry lastEntry;
try {
lastEntry = this.lastEntry();
} catch (NoSuchElementException e2) {
return null;
}
return this.getComparator(false).compare(maxKey, lastEntry.getKey()) > 0 ? lastEntry : null;
}
}
@Override
protected Map.Entry searchAbove(K minKey, boolean inclusive) {
try {
return super.searchAbove(minKey, inclusive);
} catch (IllegalArgumentException e) { // handle case where elem is out of bounds
final Map.Entry firstEntry;
try {
firstEntry = this.firstEntry();
} catch (NoSuchElementException e2) {
return null;
}
return this.getComparator(false).compare(minKey, firstEntry.getKey()) < 0 ? firstEntry : null;
}
}
@Override
protected NavigableMap createSubMap(boolean reverse, Bounds newBounds) {
final K lower = newBounds.getLowerBound();
final K upper = newBounds.getUpperBound();
final WK wlower = newBounds.getLowerBoundType() != BoundType.NONE && lower != null ?
this.keyConverter.convert(lower) : null;
final WK wupper = newBounds.getUpperBoundType() != BoundType.NONE && upper != null ?
this.keyConverter.convert(upper) : null;
NavigableMap subMap = reverse ? this.map.descendingMap() : this.map;
if (newBounds.getLowerBoundType() != BoundType.NONE && newBounds.getUpperBoundType() != BoundType.NONE) {
subMap = subMap.subMap(
wlower, newBounds.getLowerBoundType().isInclusive(),
wupper, newBounds.getUpperBoundType().isInclusive());
} else if (newBounds.getLowerBoundType() != BoundType.NONE)
subMap = subMap.tailMap(wlower, newBounds.getLowerBoundType().isInclusive());
else if (newBounds.getUpperBoundType() != BoundType.NONE)
subMap = subMap.headMap(wupper, newBounds.getUpperBoundType().isInclusive());
return new ConvertedNavigableMap<>(subMap, this.keyConverter, this.valueConverter, newBounds);
}
}