/*
* Copyright (C) 2008 - 2012.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 or
* version 2 as published by the Free Software Foundation.
*
* This program 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 for more details.
*/
package uk.me.parabola.mkgmap.reader.osm;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
/**
* Store the tags that belong to an Element.
*
* Used to use a HashMap for this. We used to have a requirement to be able
* to add to the map during iteration over it but now the main reason
* to keep this class is that it is more memory efficient than a regular
* HashMap (hash maps are the main use of memory in the
* application), as it doesn't allocate a Map.Entry object for every tag.
* Performance of the whole application is unchanged compared with when
* a regular HashMap was used.
*
* It doesn't fully behave the same way that a map would.
*
* @author Steve Ratcliffe
*/
public class Tags implements Iterable<String> {
private static final int INIT_SIZE = 8;
private static final TagDict tagDict = TagDict.getInstance();
private short keySize;
private short capacity;
private short size;
private short[] keys;
private String[] values;
public Tags() {
keys = new short[INIT_SIZE];
values = new String[INIT_SIZE];
capacity = INIT_SIZE;
}
public String get(String key) {
short k = tagDict.xlate(key);
int ind = keyPos(k);
if (ind < 0)
return null;
return values[ind];
}
public String get(short key) {
int ind = keyPos(key);
if (ind < 0)
return null;
return values[ind];
}
/**
* Retrieves the number of tags.
* @return number of tags
*/
public int size() {
return size;
}
public String put(String key, String value) {
assert key != null : "key is null";
short dictIdx = tagDict.xlate(key);
return put(dictIdx,value);
}
public String put(short key, String value) {
assert value != null : "value is null";
ensureSpace();
int ind = keyPos(key);
if (ind < 0)
assert false : "keyPos(" + key + ") returns null - size = " + keySize + ", capacity = " + capacity;
keys[ind] = key;
String old = values[ind];
if (old == null) {
keySize++;
size++;
}
values[ind] = value;
return old;
}
public String remove(short key) {
int k = keyPos(key);
if (k >= 0 && values[k] != null) {
// because of the way this works, you can never remove keys
// except when resizing.
String old = values[k];
values[k] = null;
size--;
return old;
}
return null;
}
public String remove(String key) {
short kd = tagDict.xlate(key);
return remove(kd);
}
/**
* Make a deep copy of this object.
* @return A copy of this object.
*/
public Tags copy() {
Tags cp = new Tags();
cp.keySize = keySize;
cp.size = size;
cp.capacity = capacity;
cp.keys = Arrays.copyOf(keys, keys.length);
cp.values = Arrays.copyOf(values, values.length);
return cp;
}
private void ensureSpace() {
while (keySize + 1 >= capacity) {
short ncap = (short) (capacity*2);
short[] okey = keys;
String[] oval = values;
keys = new short[ncap];
values = new String[ncap];
capacity = ncap;
keySize = 0;
size = 0;
for (int i = 0; i < okey.length; i++) {
short k = okey[i];
String v = oval[i]; // null if tag has been removed
if (k != TagDict.INVALID_TAG_VALUE && v != null){
//put(keyDict.get(k), v);
int ind = keyPos(k);
keys[ind] = k;
values[ind] = v;
++keySize;
++size;
}
}
}
assert keySize < capacity;
}
private int keyPos(short key) {
int k = key & (capacity - 1);
int i = k;
do {
if (keys[i] == TagDict.INVALID_TAG_VALUE || keys[i] == key)
return i;
i++;
if (i >= capacity)
i = 0;
} while (i != k);
return -1;
}
/**
* Iterates over the tags in a special way that is used to look up in
* the rules.
*
* If you have the tags a=b, c=d then you will get the following strings
* returned: "a=b", "a=*", "c=d", "c=*".
*
* If you add a tag during the iteration, then it is guaranteed to
* appear later in the iteration.
*/
public Iterator<String> iterator() {
return new Iterator<String>() {
private int pos;
public boolean hasNext() {
// After every normal entry there is a wild card entry.
//if (doWild)
// return true;
// Normal entries in the map
for (int i = pos; i < capacity; i++) {
if (values[i] != null) {
pos = i;
return true;
}
}
return false;
}
/**
* Get the next tag as a single string. Also returns wild card
* entries.
*/
public String next() {
/*if (doWild) {
doWild = false;
return wild + "=*";
} else*/ if (pos < capacity) {
for (int i = pos; i < capacity; i++) {
if (values[i] != null) {
pos = i+1;
return (tagDict.get(keys[i]) + "=" + values[i]);
}
}
pos = capacity;
}
return null;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public Iterator<Map.Entry<String, String>> entryIterator() {
return new Iterator<Map.Entry<String, String>>() {
private int pos;
public boolean hasNext() {
for (int i = pos; i < capacity; i++) {
if (values[i] != null) {
pos = i;
return true;
}
}
return false;
}
public Map.Entry<String, String> next() {
Map.Entry<String, String> entry = new AbstractMap.SimpleEntry<>(tagDict.get(keys[pos]), values[pos]);
pos++;
return entry;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public Iterator<Map.Entry<Short, String>> entryShortIterator() {
return new Iterator<Map.Entry<Short, String>>() {
private int pos;
public boolean hasNext() {
for (int i = pos; i < capacity; i++) {
if (values[i] != null) {
pos = i;
return true;
}
}
return false;
}
public Map.Entry<Short, String> next() {
Map.Entry<Short, String> entry = new AbstractMap.SimpleEntry<>(keys[pos], values[pos]);
pos++;
return entry;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public Map<String, String> getTagsWithPrefix(String prefix, boolean removePrefix) {
Map<String, String> map = new HashMap<>();
int prefixLen = prefix.length();
for(int i = 0; i < capacity; ++i) {
if (keys[i] != 0){
String k = tagDict.get(keys[i]);
if(k != null && k.startsWith(prefix)) {
if(removePrefix)
map.put(k.substring(prefixLen), values[i]);
else
map.put(k, values[i]);
}
}
}
return map;
}
public void removeAll() {
Arrays.fill(keys, TagDict.INVALID_TAG_VALUE);
Arrays.fill(values, null);
keySize = 0;
size = 0;
}
public String toString() {
StringBuilder s =new StringBuilder();
s.append("[");
Iterator<Entry<String,String>> tagIter = entryIterator();
while (tagIter.hasNext()) {
Entry<String,String> tag = tagIter.next();
if (s.length() > 1) {
s.append("; ");
}
s.append(tag.getKey());
s.append("=");
s.append(tag.getValue());
}
s.append("]");
return s.toString();
}
/**
* Each tag has a position in the TagDict. This routine fills an array
* so that the caller can use direct access.
* The caller has to make sure that the array is large enough to hold
* the values he is looking for.
* @param tagVals
* @return
*/
public int expand(short[] keyArray, String[] tagVals){
if (tagVals == null)
return 0;
int maxKey = tagVals.length - 1;
int cntTags = 0;
for (int i = 0; i< capacity; i++){
short tagKey = keys[i];
if (tagKey != TagDict.INVALID_TAG_VALUE && values[i] != null && tagKey <= maxKey){
tagVals[tagKey] = values[i];
keyArray[cntTags++] = tagKey;
}
}
return cntTags;
}
}