Это то, что я использую в данный момент. Он не поддерживает конфликтующие ключи (возникает исключение RuntimeException при их обнаружении).
StringStartsWithKeyMap.java:
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* A map in which a key matches an entry if key.startsWith(entry.key)
* @param <T>
*/
public class StringStartsWithKeyMap<T> implements Map<String, T> {
TreeMap<String, T> map = new TreeMap<>();
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return null != findEntry((String)key);
}
@Override
public boolean containsValue(Object value) {
return map.containsValue(value);
}
@Override
public T get(Object key) {
Entry<String, T> e = findEntry((String) key);
return e == null ? null : e.getValue();
}
@Override
public T put(String key, T value) {
if (map.containsKey(key)) {
return map.put(key, value);
} else if (null == findEntry(key) && null == findHigherEntry(key)) {
return map.put(key, value);
}
throw new RuntimeException("Conflicting keys: \""+key+"\" after "+findEntry(key)+" or before "+findHigherEntry(key));
}
@Override
public T remove(Object key) {
return map.remove(key);
}
@Override
public void putAll(Map<? extends String, ? extends T> m) {
m.forEach(this::put);
}
@Override
public void clear() {
map.clear();
}
@Override
public Set<String> keySet() {
return map.keySet();
}
@Override
public Collection<T> values() {
return map.values();
}
@Override
public Set<Entry<String, T>> entrySet() {
return map.entrySet();
}
public StringStartsWithKeyMap<T> putSameValueFor(T value, String... keys) {
for (String k : keys) {
put(k, value);
}
return this;
}
public StringStartsWithKeyMap<T> putSameValueFor(T value, Iterable<String> keys) {
for (String k : keys) {
put(k, value);
}
return this;
}
private Map.Entry<String, T> findEntry(String key) {
Map.Entry<String, T> entry = map.floorEntry(key);
if (entry != null && key.startsWith(entry.getKey())) {
return entry;
}
return null;
}
private Map.Entry<String, T> findHigherEntry(String key) {
Map.Entry<String, T> entry = map.higherEntry(key);
if (entry != null && entry.getKey().startsWith(key)) {
return entry;
}
return null;
}
}
и тесты:
StringStartsWithKeyMapTest.java:
import org.junit.Test;
import java.util.Arrays;
import java.util.Map;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
public class StringStartsWithKeyMapTest {
@Test
public void sizeTest() {
Map<String, Integer> map3 = makeMap3();
Map<String, Integer> map0 = makeMap0();
assertThat(map3.size(), is(3));
assertThat(map0.size(), is(0));
}
@Test
public void isEmptyTest() {
Map<String, Integer> map3 = makeMap3();
Map<String, Integer> map0 = makeMap0();
assertThat(map3.isEmpty(), is(false));
assertThat(map0.isEmpty(), is(true));
}
@Test
public void containsKeyTest() {
Map<String, Integer> map3 = makeMap3();
Map<String, Integer> map0 = makeMap0();
assertThat(map3.containsKey("foo"), is(true));
assertThat(map3.containsKey("foobar"), is(true));
assertThat(map3.containsKey("quux"), is(false));
assertThat(map3.containsKey("quuxfoo"), is(false));
assertThat(map0.containsKey("foo"), is(false));
}
@Test
public void containsValueTest() {
Map<String, Integer> map3 = makeMap3();
Map<String, Integer> map0 = makeMap0();
assertThat(map3.containsValue(1), is(true));
assertThat(map3.containsValue(1000), is(false));
assertThat(map0.containsValue(1), is(false));
}
@Test
public void getTest() {
Map<String, Integer> map3 = makeMap3();
Map<String, Integer> map0 = makeMap0();
assertThat(map3.get("foo"), is(1));
assertThat(map3.get("foobar"), is(1));
assertThat(map3.get("bar"), is(2));
assertThat(map3.get("baraboo"), is(2));
assertThat(map3.get("baz"), is(3));
assertThat(map3.get("bah"), is((Integer) null));
assertThat(map0.get("foo"), is((Integer) null));
assertThat(map0.get(null), is((Integer) null));
}
@Test
public void putTest() {
Map<String, Integer> map3 = makeMap3();
map3.put("qux",100);
assertThat(map3.get("qux"), is(100));
map3.put("foo",200);
assertThat(map3.get("foo"), is(200));
try {
map3.put("foobar",6);
assertThat(true, is(false));
} catch (RuntimeException re) {
}
try {
map3.put("fo",60);
assertThat(true, is(false));
} catch (RuntimeException re) {
}
}
@Test
public void removeTest() {
Map<String, Integer> map3 = makeMap3();
map3.remove("foo");
map3.remove("baraboo");
assertThat(map3.size(), is(2));
assertThat(map3.get("foo"), is((Integer)null));
assertThat(map3.get("bar"), is(2));
assertThat(map3.get("baz"), is(3));
assertThat(map3.get("baraboo"), is(2)); // need exact match for removal
}
@Test
public void putAllTest() {
Map<String, Integer> map3 = makeMap3();
Map<String, Integer> map3a = makeMap3a();
map3.putAll(map3a);
assertThat(map3a.size(), is(3));
assertThat(map3.size(), is(5));
assertThat(map3.get("foo"), is(100));
assertThat(map3.get("bar"), is(2));
assertThat(map3.get("baz"), is(3));
assertThat(map3.get("corge"), is(5));
assertThat(map3.get("grault"), is(6));
assertThat(map3.get("corgegrault"), is(5));
}
@Test
public void clearTest() {
Map<String, Integer> map3 = makeMap3();
assertThat(map3.get("foo"), is(1));
assertThat(map3.get("foobar"), is(1));
map3.clear();
assertThat(map3.get("foo"), is((Integer) null));
assertThat(map3.get("foobar"), is((Integer) null));
assertThat(map3.containsKey("foo"), is(false));
assertThat(map3.containsValue(1), is(false));
assertThat(map3.size(), is(0));
}
@Test
public void keySetTest() {
Map<String, Integer> map3 = makeMap3();
Map<String, Integer> map0 = makeMap0();
assertThat(map0.keySet().isEmpty(), is(true));
assertThat(map0.keySet().contains("foo"), is(false));
assertThat(map3.keySet().size(), is(3));
assertThat(map3.keySet().contains("foo"), is(true));
}
@Test
public void valuesTest() {
Map<String, Integer> map3 = makeMap3();
Map<String, Integer> map0 = makeMap0();
assertThat(map0.values().isEmpty(), is(true));
assertThat(map3.values().size(), is(3));
assertThat(map3.values().contains(1), is(true));
assertThat(map3.values().contains(2), is(true));
assertThat(map3.values().contains(3), is(true));
}
@Test
public void entrySetTest() {
Map<String, Integer> map3 = makeMap3();
Map<String, Integer> map0 = makeMap0();
assertThat(map0.entrySet().isEmpty(), is(true));
assertThat(map3.entrySet().size(), is(3));
}
@Test
public void putSameValueForTest() {
StringStartsWithKeyMap<Integer> map3 = makeMap3();
map3.putSameValueFor(400,"foo","corge","grault");
assertThat(map3.size(), is(5));
assertThat(map3.get("foo"), is(400));
assertThat(map3.get("bar"), is(2));
assertThat(map3.get("baz"), is(3));
assertThat(map3.get("corge"), is(400));
assertThat(map3.get("grault"), is(400));
assertThat(map3.get("corgegrault"), is(400));
}
@Test
public void putSameValueForTest2() {
StringStartsWithKeyMap<Integer> map3 = makeMap3();
map3.putSameValueFor(400, Arrays.asList("foo","corge","grault"));
assertThat(map3.size(), is(5));
assertThat(map3.get("foo"), is(400));
assertThat(map3.get("bar"), is(2));
assertThat(map3.get("baz"), is(3));
assertThat(map3.get("corge"), is(400));
assertThat(map3.get("grault"), is(400));
assertThat(map3.get("corgegrault"), is(400));
}
private StringStartsWithKeyMap<Integer> makeMap3() {
StringStartsWithKeyMap<Integer> res = new StringStartsWithKeyMap<>();
res.put("foo",1);
res.put("bar",2);
res.put("baz",3);
return res;
}
private StringStartsWithKeyMap<Integer> makeMap0() {
StringStartsWithKeyMap<Integer> res = new StringStartsWithKeyMap<>();
return res;
}
private StringStartsWithKeyMap<Integer> makeMap3a() {
StringStartsWithKeyMap<Integer> res = new StringStartsWithKeyMap<>();
res.put("foo",100);
res.put("corge",5);
res.put("grault",6);
return res;
}
}