// Copyright 2013 Daniel de Kok
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package eu.danieldk.dictomaton.collections;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import eu.danieldk.dictomaton.DictionaryBuilder;
import eu.danieldk.dictomaton.DictionaryBuilderException;
import eu.danieldk.dictomaton.PerfectHashDictionary;
/**
* An immutable mapping from {@link String} to {@link String}, where both keys an values are compactly stored
* using a finite state automaton.
*/
public class ImmutableStringStringMap extends AbstractMap<String, String>
{
private final PerfectHashDictionary d_keys;
private final PerfectHashDictionary d_values;
private int[] d_link;
/**
* A builder for {@link ImmutableStringStringMap}. Mappings
* can be added to the builder using the {@link #put} and {@link #putAll} methods. The
* {@link ImmutableStringStringMap} can then be constructed using the {@link #build} method.
*/
public static class Builder
{
private TreeMap<String, String> d_map;
public Builder()
{
d_map = new TreeMap<String, String>();
}
/**
* Put a key/value pair.
*/
public synchronized Builder put(String key, String value)
{
d_map.put(key, value);
return this;
}
/**
* Put all key/value pairs from a {@link java.util.Map}.
*/
public synchronized Builder putAll(Map<String, String> map)
{
d_map.putAll(map);
return this;
}
/**
* Construct a {@link ImmutableStringStringMap}.
*/
public synchronized ImmutableStringStringMap build() throws DictionaryBuilderException
{
PerfectHashDictionary keyDict = new DictionaryBuilder().addAll(d_map.keySet()).buildPerfectHash(false);
PerfectHashDictionary valueDict = new DictionaryBuilder().addAll(new TreeSet<String>(d_map.values()))
.buildPerfectHash(false);
int links[] = new int[keyDict.size()];
for (Map.Entry<String, String> entry : d_map.entrySet())
{
int keyHashCode = keyDict.number(entry.getKey());
int valueHashCode = valueDict.number(entry.getValue());
links[keyHashCode - 1] = valueHashCode;
}
return new ImmutableStringStringMap(keyDict, valueDict, links);
}
}
private class EntrySet extends AbstractSet<Entry<String, String>>
{
private class EntrySetIterator implements Iterator<Entry<String, String>>
{
private final Iterator<String> d_keyIter;
public EntrySetIterator()
{
d_keyIter = d_keys.iterator();
}
@Override
public boolean hasNext()
{
return d_keyIter.hasNext();
}
@Override
public Entry<String, String> next()
{
String key = d_keyIter.next();
int idx = d_keys.number(key) - 1;
return new SimpleEntry<String, String>(key, d_values.sequence(d_link[idx]));
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
}
@Override
public boolean contains(Object o)
{
if (o == null)
return false;
if (!(o instanceof Entry))
return false;
Entry e = (Entry) o;
// Null keys and values are not supported.
if (e.getKey() == null || e.getKey() == null)
return false;
if (!(e.getKey() instanceof String) || !(e.getValue() instanceof String))
return false;
String key = (String) e.getKey();
String value = (String) e.getValue();
int hash = d_keys.number(key);
// Does not contain the key.
if (hash == -1)
return false;
return d_values.sequence(d_link[hash - 1]).equals(value);
}
@Override
public boolean isEmpty()
{
return d_keys.isEmpty();
}
@Override
public Iterator<Entry<String, String>> iterator()
{
return new EntrySetIterator();
}
@Override
public int size()
{
return d_keys.size();
}
}
@Override
public void clear()
{
throw new UnsupportedOperationException();
}
@Override
public boolean containsKey(Object o)
{
return d_keys.contains(o);
}
@Override
public Set<Entry<String, String>> entrySet()
{
return new EntrySet();
}
@Override
public String get(Object o)
{
if (!(o instanceof String))
return null;
String key = (String) o;
int hashcode = d_keys.number(key);
if (hashcode == -1)
return null;
return d_values.sequence(d_link[hashcode - 1]);
}
@Override
public boolean isEmpty()
{
return d_keys.isEmpty();
}
@Override
public Set<String> keySet()
{
return d_keys;
}
@Override
public String put(String k, String v)
{
throw new UnsupportedOperationException();
}
@Override
public void putAll(Map<? extends String, ? extends String> m)
{
throw new UnsupportedOperationException();
}
@Override
public String remove(Object key)
{
throw new UnsupportedOperationException();
}
@Override
public int size()
{
return d_keys.size();
}
/**
* Get an iterator over the keys in the mapping.
*/
public Iterator<String> keyIterator()
{
return d_keys.iterator();
}
@Override
public Collection<String> values()
{
// Note: check that this is in correspondence to the contract of this method. We might
// have to return duplicate values, although they don't exist in our case.
return d_values;
}
private ImmutableStringStringMap(PerfectHashDictionary keys, PerfectHashDictionary values, int[] link)
{
this.d_keys = keys;
this.d_values = values;
this.d_link = link;
}
}