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