Use local copy of latest bndlib code for pre-release testing purposes

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1347815 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/aQute/lib/json/ArrayHandler.java b/bundleplugin/src/main/java/aQute/lib/json/ArrayHandler.java
new file mode 100644
index 0000000..62af4a7
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/ArrayHandler.java
@@ -0,0 +1,40 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+public class ArrayHandler extends Handler {
+	Type	componentType;
+
+	ArrayHandler(Class< ? > rawClass, Type componentType) {
+		this.componentType = componentType;
+	}
+
+	@Override
+	void encode(Encoder app, Object object, Map<Object, Type> visited)
+			throws IOException, Exception {
+		app.append("[");
+		String del = "";
+		int l = Array.getLength(object);
+		for (int i = 0; i < l; i++) {
+			app.append(del);
+			app.encode(Array.get(object, i), componentType, visited);
+			del = ",";
+		}
+		app.append("]");
+	}
+
+	@Override
+	Object decodeArray(Decoder r) throws Exception {
+		ArrayList<Object> list = new ArrayList<Object>();
+		r.codec.parseArray(list, componentType, r);
+		Object array = Array.newInstance(r.codec.getRawClass(componentType),
+				list.size());
+		int n = 0;
+		for (Object o : list)
+			Array.set(array, n++, o);
+
+		return array;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/BooleanHandler.java b/bundleplugin/src/main/java/aQute/lib/json/BooleanHandler.java
new file mode 100644
index 0000000..d3b56ea
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/BooleanHandler.java
@@ -0,0 +1,30 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+public class BooleanHandler extends Handler {
+
+	@Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+			throws IOException, Exception {
+		app.append( object.toString());
+	}
+	
+	@Override Object decode(boolean s) {
+		return s;
+	}
+
+	@Override Object decode(String s) {
+		return Boolean.parseBoolean(s);
+	}
+
+	@Override Object decode(Number s) {
+		return s.intValue() != 0;
+	}
+
+	@Override Object decode() {
+		return false;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java b/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java
new file mode 100644
index 0000000..65f5f74
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/ByteArrayHandler.java
@@ -0,0 +1,30 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import aQute.lib.base64.*;
+
+public class ByteArrayHandler extends Handler {
+
+	@Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+			throws IOException, Exception {
+		StringHandler.string(app, Base64.encodeBase64((byte[]) object));
+	}
+
+	@Override Object decodeArray(Decoder r) throws Exception {
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+		ArrayList<Object> list = new ArrayList<Object>();
+		r.codec.parseArray(list, Byte.class, r);
+		for (Object b : list) {
+			out.write(((Byte) b).byteValue());
+		}
+		return out.toByteArray();
+	}
+
+	@Override Object decode(String s) throws Exception {
+		return Base64.decodeBase64(s);
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/CharacterHandler.java b/bundleplugin/src/main/java/aQute/lib/json/CharacterHandler.java
new file mode 100644
index 0000000..7009e0c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/CharacterHandler.java
@@ -0,0 +1,31 @@
+package aQute.lib.json;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+public class CharacterHandler extends Handler {
+
+	@Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+			throws Exception {	
+		Character c  = (Character) object;
+		int v = (int) c.charValue();
+		app.append( v+"" );
+	}
+	
+	@Override Object decode(boolean s) {
+		return s ? 't' : 'f';
+	}
+
+	@Override Object decode(String s) {
+		return (char) Integer.parseInt(s);
+	}
+
+	@Override Object decode(Number s) {
+		return (char) s.shortValue();
+	}
+
+	@Override Object decode() {
+		return 0;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/CollectionHandler.java b/bundleplugin/src/main/java/aQute/lib/json/CollectionHandler.java
new file mode 100644
index 0000000..3fb14ee
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/CollectionHandler.java
@@ -0,0 +1,57 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+public class CollectionHandler extends Handler {
+	Class<?>	rawClass;
+	Type		componentType;
+	
+	CollectionHandler(Class<?> rawClass, Type componentType) {
+		this.componentType = componentType;
+		if (rawClass.isInterface()) {
+			if (rawClass.isAssignableFrom(ArrayList.class))
+				rawClass = ArrayList.class;
+			else if (rawClass.isAssignableFrom(LinkedList.class))
+				rawClass = LinkedList.class;
+			else if (rawClass.isAssignableFrom(HashSet.class))
+				rawClass = HashSet.class;
+			else if (rawClass.isAssignableFrom(TreeSet.class))
+				rawClass = TreeSet.class;
+			else if (rawClass.isAssignableFrom(Vector.class))
+				rawClass = Vector.class;
+			else if (rawClass.isAssignableFrom(ConcurrentLinkedQueue.class))
+				rawClass = ConcurrentLinkedQueue.class;
+			else if (rawClass.isAssignableFrom(CopyOnWriteArrayList.class))
+				rawClass = CopyOnWriteArrayList.class;
+			else if (rawClass.isAssignableFrom(CopyOnWriteArraySet.class))
+				rawClass = CopyOnWriteArraySet.class;
+			else
+				throw new IllegalArgumentException("Unknown interface type for collection: "
+						+ rawClass);
+		}
+		this.rawClass = rawClass;
+	}
+
+	@Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+			throws IOException, Exception {
+		Collection<?> collection = (Collection<?>) object;
+
+		app.append("[");
+		String del = "";
+		for (Object o : collection) {
+			app.append(del);
+			app.encode(o, componentType, visited);
+			del = ",";
+		}
+		app.append("]");
+	}
+
+	@SuppressWarnings("unchecked") @Override Object decodeArray(Decoder r) throws Exception {
+		Collection<Object> c = (Collection<Object>) rawClass.newInstance();
+		r.codec.parseArray(c, componentType, r);
+		return c;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/DateHandler.java b/bundleplugin/src/main/java/aQute/lib/json/DateHandler.java
new file mode 100644
index 0000000..d4f262e
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/DateHandler.java
@@ -0,0 +1,30 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.text.*;
+import java.util.*;
+
+public class DateHandler extends Handler {
+	final static SimpleDateFormat	sdf	= new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+
+	@Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+			throws IOException, Exception {
+		String s;
+		synchronized (sdf) {
+			s = sdf.format((Date) object);
+		}
+		StringHandler.string(app, s);
+	}
+
+	@Override Object decode(String s) throws Exception {
+		synchronized (sdf) {
+			return sdf.parse(s);
+		}
+	}
+
+	@Override Object decode(Number s) throws Exception {
+		return new Date(s.longValue());
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/Decoder.java b/bundleplugin/src/main/java/aQute/lib/json/Decoder.java
new file mode 100644
index 0000000..3cbed4b
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/Decoder.java
@@ -0,0 +1,137 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.security.*;
+import java.util.*;
+
+public class Decoder implements Closeable {
+	final JSONCodec		codec;
+	Reader				reader;
+	int					current;
+	MessageDigest		digest;
+	Map<String, Object>	extra;
+	String encoding = "UTF-8";
+	
+	boolean strict;
+
+	Decoder(JSONCodec codec) {
+		this.codec = codec;
+	}
+
+	public Decoder from(File file) throws Exception {
+		return from(new FileInputStream(file));
+	}
+
+	public Decoder from(InputStream in) throws Exception {
+		return from(new InputStreamReader(in, encoding));
+	}
+	
+	public Decoder charset(String encoding) {
+		this.encoding = encoding;
+		return this;
+	}
+	
+	public Decoder strict() {
+		this.strict = true;
+		return this;
+	}
+
+	public Decoder from(Reader in) throws Exception {
+		reader = in;
+		read();
+		return this;
+	}
+
+	public Decoder faq(String in) throws Exception {
+		return from(in.replace('\'', '"'));
+	}
+
+	public Decoder from(String in) throws Exception {
+		return from(new StringReader(in));
+	}
+
+	public Decoder mark() throws NoSuchAlgorithmException {
+		if (digest == null)
+			digest = MessageDigest.getInstance("SHA1");
+		digest.reset();
+		return this;
+	}
+
+	public byte[] digest() {
+		if (digest == null)
+			return null;
+
+		return digest.digest();
+	}
+
+	@SuppressWarnings("unchecked") public <T> T get(Class<T> clazz) throws Exception {
+		return (T) codec.decode(clazz, this);
+	}
+
+	public Object get(Type type) throws Exception {
+		return codec.decode(type, this);
+	}
+
+	public Object get() throws Exception {
+		return codec.decode(null, this);
+	}
+
+	int read() throws Exception {
+		current = reader.read();
+		if (digest != null) {
+			digest.update((byte) (current / 256));
+			digest.update((byte) (current % 256));
+		}
+		return current;
+	}
+
+	int current() {
+		return current;
+	}
+
+	/**
+	 * Skip any whitespace.
+	 * 
+	 * @return
+	 * @throws Exception
+	 */
+	int skipWs() throws Exception {
+		while (Character.isWhitespace(current()))
+			read();
+		return current();
+	}
+
+	/**
+	 * Skip any whitespace.
+	 * 
+	 * @return
+	 * @throws Exception
+	 */
+	int next() throws Exception {
+		read();
+		return skipWs();
+	}
+
+	void expect(String s) throws Exception {
+		for (int i = 0; i < s.length(); i++)
+			if (!(s.charAt(i) == read()))
+				throw new IllegalArgumentException("Expected " + s + " but got something different");
+		read();
+	}
+
+	public boolean isEof() throws Exception {
+		int c = skipWs();
+		return c < 0;
+	}
+
+	public void close() throws IOException {
+		reader.close();
+	}
+
+	public Map<String, Object> getExtra() {
+		if (extra == null)
+			extra = new HashMap<String, Object>();
+		return extra;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/Encoder.java b/bundleplugin/src/main/java/aQute/lib/json/Encoder.java
new file mode 100644
index 0000000..09990ed
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/Encoder.java
@@ -0,0 +1,112 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.security.*;
+import java.util.*;
+
+public class Encoder implements Appendable, Closeable, Flushable {
+	final JSONCodec	codec;
+	Appendable		app;
+	MessageDigest	digest;
+	boolean			writeDefaults;
+	String			encoding	= "UTF-8";
+
+	Encoder(JSONCodec codec) {
+		this.codec = codec;
+	}
+
+	public Encoder put(Object object) throws Exception {
+		if (app == null)
+			to();
+
+		codec.encode(this, object, null, new IdentityHashMap<Object, Type>());
+		return this;
+	}
+
+	public Encoder mark() throws NoSuchAlgorithmException {
+		if (digest == null)
+			digest = MessageDigest.getInstance("SHA1");
+		digest.reset();
+		return this;
+	}
+
+	public byte[] digest() throws NoSuchAlgorithmException, IOException {
+		if (digest == null)
+			return null;
+		append('\n');
+		return digest.digest();
+	}
+
+	public Encoder to() throws IOException {
+		to(new StringWriter());
+		return this;
+	}
+
+	public Encoder to(File file) throws IOException {
+		return to(new FileOutputStream(file));
+	}
+
+	public Encoder charset(String encoding) {
+		this.encoding = encoding;
+		return this;
+	}
+
+	public Encoder to(OutputStream out) throws IOException {
+		return to(new OutputStreamWriter(out, encoding));
+	}
+
+	public Encoder to(Appendable out) throws IOException {
+		app = out;
+		return this;
+	}
+
+	public Appendable append(char c) throws IOException {
+		if (digest != null) {
+			digest.update((byte) (c / 256));
+			digest.update((byte) (c % 256));
+		}
+		app.append(c);
+		return this;
+	}
+
+	public Appendable append(CharSequence sq) throws IOException {
+		return append(sq, 0, sq.length());
+	}
+
+	public Appendable append(CharSequence sq, int start, int length) throws IOException {
+		if (digest != null) {
+			for (int i = start; i < length; i++) {
+				char c = sq.charAt(i);
+				digest.update((byte) (c / 256));
+				digest.update((byte) (c % 256));
+			}
+		}
+		app.append(sq, start, length);
+		return this;
+	}
+
+	public String toString() {
+		return app.toString();
+	}
+
+	public void close() throws IOException {
+		if (app != null && app instanceof Closeable)
+			((Closeable) app).close();
+	}
+
+	void encode(Object object, Type type, Map<Object, Type> visited) throws Exception {
+		codec.encode(this, object, type, visited);
+	}
+
+	public Encoder writeDefaults() {
+		writeDefaults = true;
+		return this;
+	}
+
+	public void flush() throws IOException {
+		if (app instanceof Flushable) {
+			((Flushable) app).flush();
+		}
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/EnumHandler.java b/bundleplugin/src/main/java/aQute/lib/json/EnumHandler.java
new file mode 100644
index 0000000..dbdb492
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/EnumHandler.java
@@ -0,0 +1,24 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+public class EnumHandler extends Handler {
+	@SuppressWarnings("rawtypes")
+	final Class				type;
+
+	public EnumHandler(Class<?> type) {
+		this.type = type;
+	}
+
+	@Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+			throws IOException, Exception {
+		StringHandler.string(app, object.toString());
+	}
+
+	@SuppressWarnings("unchecked") Object decode(String s) throws Exception {
+		return Enum.valueOf(type, s);
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/FileHandler.java b/bundleplugin/src/main/java/aQute/lib/json/FileHandler.java
new file mode 100644
index 0000000..2f0eea5
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/FileHandler.java
@@ -0,0 +1,38 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import aQute.lib.base64.*;
+
+public class FileHandler extends Handler {
+
+	@Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+			throws IOException, Exception {
+		File f = (File) object;
+		if ( !f.isFile())
+			throw new RuntimeException("Encoding a file requires the file to exist and to be a normal file " + f );
+		
+		FileInputStream in = new FileInputStream(f);
+		try {
+			app.append('"');
+			Base64.encode(in, app);
+			app.append('"');
+		} finally {
+			in.close();
+		}
+	}
+
+	Object decode(String s) throws Exception {
+		File tmp = File.createTempFile("json", ".bin");
+		FileOutputStream fout = new FileOutputStream(tmp);
+		try {
+			Base64.decode(new StringReader(s), fout);
+		} finally {
+			fout.close();
+		}
+		return tmp;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/Handler.java b/bundleplugin/src/main/java/aQute/lib/json/Handler.java
new file mode 100644
index 0000000..18957e8
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/Handler.java
@@ -0,0 +1,35 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+abstract class Handler {
+	abstract void encode(Encoder app, Object object, Map<Object, Type> visited)
+			throws IOException, Exception;
+
+	Object decodeObject(Decoder isr) throws Exception {
+		throw new UnsupportedOperationException("Cannot be mapped to object " + this);
+	}
+
+	Object decodeArray(Decoder isr) throws Exception {
+		throw new UnsupportedOperationException("Cannot be mapped to array " + this);
+	}
+
+	Object decode(String s) throws Exception {
+		throw new UnsupportedOperationException("Cannot be mapped to string " + this);
+	}
+
+	Object decode(Number s) throws Exception {
+		throw new UnsupportedOperationException("Cannot be mapped to number " + this);
+	}
+
+	Object decode(boolean s) {
+		throw new UnsupportedOperationException("Cannot be mapped to boolean " + this);
+	}
+
+	Object decode() {
+		return null;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java b/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java
new file mode 100644
index 0000000..a45b5b4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/JSONCodec.java
@@ -0,0 +1,481 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.regex.*;
+
+/**
+ * This is a simple JSON Coder and Encoder that uses the Java type system to
+ * convert data objects to JSON and JSON to (type safe) Java objects. The
+ * conversion is very much driven by classes and their public fields. Generic
+ * information, when present is taken into account. </p> Usage patterns to
+ * encode:
+ * 
+ * <pre>
+ *  JSONCoder codec = new JSONCodec(); // 
+ * 	assert "1".equals( codec.enc().to().put(1).toString());
+ * 	assert "[1,2,3]".equals( codec.enc().to().put(Arrays.asList(1,2,3).toString());
+ * 
+ *  Map m = new HashMap();
+ *  m.put("a", "A");
+ * 	assert "{\"a\":\"A\"}".equals( codec.enc().to().put(m).toString());
+ * 
+ *  static class D { public int a; }
+ *  D d = new D();
+ *  d.a = 41;
+ *  assert "{\"a\":41}".equals( codec.enc().to().put(d).toString());
+ * </pre>
+ * 
+ * It is possible to redirect the encoder to another output (default is a
+ * string). See {@link Encoder#to()},{@link Encoder#to(File))},
+ * {@link Encoder#to(OutputStream)}, {@link Encoder#to(Appendable))}. To reset
+ * the string output call {@link Encoder#to()}.
+ * <p/>
+ * This Codec class can be used in a concurrent environment. The Decoders and
+ * Encoders, however, must only be used in a single thread.
+ */
+public class JSONCodec {
+	final static String								START_CHARACTERS	= "[{\"-0123456789tfn";
+
+	// Handlers
+	private final static WeakHashMap<Type, Handler>	handlers			= new WeakHashMap<Type, Handler>();
+	private static StringHandler					sh					= new StringHandler();
+	private static BooleanHandler					bh					= new BooleanHandler();
+	private static CharacterHandler					ch					= new CharacterHandler();
+	private static CollectionHandler				dch					= new CollectionHandler(
+																				ArrayList.class,
+																				Object.class);
+	private static SpecialHandler					sph					= new SpecialHandler(
+																				Pattern.class,
+																				null, null);
+	private static DateHandler						sdh					= new DateHandler();
+	private static FileHandler						fh					= new FileHandler();
+	private static ByteArrayHandler					byteh				= new ByteArrayHandler();
+
+	/**
+	 * Create a new Encoder with the state and appropriate API.
+	 * 
+	 * @return an Encoder
+	 */
+	public Encoder enc() {
+		return new Encoder(this);
+	}
+
+	/**
+	 * Create a new Decoder with the state and appropriate API.
+	 * 
+	 * @return a Decoder
+	 */
+	public Decoder dec() {
+		return new Decoder(this);
+	}
+
+	/*
+	 * Work horse encode methods, all encoding ends up here.
+	 */
+	void encode(Encoder app, Object object, Type type, Map<Object, Type> visited) throws Exception {
+
+		// Get the null out of the way
+
+		if (object == null) {
+			app.append("null");
+			return;
+		}
+
+		// If we have no type or the type is Object.class
+		// we take the type of the object itself. Normally types
+		// come from declaration sites (returns, fields, methods, etc)
+		// and contain generic info.
+
+		if (type == null || type == Object.class)
+			type = object.getClass();
+
+		// Dispatch to the handler who knows how to handle the given type.
+		Handler h = getHandler(type);
+		h.encode(app, object, visited);
+	}
+
+	/*
+	 * This method figures out which handler should handle the type specific
+	 * stuff. It returns a handler for each type. If no appropriate handler
+	 * exists, it will create one for the given type. There are actually quite a
+	 * lot of handlers since Java is not very object oriented.
+	 * 
+	 * @param type
+	 * 
+	 * @return
+	 * 
+	 * @throws Exception
+	 */
+	Handler getHandler(Type type) throws Exception {
+
+		// First the static hard coded handlers for the common types.
+
+		if (type == String.class)
+			return sh;
+
+		if (type == Boolean.class || type == boolean.class)
+			return bh;
+
+		if (type == byte[].class)
+			return byteh;
+
+		if (Character.class == type || char.class == type)
+			return ch;
+
+		if (Pattern.class == type)
+			return sph;
+
+		if (Date.class == type)
+			return sdh;
+
+		if (File.class == type)
+			return fh;
+
+		Handler h;
+		synchronized (handlers) {
+			h = handlers.get(type);
+		}
+
+		if (h != null)
+			return h;
+
+		if (type instanceof Class) {
+
+			Class<?> clazz = (Class<?>) type;
+
+			if (Enum.class.isAssignableFrom(clazz))
+				h = new EnumHandler(clazz);
+			else if (Collection.class.isAssignableFrom(clazz)) // A Non Generic
+																// collection
+
+				h = dch;
+			else if (clazz.isArray()) // Non generic array
+				h = new ArrayHandler(clazz, clazz.getComponentType());
+			else if (Map.class.isAssignableFrom(clazz)) // A Non Generic map
+				h = new MapHandler(clazz, Object.class, Object.class);
+			else if (Number.class.isAssignableFrom(clazz) || clazz.isPrimitive())
+				h = new NumberHandler(clazz);
+			else {
+				Method valueOf = null;
+				Constructor<?> constructor = null;
+
+				try {
+					constructor = clazz.getConstructor(String.class);
+				} catch (Exception e) {
+					// Ignore
+				}
+				try {
+					valueOf = clazz.getMethod("valueOf", String.class);
+				} catch (Exception e) {
+					// Ignore
+				}
+				if (constructor != null || valueOf != null)
+					h = new SpecialHandler(clazz, constructor, valueOf);
+				else
+					h = new ObjectHandler(this, clazz); // Hmm, might not be a
+														// data class ...
+			}
+
+		} else {
+
+			// We have generic information available
+			// We only support generics on Collection, Map, and arrays
+
+			if (type instanceof ParameterizedType) {
+				ParameterizedType pt = (ParameterizedType) type;
+				Type rawType = pt.getRawType();
+				if (rawType instanceof Class) {
+					Class<?> rawClass = (Class<?>) rawType;
+					if (Collection.class.isAssignableFrom(rawClass))
+						h = new CollectionHandler(rawClass, pt.getActualTypeArguments()[0]);
+					else if (Map.class.isAssignableFrom(rawClass))
+						h = new MapHandler(rawClass, pt.getActualTypeArguments()[0],
+								pt.getActualTypeArguments()[1]);
+					else
+						throw new IllegalArgumentException(
+								"Found a parameterized type that is not a map or collection");
+				}
+			} else if (type instanceof GenericArrayType) {
+				GenericArrayType gat = (GenericArrayType) type;
+				h = new ArrayHandler(getRawClass(type), gat.getGenericComponentType());
+			} else
+				throw new IllegalArgumentException(
+						"Found a parameterized type that is not a map or collection");
+		}
+		synchronized (handlers) {
+			// We might actually have duplicates
+			// but who cares? They should be identical
+			handlers.put(type, h);
+		}
+		return h;
+	}
+
+	Object decode(Type type, Decoder isr) throws Exception {
+		int c = isr.skipWs();
+		Handler h;
+
+		if (type == null || type == Object.class) {
+
+			// Establish default behavior when we run without
+			// type information
+
+			switch (c) {
+			case '{':
+				type = LinkedHashMap.class;
+				break;
+
+			case '[':
+				type = ArrayList.class;
+				break;
+
+			case '"':
+				return parseString(isr);
+
+			case 'n':
+				isr.expect("ull");
+				return null;
+
+			case 't':
+				isr.expect("rue");
+				return true;
+
+			case 'f':
+				isr.expect("alse");
+				return false;
+
+			case '0':
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+			case '8':
+			case '9':
+			case '-':
+				return parseNumber(isr);
+
+			default:
+				throw new IllegalArgumentException("Invalid character at begin of token: "
+						+ (char) c);
+			}
+		}
+
+		h = getHandler(type);
+
+		switch (c) {
+		case '{':
+			return h.decodeObject(isr);
+
+		case '[':
+			return h.decodeArray(isr);
+
+		case '"':
+			return h.decode(parseString(isr));
+
+		case 'n':
+			isr.expect("ull");
+			return h.decode();
+
+		case 't':
+			isr.expect("rue");
+			return h.decode(Boolean.TRUE);
+
+		case 'f':
+			isr.expect("alse");
+			return h.decode(Boolean.FALSE);
+
+		case '0':
+		case '1':
+		case '2':
+		case '3':
+		case '4':
+		case '5':
+		case '6':
+		case '7':
+		case '8':
+		case '9':
+		case '-':
+			return h.decode(parseNumber(isr));
+
+		default:
+			throw new IllegalArgumentException("Unexpected character in input stream: " + (char) c);
+		}
+	}
+
+	String parseString(Decoder r) throws Exception {
+		assert r.current() == '"';
+
+		int c = r.next(); // skip first "
+
+		StringBuilder sb = new StringBuilder();
+		while (c != '"') {
+			if (c < 0 || Character.isISOControl(c))
+				throw new IllegalArgumentException(
+						"JSON strings may not contain control characters: " + r.current());
+
+			if (c == '\\') {
+				c = r.read();
+				switch (c) {
+				case '"':
+				case '\\':
+				case '/':
+					sb.append((char)c);
+					break;
+
+				case 'b':
+					sb.append('\b');
+					break;
+
+				case 'f':
+					sb.append('\f');
+					break;
+				case 'n':
+					sb.append('\n');
+					break;
+				case 'r':
+					sb.append('\r');
+					break;
+				case 't':
+					sb.append('\t');
+					break;
+				case 'u':
+					int a3 = hexDigit(r.read()) << 12;
+					int a2 = hexDigit(r.read()) << 8;
+					int a1 = hexDigit(r.read()) << 4;
+					int a0 = hexDigit(r.read()) << 0;
+					c = a3 + a2 + a1 + a0;
+					sb.append((char) c);
+					break;
+
+				default:
+					throw new IllegalArgumentException(
+							"The only characters after a backslash are \", \\, b, f, n, r, t, and u but got "
+									+ c);
+				}
+			} else
+				sb.append((char) c);
+
+			c = r.read();
+		}
+		assert c == '"';
+		r.read(); // skip quote
+		return sb.toString();
+	}
+
+	private int hexDigit(int c) throws EOFException {
+		if (c >= '0' && c <= '9')
+			return c - '0';
+
+		if (c >= 'A' && c <= 'F')
+			return c - 'A' + 10;
+
+		if (c >= 'a' && c <= 'f')
+			return c - 'a' + 10;
+
+		throw new IllegalArgumentException("Invalid hex character: " + c);
+	}
+
+	private Number parseNumber(Decoder r) throws Exception {
+		StringBuilder sb = new StringBuilder();
+		boolean d = false;
+
+		if (r.current() == '-') {
+			sb.append('-');
+			r.read();
+		}
+
+		int c = r.current();
+		if (c == '0') {
+			sb.append('0');
+			c = r.read();
+		} else if (c >= '1' && c <= '9') {
+			sb.append((char) c);
+			c = r.read();
+
+			while (c >= '0' && c <= '9') {
+				sb.append((char) c);
+				c = r.read();
+			}
+		} else
+			throw new IllegalArgumentException("Expected digit");
+
+		if (c == '.') {
+			d = true;
+			sb.append('.');
+			c = r.read();
+			while (c >= '0' && c <= '9') {
+				sb.append((char) c);
+				c = r.read();
+			}
+		}
+		if (c == 'e' || c == 'E') {
+			d = true;
+			sb.append('e');
+			c = r.read();
+			if (c == '+') {
+				sb.append('+');
+				c = r.read();
+			} else if (c == '-') {
+				sb.append('-');
+				c = r.read();
+			}
+			while (c >= '0' && c <= '9') {
+				sb.append((char) c);
+				c = r.read();
+			}
+		}
+		if (d)
+			return Double.parseDouble(sb.toString());
+		long l = Long.parseLong(sb.toString());
+		if (l > Integer.MAX_VALUE || l < Integer.MIN_VALUE)
+			return l;
+		return (int) l;
+	}
+
+	void parseArray(Collection<Object> list, Type componentType, Decoder r) throws Exception {
+		assert r.current() == '[';
+		int c = r.next();
+		while (START_CHARACTERS.indexOf(c) >= 0) {
+			Object o = decode(componentType, r);
+			list.add(o);
+
+			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
+	}
+
+	@SuppressWarnings("rawtypes")
+	Class<?> getRawClass(Type type) {
+		if (type instanceof Class)
+			return (Class) type;
+
+		if (type instanceof ParameterizedType)
+			return getRawClass(((ParameterizedType) type).getRawType());
+
+		if (type instanceof GenericArrayType) {
+			Type subType = ((GenericArrayType) type).getGenericComponentType();
+			Class c = getRawClass(subType);
+			return Array.newInstance(c, 0).getClass();
+		}
+
+		throw new IllegalArgumentException(
+				"Does not support generics beyond Parameterized Type  and GenericArrayType, got "
+						+ type);
+	}
+
+}
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/lib/json/MapHandler.java b/bundleplugin/src/main/java/aQute/lib/json/MapHandler.java
new file mode 100644
index 0000000..5a413b6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/MapHandler.java
@@ -0,0 +1,91 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+public class MapHandler extends Handler {
+	final Class<?>	rawClass;
+	final Type		keyType;
+	final Type		valueType;
+
+	MapHandler(Class<?> rawClass, Type keyType, Type valueType) {
+		this.keyType = keyType;
+		this.valueType = valueType;
+		if (rawClass.isInterface()) {
+			if (rawClass.isAssignableFrom(HashMap.class))
+				rawClass = HashMap.class;
+			else if (rawClass.isAssignableFrom(TreeMap.class))
+				rawClass = TreeMap.class;
+			else if (rawClass.isAssignableFrom(Hashtable.class))
+				rawClass = Hashtable.class;
+			else if (rawClass.isAssignableFrom(LinkedHashMap.class))
+				rawClass = LinkedHashMap.class;
+			else
+				throw new IllegalArgumentException("Unknown map interface: " + rawClass);
+		}
+		this.rawClass = rawClass;
+	}
+
+	@Override 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()) {
+			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 = ",";
+		}
+		app.append("}");
+	}
+
+	@SuppressWarnings("unchecked") @Override Object decodeObject(Decoder r) throws Exception {
+		assert r.current() == '{';
+		
+		Map<Object, Object> map = (Map<Object, Object>) rawClass.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);
+				key = h.decode((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);
+			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;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/NumberHandler.java b/bundleplugin/src/main/java/aQute/lib/json/NumberHandler.java
new file mode 100644
index 0000000..644d49a
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/NumberHandler.java
@@ -0,0 +1,71 @@
+package aQute.lib.json;
+
+import java.lang.reflect.*;
+import java.math.*;
+import java.util.*;
+
+public class NumberHandler extends Handler {
+	final Class<?>	type;
+
+	NumberHandler(Class<?> clazz) {
+		this.type = clazz;
+	}
+
+	@Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+			throws Exception {
+		String s = object.toString();
+		if ( s.endsWith(".0"))
+			s  = s.substring(0,s.length()-2);
+		
+		app.append(s);
+	}
+
+	@Override Object decode(boolean s) {
+		return decode(s ? 1d : 0d);
+	}
+
+	@Override Object decode(String s) {
+		double d = Double.parseDouble(s);
+		return decode(d);
+	}
+
+	@Override Object decode() {
+		return decode(0d);
+	}
+
+	@Override Object decode(Number s) {
+		double dd = s.doubleValue();
+		
+		if (type == double.class || type == Double.class)
+			return s.doubleValue();
+
+		if ((type == int.class || type == Integer.class)
+				&& within(dd, Integer.MIN_VALUE, Integer.MAX_VALUE))
+			return s.intValue();
+
+		if ((type == long.class || type == Long.class) && within(dd, Long.MIN_VALUE, Long.MAX_VALUE))
+			return s.longValue();
+
+		if ((type == byte.class || type == Byte.class) && within(dd, Byte.MIN_VALUE, Byte.MAX_VALUE))
+			return s.byteValue();
+
+		if ((type == short.class || type == Short.class)
+				&& within(dd, Short.MIN_VALUE, Short.MAX_VALUE))
+			return s.shortValue();
+
+		if (type == float.class || type == Float.class)
+			return s.floatValue();
+
+		if (type == BigDecimal.class)
+			return BigDecimal.valueOf(dd);
+
+		if (type == BigInteger.class)
+			return BigInteger.valueOf(s.longValue());
+
+		throw new IllegalArgumentException("Unknown number format: " + type);
+	}
+
+	private boolean within(double s, double minValue, double maxValue) {
+		return s >= minValue && s <= maxValue;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/ObjectHandler.java b/bundleplugin/src/main/java/aQute/lib/json/ObjectHandler.java
new file mode 100644
index 0000000..fc6f6bd
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/ObjectHandler.java
@@ -0,0 +1,147 @@
+package aQute.lib.json;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+public class ObjectHandler extends Handler {
+	@SuppressWarnings("rawtypes")
+	final Class		rawClass;
+	final Field		fields[];
+	final Type		types[];
+	final Object	defaults[];
+	final Field		extra;
+
+	ObjectHandler(JSONCodec codec, Class<?> c) throws Exception {
+		rawClass = c;
+		fields = c.getFields();
+
+		// Sort the fields so the output is canonical
+		Arrays.sort(fields, new Comparator<Field>() {
+			public int compare(Field o1, Field o2) {
+				return o1.getName().compareTo(o2.getName());
+			}
+		});
+
+		types = new Type[fields.length];
+		defaults = new Object[fields.length];
+
+		Field x = null;
+		for (int i = 0; i < fields.length; i++) {
+			if (fields[i].getName().equals("__extra"))
+				x = fields[i];
+			types[i] = fields[i].getGenericType();
+		}
+		if (x != null && Map.class.isAssignableFrom(x.getType()))
+			extra = x;
+		else
+			extra = null;
+		
+		try {
+			Object template = c.newInstance();
+
+			for (int i = 0; i < fields.length; i++) {
+				defaults[i] = fields[i].get(template);
+			}
+		} catch (Exception e) {
+			// Ignore
+		}
+	}
+
+	@Override void encode(Encoder app, Object object, Map<Object, Type> visited) throws Exception {
+		app.append("{");
+		String del = "";
+		for (int i = 0; i < fields.length; i++) {
+			if (fields[i].getName().startsWith("__"))
+				continue;
+
+			Object value = fields[i].get(object);
+			if (!app.writeDefaults) {
+				if (value == defaults[i])
+					continue;
+
+				if (value != null && value.equals(defaults[i]))
+					continue;
+			}
+
+			app.append(del);
+			StringHandler.string(app, fields[i].getName());
+			app.append(":");
+			app.encode(value, types[i], visited);
+			del = ",";
+		}
+		app.append("}");
+	}
+
+	@SuppressWarnings("unchecked") @Override Object decodeObject(Decoder r) throws Exception {
+		assert r.current() == '{';
+		Object targetObject = rawClass.newInstance();
+
+		int c = r.next();
+		while (JSONCodec.START_CHARACTERS.indexOf(c) >= 0) {
+
+			// Get key
+			String key = r.codec.parseString(r);
+
+			// Get separator
+			c = r.skipWs();
+			if (c != ':')
+				throw new IllegalArgumentException("Expected ':' but got " + (char) c);
+
+			c = r.next();
+
+			// Get value
+
+			Field f = getField(key);
+			if (f != null) {
+				// We have a field and thus a type
+				Object value = r.codec.decode(f.getGenericType(), r);
+				f.set(targetObject, value);
+			} else {
+				// No field, but may extra is defined
+				if (extra == null) {
+					if (r.strict)
+						throw new IllegalArgumentException("No such field " + key);
+					Object value = r.codec.decode(null, r);
+					r.getExtra().put(rawClass.getName() + "." + key, value);
+				} else {
+
+					Map<String, Object> map = (Map<String, Object>) extra.get(targetObject);
+					if (map == null) {
+						map = new LinkedHashMap<String, Object>();
+						extra.set(targetObject, map);
+					}
+					Object value = r.codec.decode(null, r);
+					map.put(key, value);
+				}
+			}
+
+			c = r.skipWs();
+
+			if (c == '}')
+				break;
+
+			if (c == ',') {
+				c = r.next();
+				continue;
+			}
+
+			throw new IllegalArgumentException(
+					"Invalid character in parsing object, expected } or , but found " + (char) c);
+		}
+		assert r.current() == '}';
+		r.read(); // skip closing
+		return targetObject;
+	}
+
+	private Field getField(String key) {
+		for (int i = 0; i < fields.length; i++) {
+			int n = key.compareTo(fields[i].getName());
+			if (n == 0)
+				return fields[i];
+			if (n < 0)
+				return null;
+		}
+		return null;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/SpecialHandler.java b/bundleplugin/src/main/java/aQute/lib/json/SpecialHandler.java
new file mode 100644
index 0000000..33bde8f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/SpecialHandler.java
@@ -0,0 +1,44 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.text.*;
+import java.util.*;
+import java.util.regex.*;
+
+public class SpecialHandler extends Handler {
+	@SuppressWarnings("rawtypes")
+	final Class						type;
+	final Method					valueOf;
+	final Constructor< ? >			constructor;
+	final static SimpleDateFormat	sdf	= new SimpleDateFormat();
+
+	public SpecialHandler(Class< ? > type, Constructor< ? > constructor,
+			Method valueOf) {
+		this.type = type;
+		this.constructor = constructor;
+		this.valueOf = valueOf;
+	}
+
+	@Override
+	void encode(Encoder app, Object object, Map<Object, Type> visited)
+			throws IOException, Exception {
+		StringHandler.string(app, object.toString());
+	}
+
+	@Override
+	Object decode(String s) throws Exception {
+		if (type == Pattern.class)
+			return Pattern.compile(s);
+
+		if (constructor != null)
+			return constructor.newInstance(s);
+
+		if (valueOf != null)
+			return valueOf.invoke(null, s);
+
+		throw new IllegalArgumentException("Do not know how to convert a "
+				+ type + " from a string");
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/StringHandler.java b/bundleplugin/src/main/java/aQute/lib/json/StringHandler.java
new file mode 100644
index 0000000..2450ed0
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/StringHandler.java
@@ -0,0 +1,146 @@
+package aQute.lib.json;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+public class StringHandler extends Handler {
+
+	@Override void encode(Encoder app, Object object, Map<Object, Type> visited)
+			throws IOException {
+		string(app, object.toString());
+	}
+
+	static void string(Appendable app, String s) throws IOException {
+
+		app.append('"');
+		for (int i = 0; i < s.length(); i++) {
+			char c = s.charAt(i);
+			switch (c) {
+			case '"':
+				app.append("\\\"");
+				break;
+
+			case '\\':
+				app.append("\\\\");
+				break;
+
+			case '\b':
+				app.append("\\b");
+				break;
+
+			case '\f':
+				app.append("\\f");
+				break;
+
+			case '\n':
+				app.append("\\n");
+				break;
+
+			case '\r':
+				app.append("\\r");
+				break;
+
+			case '\t':
+				app.append("\\t");
+				break;
+
+			default:
+				if (Character.isISOControl(c)) {
+					app.append("\\u");
+					app.append("0123456789ABCDEF".charAt(0xF & (c >> 12)));
+					app.append("0123456789ABCDEF".charAt(0xF & (c >> 8)));
+					app.append("0123456789ABCDEF".charAt(0xF & (c >> 4)));
+					app.append("0123456789ABCDEF".charAt(0xF & (c >> 0)));
+				} else
+					app.append(c);
+			}
+		}
+		app.append('"');
+	}
+
+	Object decode(String s) throws Exception {
+		return s;
+	}
+
+	Object decode(Number s) {
+		return s.toString();
+	}
+
+	Object decode(boolean s) {
+		return Boolean.toString(s);
+	}
+
+	Object decode() {
+		return null;
+	}
+
+	/**
+	 * An object can be assigned to a string. This means that the stream is
+	 * interpreted as the object but stored in its complete in the string.
+	 */
+	Object decodeObject(Decoder r) throws Exception {
+		return collect(r, '}');
+	}
+
+	/**
+	 * An array can be assigned to a string. This means that the stream is
+	 * interpreted as the array but stored in its complete in the string.
+	 */
+	Object decodeArray(Decoder r) throws Exception {
+		return collect(r, ']');
+	}
+
+	/**
+	 * Gather the input until you find the the closing character making sure
+	 * that new blocks are are take care of.
+	 * <p>
+	 * This method parses the input for a complete block so that it can be
+	 * stored in a string. This allows envelopes.
+	 * 
+	 * @param isr
+	 * @param c
+	 * @return
+	 * @throws Exception
+	 */
+	private Object collect(Decoder isr, char close) throws Exception {
+		boolean instring = false;
+		int level = 1;
+		StringBuilder sb = new StringBuilder();
+
+		int c = isr.current();
+		while (c > 0 && level > 0) {
+			sb.append((char) c);
+			if (instring)
+				switch (c) {
+				case '"':
+					instring = true;
+					break;
+
+				case '[':
+				case '{':
+					level++;
+					break;
+
+				case ']':
+				case '}':
+					level--;
+					break;
+				}
+			else
+				switch (c) {
+				case '"':
+					instring = false;
+					break;
+
+				case '\\':
+					sb.append((char) isr.read());
+					break;
+				}
+
+			c = isr.read();
+		}
+		return sb.toString();
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/lib/json/packageinfo b/bundleplugin/src/main/java/aQute/lib/json/packageinfo
new file mode 100644
index 0000000..86528b7
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/json/packageinfo
@@ -0,0 +1 @@
+version 2.4.0