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