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/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