package aQute.lib.json;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
public class MapHandler extends Handler {
final Class< ? > rawClass;
final Type keyType;
final Type valueType;
MapHandler(Class< ? > rawClass, Type keyType, Type valueType) {
if (rawClass != Map.class) {
ParameterizedType type = findAncestor(rawClass, Map.class);
this.keyType = keyType == Object.class ? resolve(type.getActualTypeArguments()[0]) : keyType;
this.valueType = valueType == Object.class ? resolve(type.getActualTypeArguments()[1]) : valueType;
} else {
this.keyType = keyType;
this.valueType = valueType;
}
if (rawClass.isInterface()) {
if (rawClass.isAssignableFrom(LinkedHashMap.class))
rawClass = LinkedHashMap.class;
else if (rawClass.isAssignableFrom(TreeMap.class))
rawClass = TreeMap.class;
else if (rawClass.isAssignableFrom(Hashtable.class))
rawClass = Hashtable.class;
else if (rawClass.isAssignableFrom(HashMap.class))
rawClass = HashMap.class;
else if (rawClass.isAssignableFrom(Dictionary.class))
rawClass = Hashtable.class;
else
throw new IllegalArgumentException("Unknown map interface: " + rawClass);
}
this.rawClass = rawClass;
}
private Type resolve(Type type) {
if (type instanceof TypeVariable< ? >) {
TypeVariable< ? > tv = (TypeVariable< ? >) type;
Type[] bounds = tv.getBounds();
return resolve(bounds[bounds.length - 1]);
}
return type;
}
private ParameterizedType findAncestor(Class< ? > start, Class< ? > target) {
if (start == null || start == Object.class)
return null;
for (Type t : start.getGenericInterfaces()) {
if (t instanceof ParameterizedType) {
if (((ParameterizedType) t).getRawType() == target)
return (ParameterizedType) t;
}
}
for (Class< ? > impls : start.getInterfaces()) {
ParameterizedType ancestor = findAncestor(impls, target);
if (ancestor != null)
return ancestor;
}
return findAncestor(start.getSuperclass(), target);
}
@Override
public void encode(Encoder app, Object object, Map<Object,Type> visited) throws IOException, Exception {
Map< ? , ? > map = (Map< ? , ? >) object;
app.append("{");
String del = "";
for (Map.Entry< ? , ? > e : map.entrySet())
try {
app.append(del);
String key;
if (e.getKey() != null && (keyType == String.class || keyType == Object.class))
key = e.getKey().toString();
else {
key = app.codec.enc().put(e.getKey()).toString();
}
StringHandler.string(app, key);
app.append(":");
app.encode(e.getValue(), valueType, visited);
del = ",";
} catch (Exception ee) {
throw new IllegalArgumentException("[\"" + e.getKey() + "\"]", ee);
}
app.append("}");
}
@Override
public Object decodeObject(Decoder r) throws Exception {
assert r.current() == '{';
@SuppressWarnings("unchecked")
Map<Object,Object> map = (Map<Object,Object>) rawClass.getConstructor().newInstance();
int c = r.next();
while (JSONCodec.START_CHARACTERS.indexOf(c) >= 0) {
Object key = r.codec.parseString(r);
if (!(keyType == null || keyType == Object.class)) {
Handler h = r.codec.getHandler(keyType, null);
key = h.decode(r, (String) key);
}
c = r.skipWs();
if (c != ':')
throw new IllegalArgumentException("Expected ':' but got " + (char) c);
c = r.next();
Object value = r.codec.decode(valueType, r);
if (value != null || !r.codec.ignorenull)
map.put(key, value);
c = r.skipWs();
if (c == '}')
break;
if (c == ',') {
c = r.next();
continue;
}
throw new IllegalArgumentException(
"Invalid character in parsing list, expected } or , but found " + (char) c);
}
assert r.current() == '}';
r.read(); // skip closing
return map;
}
}