Rev 3408 |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/*
* 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
;
}
}