You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2017/06/02 11:08:30 UTC

[5/5] karaf git commit: Move karaf's json reader/writer to util

Move karaf's json reader/writer to util

Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/0748a4b4
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/0748a4b4
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/0748a4b4

Branch: refs/heads/master
Commit: 0748a4b4ac993a5c881447121f9ffe127d822a5e
Parents: d5d2516
Author: Guillaume Nodet <gn...@apache.org>
Authored: Fri Jun 2 13:07:15 2017 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Fri Jun 2 13:08:16 2017 +0200

----------------------------------------------------------------------
 features/core/pom.xml                           |   1 +
 .../features/internal/region/DigraphHelper.java |   4 +-
 .../internal/region/OfflineResolver.java        |   2 +-
 .../internal/region/SubsystemResolver.java      |   2 +-
 .../internal/repository/JsonRepository.java     |   2 +-
 .../internal/service/FeaturesServiceImpl.java   |   4 +-
 .../features/internal/service/StateStorage.java |   4 +-
 .../features/internal/util/JsonReader.java      | 352 -------------------
 .../features/internal/util/JsonWriter.java      | 192 ----------
 jms/core/pom.xml                                |   3 +-
 .../ArtemisDestinationSourceFactory.java        |   2 +
 .../apache/karaf/jms/internal/JsonReader.java   | 352 -------------------
 .../org/apache/karaf/util/json/JsonReader.java  | 352 +++++++++++++++++++
 .../org/apache/karaf/util/json/JsonWriter.java  | 192 ++++++++++
 14 files changed, 558 insertions(+), 906 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/0748a4b4/features/core/pom.xml
----------------------------------------------------------------------
diff --git a/features/core/pom.xml b/features/core/pom.xml
index ac2f504..d4f5944 100644
--- a/features/core/pom.xml
+++ b/features/core/pom.xml
@@ -147,6 +147,7 @@
                             org.apache.karaf.util,
                             org.apache.karaf.util.bundles,
                             org.apache.karaf.util.collections,
+                            org.apache.karaf.util.json,
                             org.apache.karaf.util.maven,
                             org.eclipse.equinox.internal.region.*;-split-package:=merge-first,
                             org.apache.felix.resolver.*,

http://git-wip-us.apache.org/repos/asf/karaf/blob/0748a4b4/features/core/src/main/java/org/apache/karaf/features/internal/region/DigraphHelper.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/region/DigraphHelper.java b/features/core/src/main/java/org/apache/karaf/features/internal/region/DigraphHelper.java
index ac7d38b..31056b4 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/region/DigraphHelper.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/region/DigraphHelper.java
@@ -35,8 +35,8 @@ import java.util.Map;
 import java.util.Set;
 
 import org.apache.karaf.features.internal.service.FeaturesServiceImpl;
-import org.apache.karaf.features.internal.util.JsonReader;
-import org.apache.karaf.features.internal.util.JsonWriter;
+import org.apache.karaf.util.json.JsonReader;
+import org.apache.karaf.util.json.JsonWriter;
 import org.eclipse.equinox.internal.region.StandardRegionDigraph;
 import org.eclipse.equinox.region.Region;
 import org.eclipse.equinox.region.RegionDigraph;

http://git-wip-us.apache.org/repos/asf/karaf/blob/0748a4b4/features/core/src/main/java/org/apache/karaf/features/internal/region/OfflineResolver.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/region/OfflineResolver.java b/features/core/src/main/java/org/apache/karaf/features/internal/region/OfflineResolver.java
index cb25236..1ff7ec5 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/region/OfflineResolver.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/region/OfflineResolver.java
@@ -33,7 +33,7 @@ import org.apache.karaf.features.internal.resolver.RequirementImpl;
 import org.apache.karaf.features.internal.resolver.ResourceBuilder;
 import org.apache.karaf.features.internal.resolver.ResourceImpl;
 import org.apache.karaf.features.internal.resolver.SimpleFilter;
-import org.apache.karaf.features.internal.util.JsonReader;
+import org.apache.karaf.util.json.JsonReader;
 import org.osgi.framework.BundleException;
 import org.osgi.resource.Capability;
 import org.osgi.resource.Requirement;

http://git-wip-us.apache.org/repos/asf/karaf/blob/0748a4b4/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java b/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java
index c477f75..b39c965 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java
@@ -37,7 +37,7 @@ import org.apache.karaf.features.internal.resolver.ResolverUtil;
 import org.apache.karaf.features.internal.resolver.ResourceBuilder;
 import org.apache.karaf.features.internal.resolver.ResourceImpl;
 import org.apache.karaf.features.internal.resolver.SimpleFilter;
-import org.apache.karaf.features.internal.util.JsonWriter;
+import org.apache.karaf.util.json.JsonWriter;
 import org.eclipse.equinox.internal.region.StandardRegionDigraph;
 import org.eclipse.equinox.region.Region;
 import org.eclipse.equinox.region.RegionDigraph;

http://git-wip-us.apache.org/repos/asf/karaf/blob/0748a4b4/features/core/src/main/java/org/apache/karaf/features/internal/repository/JsonRepository.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/repository/JsonRepository.java b/features/core/src/main/java/org/apache/karaf/features/internal/repository/JsonRepository.java
index 92d8d73..c105b8d 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/repository/JsonRepository.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/repository/JsonRepository.java
@@ -25,7 +25,7 @@ import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import org.apache.karaf.features.internal.resolver.ResourceBuilder;
-import org.apache.karaf.features.internal.util.JsonReader;
+import org.apache.karaf.util.json.JsonReader;
 import org.osgi.resource.Capability;
 import org.osgi.resource.Requirement;
 import org.osgi.resource.Resource;

http://git-wip-us.apache.org/repos/asf/karaf/blob/0748a4b4/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
index 5d65c03..5f79fba 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
@@ -62,8 +62,8 @@ import org.apache.karaf.features.internal.download.DownloadManager;
 import org.apache.karaf.features.internal.download.DownloadManagers;
 import org.apache.karaf.features.internal.region.DigraphHelper;
 import org.apache.karaf.features.internal.service.BundleInstallSupport.FrameworkInfo;
-import org.apache.karaf.features.internal.util.JsonReader;
-import org.apache.karaf.features.internal.util.JsonWriter;
+import org.apache.karaf.util.json.JsonReader;
+import org.apache.karaf.util.json.JsonWriter;
 import org.apache.karaf.util.collections.CopyOnWriteArrayIdentityList;
 import org.eclipse.equinox.region.RegionDigraph;
 import org.ops4j.pax.url.mvn.MavenResolver;

http://git-wip-us.apache.org/repos/asf/karaf/blob/0748a4b4/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java
index 36cdc69..0d1fa98 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java
@@ -25,8 +25,8 @@ import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
 
-import org.apache.karaf.features.internal.util.JsonReader;
-import org.apache.karaf.features.internal.util.JsonWriter;
+import org.apache.karaf.util.json.JsonReader;
+import org.apache.karaf.util.json.JsonWriter;
 
 @SuppressWarnings({
     "rawtypes"

http://git-wip-us.apache.org/repos/asf/karaf/blob/0748a4b4/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonReader.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonReader.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonReader.java
deleted file mode 100644
index 7ce1b76..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonReader.java
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.karaf.features.internal.util;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- */
-public final class JsonReader {
-
-    //
-    // Implementation
-    //
-
-    private final Reader reader;
-    private final StringBuilder recorder;
-    private int current;
-    private int line = 1;
-    private int column;
-
-    private JsonReader(Reader reader) {
-        this.reader = reader;
-        recorder = new StringBuilder();
-    }
-
-    public static Object read(Reader reader) throws IOException {
-        return new JsonReader(reader).parse();
-    }
-
-    public static Object read(InputStream is) throws IOException {
-        return new JsonReader(new InputStreamReader(is)).parse();
-    }
-
-    private Object parse() throws IOException {
-        read();
-        skipWhiteSpace();
-        Object result = readValue();
-        skipWhiteSpace();
-        if (!endOfText()) {
-            throw error("Unexpected character");
-        }
-        return result;
-    }
-
-    private Object readValue() throws IOException {
-        switch (current) {
-        case 'n':
-            return readNull();
-        case 't':
-            return readTrue();
-        case 'f':
-            return readFalse();
-        case '"':
-            return readString();
-        case '[':
-            return readArray();
-        case '{':
-            return readObject();
-        case '-':
-        case '0':
-        case '1':
-        case '2':
-        case '3':
-        case '4':
-        case '5':
-        case '6':
-        case '7':
-        case '8':
-        case '9':
-            return readNumber();
-        default:
-            throw expected("value");
-        }
-    }
-
-    private Collection<?> readArray() throws IOException {
-        read();
-        Collection<Object> array = new ArrayList<>();
-        skipWhiteSpace();
-        if (readChar(']')) {
-            return array;
-        }
-        do {
-            skipWhiteSpace();
-            array.add(readValue());
-            skipWhiteSpace();
-        } while (readChar(','));
-        if (!readChar(']')) {
-            throw expected("',' or ']'");
-        }
-        return array;
-    }
-
-    private Map<String, Object> readObject() throws IOException {
-        read();
-        Map<String, Object> object = new HashMap<>();
-        skipWhiteSpace();
-        if (readChar('}')) {
-            return object;
-        }
-        do {
-            skipWhiteSpace();
-            String name = readName();
-            skipWhiteSpace();
-            if (!readChar(':')) {
-                throw expected("':'");
-            }
-            skipWhiteSpace();
-            object.put(name, readValue());
-            skipWhiteSpace();
-        } while (readChar(','));
-        if (!readChar('}')) {
-            throw expected("',' or '}'");
-        }
-        return object;
-    }
-
-    private Object readNull() throws IOException {
-        read();
-        readRequiredChar('u');
-        readRequiredChar('l');
-        readRequiredChar('l');
-        return null;
-    }
-
-    private Boolean readTrue() throws IOException {
-        read();
-        readRequiredChar('r');
-        readRequiredChar('u');
-        readRequiredChar('e');
-        return Boolean.TRUE;
-    }
-
-    private Boolean readFalse() throws IOException {
-        read();
-        readRequiredChar('a');
-        readRequiredChar('l');
-        readRequiredChar('s');
-        readRequiredChar('e');
-        return Boolean.FALSE;
-    }
-
-    private void readRequiredChar(char ch) throws IOException {
-        if (!readChar(ch)) {
-            throw expected("'" + ch + "'");
-        }
-    }
-
-    private String readString() throws IOException {
-        read();
-        recorder.setLength(0);
-        while (current != '"') {
-            if (current == '\\') {
-                readEscape();
-            } else if (current < 0x20) {
-                throw expected("valid string character");
-            } else {
-                recorder.append((char) current);
-                read();
-            }
-        }
-        read();
-        return recorder.toString();
-    }
-
-    private void readEscape() throws IOException {
-        read();
-        switch (current) {
-        case '"':
-        case '/':
-        case '\\':
-            recorder.append((char) current);
-            break;
-        case 'b':
-            recorder.append('\b');
-            break;
-        case 'f':
-            recorder.append('\f');
-            break;
-        case 'n':
-            recorder.append('\n');
-            break;
-        case 'r':
-            recorder.append('\r');
-            break;
-        case 't':
-            recorder.append('\t');
-            break;
-        case 'u':
-            char[] hexChars = new char[4];
-            for (int i = 0; i < 4; i++) {
-                read();
-                if (!isHexDigit(current)) {
-                    throw expected("hexadecimal digit");
-                }
-                hexChars[i] = (char) current;
-            }
-            recorder.append((char) Integer.parseInt(String.valueOf(hexChars), 16));
-            break;
-        default:
-            throw expected("valid escape sequence");
-        }
-        read();
-    }
-
-    private Number readNumber() throws IOException {
-        recorder.setLength(0);
-        readAndAppendChar('-');
-        int firstDigit = current;
-        if (!readAndAppendDigit()) {
-            throw expected("digit");
-        }
-        if (firstDigit != '0') {
-            while (readAndAppendDigit()) {
-                // Do nothing
-            }
-        }
-        readFraction();
-        readExponent();
-        return Double.parseDouble(recorder.toString());
-    }
-
-    private boolean readFraction() throws IOException {
-        if (!readAndAppendChar('.')) {
-            return false;
-        }
-        if (!readAndAppendDigit()) {
-            throw expected("digit");
-        }
-        while (readAndAppendDigit()) {
-            // Do nothing
-        }
-        return true;
-    }
-
-    private boolean readExponent() throws IOException {
-        if (!readAndAppendChar('e') && !readAndAppendChar('E')) {
-            return false;
-        }
-        if (!readAndAppendChar('+')) {
-            readAndAppendChar('-');
-        }
-        if (!readAndAppendDigit()) {
-            throw expected("digit");
-        }
-        while (readAndAppendDigit()) {
-            // Do nothing
-        }
-        return true;
-    }
-
-    private String readName() throws IOException {
-        if (current != '"') {
-            throw expected("name");
-        }
-        readString();
-        return recorder.toString();
-    }
-
-    private boolean readAndAppendChar(char ch) throws IOException {
-        if (current != ch) {
-            return false;
-        }
-        recorder.append(ch);
-        read();
-        return true;
-    }
-
-    private boolean readChar(char ch) throws IOException {
-        if (current != ch) {
-            return false;
-        }
-        read();
-        return true;
-    }
-
-    private boolean readAndAppendDigit() throws IOException {
-        if (!isDigit(current)) {
-            return false;
-        }
-        recorder.append((char) current);
-        read();
-        return true;
-    }
-
-    private void skipWhiteSpace() throws IOException {
-        while (isWhiteSpace(current) && !endOfText()) {
-            read();
-        }
-    }
-
-    private void read() throws IOException {
-        if (endOfText()) {
-            throw error("Unexpected end of input");
-        }
-        column++;
-        if (current == '\n') {
-            line++;
-            column = 0;
-        }
-        current = reader.read();
-    }
-
-    private boolean endOfText() {
-        return current == -1;
-    }
-
-    private IOException expected(String expected) {
-        if (endOfText()) {
-            return error("Unexpected end of input");
-        }
-        return error("Expected " + expected);
-    }
-
-    private IOException error(String message) {
-        return new IOException(message + " at " + line + ":" + column);
-    }
-
-    private static boolean isWhiteSpace(int ch) {
-        return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
-    }
-
-    private static boolean isDigit(int ch) {
-        return ch >= '0' && ch <= '9';
-    }
-
-    private static boolean isHexDigit(int ch) {
-        return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'f' || ch >= 'A' && ch <= 'F';
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0748a4b4/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
deleted file mode 100644
index 71c4b9f..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.karaf.features.internal.util;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Map;
-
-/**
- */
-public final class JsonWriter {
-
-    private JsonWriter() {
-    }
-
-    public static void write(OutputStream stream, Object value) throws IOException {
-        write(stream, value, false);
-    }
-
-    public static void write(OutputStream stream, Object value, boolean format) throws IOException {
-        Writer writer = new OutputStreamWriter(stream);
-        write(writer, value, format);
-        writer.flush();
-    }
-
-    public static void write(Writer writer, Object value) throws IOException {
-        write(writer, value, false);
-    }
-
-    public static void write(Writer writer, Object value, boolean format) throws IOException {
-        int indent = format ? 0 : -1;
-        write(writer, value, indent);
-    }
-
-    @SuppressWarnings("rawtypes")
-    private static void write(Writer writer, Object value, int indent) throws IOException {
-        if (value instanceof Map) {
-            writeObject(writer, (Map) value, indent);
-        } else if (value instanceof Collection) {
-            writeArray(writer, (Collection) value, indent);
-        } else if (value instanceof Number) {
-            writeNumber(writer, (Number) value);
-        } else if (value instanceof String) {
-            writeString(writer, (String) value);
-        } else if (value instanceof Boolean) {
-            writeBoolean(writer, (Boolean) value);
-        } else if (value == null) {
-            writeNull(writer);
-        } else {
-            throw new IllegalArgumentException("Unsupported value: " + value);
-        }
-    }
-
-    @SuppressWarnings("rawtypes")
-    private static void writeObject(Writer writer, Map<?, ?> value, int indent) throws IOException {
-        writer.append('{');
-        boolean first = true;
-        for (Map.Entry entry : value.entrySet()) {
-            if (!first) {
-                writer.append(',');
-            } else {
-                first = false;
-            }
-            if (indent >= 0) {
-                indent(writer, indent + 1);
-            }
-            writeString(writer, (String) entry.getKey());
-            if (indent >= 0) {
-                writer.append(' ');
-            }
-            writer.append(':');
-            if (indent >= 0) {
-                writer.append(' ');
-            }
-            write(writer, entry.getValue(), indent >= 0 ? indent + 1 : -1);
-        }
-        if (indent >= 0) {
-            indent(writer, indent);
-        }
-        writer.append('}');
-    }
-
-    private static void writeString(Writer writer, String value) throws IOException {
-        writer.append('"');
-        for (int i = 0; i < value.length(); i++) {
-            char c = value.charAt(i);
-            switch (c) {
-            case '\"':
-                writer.append("\\\"");
-                break;
-            case '\\':
-                writer.append("\\\\");
-                break;
-            case '\b':
-                writer.append("\\b");
-                break;
-            case '\f':
-                writer.append("\\f");
-                break;
-            case '\n':
-                writer.append("\\n");
-                break;
-            case '\r':
-                writer.append("\\r");
-                break;
-            case '\t':
-                writer.append("\\t");
-                break;
-            default:
-                if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) {
-                    String s = Integer.toHexString(c);
-                    writer.append('\\');
-                    writer.append('u');
-                    for (int j = s.length(); j < 4; j++) {
-                        writer.append('0');
-                    }
-                    writer.append(s);
-                } else {
-                    writer.append(c);
-                }
-                break;
-            }
-        }
-        writer.append('"');
-    }
-
-    private static void writeNumber(Writer writer, Number value) throws IOException {
-        writer.append(value.toString());
-    }
-
-    private static void writeBoolean(Writer writer, Boolean value) throws IOException {
-        writer.append(Boolean.toString(value));
-    }
-
-    private static void writeArray(Writer writer, Collection<?> value, int indent) throws IOException {
-        writer.append('[');
-        boolean first = true;
-        for (Object obj : value) {
-            if (!first) {
-                writer.append(',');
-            } else {
-                first = false;
-            }
-            if (indent >= 0) {
-                indent(writer, indent + 1);
-            }
-            write(writer, obj, indent + 1);
-        }
-        if (indent >= 0) {
-            indent(writer, indent);
-        }
-        writer.append(']');
-    }
-
-    private static void writeNull(Writer writer) throws IOException {
-        writer.append("null");
-    }
-
-    static char[] INDENT;
-    static {
-        INDENT = new char[1];
-        Arrays.fill(INDENT, '\t');
-    }
-
-    private static void indent(Writer writer, int indent) throws IOException {
-        writer.write("\n");
-        while (indent > INDENT.length) {
-            char[] a = new char[INDENT.length * 2];
-            Arrays.fill(a, '\t');
-            INDENT = a;
-        }
-        writer.write(INDENT, 0, indent);
-    }
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0748a4b4/jms/core/pom.xml
----------------------------------------------------------------------
diff --git a/jms/core/pom.xml b/jms/core/pom.xml
index 22da830..8d2d485 100644
--- a/jms/core/pom.xml
+++ b/jms/core/pom.xml
@@ -111,7 +111,8 @@
                             org.apache.karaf.jms.command.completers,
                             org.apache.karaf.jms.internal,
                             org.apache.karaf.jms.internal.osgi,
-                            org.apache.karaf.util
+                            org.apache.karaf.util,
+                            org.apache.karaf.util.json
                         </Private-Package>
                     </instructions>
                 </configuration>

http://git-wip-us.apache.org/repos/asf/karaf/blob/0748a4b4/jms/core/src/main/java/org/apache/karaf/jms/internal/ArtemisDestinationSourceFactory.java
----------------------------------------------------------------------
diff --git a/jms/core/src/main/java/org/apache/karaf/jms/internal/ArtemisDestinationSourceFactory.java b/jms/core/src/main/java/org/apache/karaf/jms/internal/ArtemisDestinationSourceFactory.java
index 3659a23..b3ea458 100644
--- a/jms/core/src/main/java/org/apache/karaf/jms/internal/ArtemisDestinationSourceFactory.java
+++ b/jms/core/src/main/java/org/apache/karaf/jms/internal/ArtemisDestinationSourceFactory.java
@@ -16,6 +16,8 @@
  */
 package org.apache.karaf.jms.internal;
 
+import org.apache.karaf.util.json.JsonReader;
+
 import javax.jms.Connection;
 import javax.jms.JMSException;
 import javax.jms.Message;

http://git-wip-us.apache.org/repos/asf/karaf/blob/0748a4b4/jms/core/src/main/java/org/apache/karaf/jms/internal/JsonReader.java
----------------------------------------------------------------------
diff --git a/jms/core/src/main/java/org/apache/karaf/jms/internal/JsonReader.java b/jms/core/src/main/java/org/apache/karaf/jms/internal/JsonReader.java
deleted file mode 100644
index 6c08a19..0000000
--- a/jms/core/src/main/java/org/apache/karaf/jms/internal/JsonReader.java
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.karaf.jms.internal;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- */
-public final class JsonReader {
-
-    //
-    // Implementation
-    //
-
-    private final Reader reader;
-    private final StringBuilder recorder;
-    private int current;
-    private int line = 1;
-    private int column;
-
-    private JsonReader(Reader reader) {
-        this.reader = reader;
-        recorder = new StringBuilder();
-    }
-
-    public static Object read(Reader reader) throws IOException {
-        return new JsonReader(reader).parse();
-    }
-
-    public static Object read(InputStream is) throws IOException {
-        return new JsonReader(new InputStreamReader(is)).parse();
-    }
-
-    private Object parse() throws IOException {
-        read();
-        skipWhiteSpace();
-        Object result = readValue();
-        skipWhiteSpace();
-        if (!endOfText()) {
-            throw error("Unexpected character");
-        }
-        return result;
-    }
-
-    private Object readValue() throws IOException {
-        switch (current) {
-        case 'n':
-            return readNull();
-        case 't':
-            return readTrue();
-        case 'f':
-            return readFalse();
-        case '"':
-            return readString();
-        case '[':
-            return readArray();
-        case '{':
-            return readObject();
-        case '-':
-        case '0':
-        case '1':
-        case '2':
-        case '3':
-        case '4':
-        case '5':
-        case '6':
-        case '7':
-        case '8':
-        case '9':
-            return readNumber();
-        default:
-            throw expected("value");
-        }
-    }
-
-    private Collection<?> readArray() throws IOException {
-        read();
-        Collection<Object> array = new ArrayList<>();
-        skipWhiteSpace();
-        if (readChar(']')) {
-            return array;
-        }
-        do {
-            skipWhiteSpace();
-            array.add(readValue());
-            skipWhiteSpace();
-        } while (readChar(','));
-        if (!readChar(']')) {
-            throw expected("',' or ']'");
-        }
-        return array;
-    }
-
-    private Map<String, Object> readObject() throws IOException {
-        read();
-        Map<String, Object> object = new HashMap<>();
-        skipWhiteSpace();
-        if (readChar('}')) {
-            return object;
-        }
-        do {
-            skipWhiteSpace();
-            String name = readName();
-            skipWhiteSpace();
-            if (!readChar(':')) {
-                throw expected("':'");
-            }
-            skipWhiteSpace();
-            object.put(name, readValue());
-            skipWhiteSpace();
-        } while (readChar(','));
-        if (!readChar('}')) {
-            throw expected("',' or '}'");
-        }
-        return object;
-    }
-
-    private Object readNull() throws IOException {
-        read();
-        readRequiredChar('u');
-        readRequiredChar('l');
-        readRequiredChar('l');
-        return null;
-    }
-
-    private Boolean readTrue() throws IOException {
-        read();
-        readRequiredChar('r');
-        readRequiredChar('u');
-        readRequiredChar('e');
-        return Boolean.TRUE;
-    }
-
-    private Boolean readFalse() throws IOException {
-        read();
-        readRequiredChar('a');
-        readRequiredChar('l');
-        readRequiredChar('s');
-        readRequiredChar('e');
-        return Boolean.FALSE;
-    }
-
-    private void readRequiredChar(char ch) throws IOException {
-        if (!readChar(ch)) {
-            throw expected("'" + ch + "'");
-        }
-    }
-
-    private String readString() throws IOException {
-        read();
-        recorder.setLength(0);
-        while (current != '"') {
-            if (current == '\\') {
-                readEscape();
-            } else if (current < 0x20) {
-                throw expected("valid string character");
-            } else {
-                recorder.append((char) current);
-                read();
-            }
-        }
-        read();
-        return recorder.toString();
-    }
-
-    private void readEscape() throws IOException {
-        read();
-        switch (current) {
-        case '"':
-        case '/':
-        case '\\':
-            recorder.append((char) current);
-            break;
-        case 'b':
-            recorder.append('\b');
-            break;
-        case 'f':
-            recorder.append('\f');
-            break;
-        case 'n':
-            recorder.append('\n');
-            break;
-        case 'r':
-            recorder.append('\r');
-            break;
-        case 't':
-            recorder.append('\t');
-            break;
-        case 'u':
-            char[] hexChars = new char[4];
-            for (int i = 0; i < 4; i++) {
-                read();
-                if (!isHexDigit(current)) {
-                    throw expected("hexadecimal digit");
-                }
-                hexChars[i] = (char) current;
-            }
-            recorder.append((char) Integer.parseInt(String.valueOf(hexChars), 16));
-            break;
-        default:
-            throw expected("valid escape sequence");
-        }
-        read();
-    }
-
-    private Number readNumber() throws IOException {
-        recorder.setLength(0);
-        readAndAppendChar('-');
-        int firstDigit = current;
-        if (!readAndAppendDigit()) {
-            throw expected("digit");
-        }
-        if (firstDigit != '0') {
-            while (readAndAppendDigit()) {
-                // Do nothing
-            }
-        }
-        readFraction();
-        readExponent();
-        return Double.parseDouble(recorder.toString());
-    }
-
-    private boolean readFraction() throws IOException {
-        if (!readAndAppendChar('.')) {
-            return false;
-        }
-        if (!readAndAppendDigit()) {
-            throw expected("digit");
-        }
-        while (readAndAppendDigit()) {
-            // Do nothing
-        }
-        return true;
-    }
-
-    private boolean readExponent() throws IOException {
-        if (!readAndAppendChar('e') && !readAndAppendChar('E')) {
-            return false;
-        }
-        if (!readAndAppendChar('+')) {
-            readAndAppendChar('-');
-        }
-        if (!readAndAppendDigit()) {
-            throw expected("digit");
-        }
-        while (readAndAppendDigit()) {
-            // Do nothing
-        }
-        return true;
-    }
-
-    private String readName() throws IOException {
-        if (current != '"') {
-            throw expected("name");
-        }
-        readString();
-        return recorder.toString();
-    }
-
-    private boolean readAndAppendChar(char ch) throws IOException {
-        if (current != ch) {
-            return false;
-        }
-        recorder.append(ch);
-        read();
-        return true;
-    }
-
-    private boolean readChar(char ch) throws IOException {
-        if (current != ch) {
-            return false;
-        }
-        read();
-        return true;
-    }
-
-    private boolean readAndAppendDigit() throws IOException {
-        if (!isDigit(current)) {
-            return false;
-        }
-        recorder.append((char) current);
-        read();
-        return true;
-    }
-
-    private void skipWhiteSpace() throws IOException {
-        while (isWhiteSpace(current) && !endOfText()) {
-            read();
-        }
-    }
-
-    private void read() throws IOException {
-        if (endOfText()) {
-            throw error("Unexpected end of input");
-        }
-        column++;
-        if (current == '\n') {
-            line++;
-            column = 0;
-        }
-        current = reader.read();
-    }
-
-    private boolean endOfText() {
-        return current == -1;
-    }
-
-    private IOException expected(String expected) {
-        if (endOfText()) {
-            return error("Unexpected end of input");
-        }
-        return error("Expected " + expected);
-    }
-
-    private IOException error(String message) {
-        return new IOException(message + " at " + line + ":" + column);
-    }
-
-    private static boolean isWhiteSpace(int ch) {
-        return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
-    }
-
-    private static boolean isDigit(int ch) {
-        return ch >= '0' && ch <= '9';
-    }
-
-    private static boolean isHexDigit(int ch) {
-        return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'f' || ch >= 'A' && ch <= 'F';
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0748a4b4/util/src/main/java/org/apache/karaf/util/json/JsonReader.java
----------------------------------------------------------------------
diff --git a/util/src/main/java/org/apache/karaf/util/json/JsonReader.java b/util/src/main/java/org/apache/karaf/util/json/JsonReader.java
new file mode 100644
index 0000000..b629eaf
--- /dev/null
+++ b/util/src/main/java/org/apache/karaf/util/json/JsonReader.java
@@ -0,0 +1,352 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.util.json;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ */
+public final class JsonReader {
+
+    //
+    // Implementation
+    //
+
+    private final Reader reader;
+    private final StringBuilder recorder;
+    private int current;
+    private int line = 1;
+    private int column;
+
+    private JsonReader(Reader reader) {
+        this.reader = reader;
+        recorder = new StringBuilder();
+    }
+
+    public static Object read(Reader reader) throws IOException {
+        return new JsonReader(reader).parse();
+    }
+
+    public static Object read(InputStream is) throws IOException {
+        return new JsonReader(new InputStreamReader(is)).parse();
+    }
+
+    private Object parse() throws IOException {
+        read();
+        skipWhiteSpace();
+        Object result = readValue();
+        skipWhiteSpace();
+        if (!endOfText()) {
+            throw error("Unexpected character");
+        }
+        return result;
+    }
+
+    private Object readValue() throws IOException {
+        switch (current) {
+        case 'n':
+            return readNull();
+        case 't':
+            return readTrue();
+        case 'f':
+            return readFalse();
+        case '"':
+            return readString();
+        case '[':
+            return readArray();
+        case '{':
+            return readObject();
+        case '-':
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+            return readNumber();
+        default:
+            throw expected("value");
+        }
+    }
+
+    private Collection<?> readArray() throws IOException {
+        read();
+        Collection<Object> array = new ArrayList<>();
+        skipWhiteSpace();
+        if (readChar(']')) {
+            return array;
+        }
+        do {
+            skipWhiteSpace();
+            array.add(readValue());
+            skipWhiteSpace();
+        } while (readChar(','));
+        if (!readChar(']')) {
+            throw expected("',' or ']'");
+        }
+        return array;
+    }
+
+    private Map<String, Object> readObject() throws IOException {
+        read();
+        Map<String, Object> object = new HashMap<>();
+        skipWhiteSpace();
+        if (readChar('}')) {
+            return object;
+        }
+        do {
+            skipWhiteSpace();
+            String name = readName();
+            skipWhiteSpace();
+            if (!readChar(':')) {
+                throw expected("':'");
+            }
+            skipWhiteSpace();
+            object.put(name, readValue());
+            skipWhiteSpace();
+        } while (readChar(','));
+        if (!readChar('}')) {
+            throw expected("',' or '}'");
+        }
+        return object;
+    }
+
+    private Object readNull() throws IOException {
+        read();
+        readRequiredChar('u');
+        readRequiredChar('l');
+        readRequiredChar('l');
+        return null;
+    }
+
+    private Boolean readTrue() throws IOException {
+        read();
+        readRequiredChar('r');
+        readRequiredChar('u');
+        readRequiredChar('e');
+        return Boolean.TRUE;
+    }
+
+    private Boolean readFalse() throws IOException {
+        read();
+        readRequiredChar('a');
+        readRequiredChar('l');
+        readRequiredChar('s');
+        readRequiredChar('e');
+        return Boolean.FALSE;
+    }
+
+    private void readRequiredChar(char ch) throws IOException {
+        if (!readChar(ch)) {
+            throw expected("'" + ch + "'");
+        }
+    }
+
+    private String readString() throws IOException {
+        read();
+        recorder.setLength(0);
+        while (current != '"') {
+            if (current == '\\') {
+                readEscape();
+            } else if (current < 0x20) {
+                throw expected("valid string character");
+            } else {
+                recorder.append((char) current);
+                read();
+            }
+        }
+        read();
+        return recorder.toString();
+    }
+
+    private void readEscape() throws IOException {
+        read();
+        switch (current) {
+        case '"':
+        case '/':
+        case '\\':
+            recorder.append((char) current);
+            break;
+        case 'b':
+            recorder.append('\b');
+            break;
+        case 'f':
+            recorder.append('\f');
+            break;
+        case 'n':
+            recorder.append('\n');
+            break;
+        case 'r':
+            recorder.append('\r');
+            break;
+        case 't':
+            recorder.append('\t');
+            break;
+        case 'u':
+            char[] hexChars = new char[4];
+            for (int i = 0; i < 4; i++) {
+                read();
+                if (!isHexDigit(current)) {
+                    throw expected("hexadecimal digit");
+                }
+                hexChars[i] = (char) current;
+            }
+            recorder.append((char) Integer.parseInt(String.valueOf(hexChars), 16));
+            break;
+        default:
+            throw expected("valid escape sequence");
+        }
+        read();
+    }
+
+    private Number readNumber() throws IOException {
+        recorder.setLength(0);
+        readAndAppendChar('-');
+        int firstDigit = current;
+        if (!readAndAppendDigit()) {
+            throw expected("digit");
+        }
+        if (firstDigit != '0') {
+            while (readAndAppendDigit()) {
+                // Do nothing
+            }
+        }
+        readFraction();
+        readExponent();
+        return Double.parseDouble(recorder.toString());
+    }
+
+    private boolean readFraction() throws IOException {
+        if (!readAndAppendChar('.')) {
+            return false;
+        }
+        if (!readAndAppendDigit()) {
+            throw expected("digit");
+        }
+        while (readAndAppendDigit()) {
+            // Do nothing
+        }
+        return true;
+    }
+
+    private boolean readExponent() throws IOException {
+        if (!readAndAppendChar('e') && !readAndAppendChar('E')) {
+            return false;
+        }
+        if (!readAndAppendChar('+')) {
+            readAndAppendChar('-');
+        }
+        if (!readAndAppendDigit()) {
+            throw expected("digit");
+        }
+        while (readAndAppendDigit()) {
+            // Do nothing
+        }
+        return true;
+    }
+
+    private String readName() throws IOException {
+        if (current != '"') {
+            throw expected("name");
+        }
+        readString();
+        return recorder.toString();
+    }
+
+    private boolean readAndAppendChar(char ch) throws IOException {
+        if (current != ch) {
+            return false;
+        }
+        recorder.append(ch);
+        read();
+        return true;
+    }
+
+    private boolean readChar(char ch) throws IOException {
+        if (current != ch) {
+            return false;
+        }
+        read();
+        return true;
+    }
+
+    private boolean readAndAppendDigit() throws IOException {
+        if (!isDigit(current)) {
+            return false;
+        }
+        recorder.append((char) current);
+        read();
+        return true;
+    }
+
+    private void skipWhiteSpace() throws IOException {
+        while (isWhiteSpace(current) && !endOfText()) {
+            read();
+        }
+    }
+
+    private void read() throws IOException {
+        if (endOfText()) {
+            throw error("Unexpected end of input");
+        }
+        column++;
+        if (current == '\n') {
+            line++;
+            column = 0;
+        }
+        current = reader.read();
+    }
+
+    private boolean endOfText() {
+        return current == -1;
+    }
+
+    private IOException expected(String expected) {
+        if (endOfText()) {
+            return error("Unexpected end of input");
+        }
+        return error("Expected " + expected);
+    }
+
+    private IOException error(String message) {
+        return new IOException(message + " at " + line + ":" + column);
+    }
+
+    private static boolean isWhiteSpace(int ch) {
+        return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
+    }
+
+    private static boolean isDigit(int ch) {
+        return ch >= '0' && ch <= '9';
+    }
+
+    private static boolean isHexDigit(int ch) {
+        return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'f' || ch >= 'A' && ch <= 'F';
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/0748a4b4/util/src/main/java/org/apache/karaf/util/json/JsonWriter.java
----------------------------------------------------------------------
diff --git a/util/src/main/java/org/apache/karaf/util/json/JsonWriter.java b/util/src/main/java/org/apache/karaf/util/json/JsonWriter.java
new file mode 100644
index 0000000..35c8f12
--- /dev/null
+++ b/util/src/main/java/org/apache/karaf/util/json/JsonWriter.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.util.json;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ */
+public final class JsonWriter {
+
+    private JsonWriter() {
+    }
+
+    public static void write(OutputStream stream, Object value) throws IOException {
+        write(stream, value, false);
+    }
+
+    public static void write(OutputStream stream, Object value, boolean format) throws IOException {
+        Writer writer = new OutputStreamWriter(stream);
+        write(writer, value, format);
+        writer.flush();
+    }
+
+    public static void write(Writer writer, Object value) throws IOException {
+        write(writer, value, false);
+    }
+
+    public static void write(Writer writer, Object value, boolean format) throws IOException {
+        int indent = format ? 0 : -1;
+        write(writer, value, indent);
+    }
+
+    @SuppressWarnings("rawtypes")
+    private static void write(Writer writer, Object value, int indent) throws IOException {
+        if (value instanceof Map) {
+            writeObject(writer, (Map) value, indent);
+        } else if (value instanceof Collection) {
+            writeArray(writer, (Collection) value, indent);
+        } else if (value instanceof Number) {
+            writeNumber(writer, (Number) value);
+        } else if (value instanceof String) {
+            writeString(writer, (String) value);
+        } else if (value instanceof Boolean) {
+            writeBoolean(writer, (Boolean) value);
+        } else if (value == null) {
+            writeNull(writer);
+        } else {
+            throw new IllegalArgumentException("Unsupported value: " + value);
+        }
+    }
+
+    @SuppressWarnings("rawtypes")
+    private static void writeObject(Writer writer, Map<?, ?> value, int indent) throws IOException {
+        writer.append('{');
+        boolean first = true;
+        for (Map.Entry entry : value.entrySet()) {
+            if (!first) {
+                writer.append(',');
+            } else {
+                first = false;
+            }
+            if (indent >= 0) {
+                indent(writer, indent + 1);
+            }
+            writeString(writer, (String) entry.getKey());
+            if (indent >= 0) {
+                writer.append(' ');
+            }
+            writer.append(':');
+            if (indent >= 0) {
+                writer.append(' ');
+            }
+            write(writer, entry.getValue(), indent >= 0 ? indent + 1 : -1);
+        }
+        if (indent >= 0) {
+            indent(writer, indent);
+        }
+        writer.append('}');
+    }
+
+    private static void writeString(Writer writer, String value) throws IOException {
+        writer.append('"');
+        for (int i = 0; i < value.length(); i++) {
+            char c = value.charAt(i);
+            switch (c) {
+            case '\"':
+                writer.append("\\\"");
+                break;
+            case '\\':
+                writer.append("\\\\");
+                break;
+            case '\b':
+                writer.append("\\b");
+                break;
+            case '\f':
+                writer.append("\\f");
+                break;
+            case '\n':
+                writer.append("\\n");
+                break;
+            case '\r':
+                writer.append("\\r");
+                break;
+            case '\t':
+                writer.append("\\t");
+                break;
+            default:
+                if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) {
+                    String s = Integer.toHexString(c);
+                    writer.append('\\');
+                    writer.append('u');
+                    for (int j = s.length(); j < 4; j++) {
+                        writer.append('0');
+                    }
+                    writer.append(s);
+                } else {
+                    writer.append(c);
+                }
+                break;
+            }
+        }
+        writer.append('"');
+    }
+
+    private static void writeNumber(Writer writer, Number value) throws IOException {
+        writer.append(value.toString());
+    }
+
+    private static void writeBoolean(Writer writer, Boolean value) throws IOException {
+        writer.append(Boolean.toString(value));
+    }
+
+    private static void writeArray(Writer writer, Collection<?> value, int indent) throws IOException {
+        writer.append('[');
+        boolean first = true;
+        for (Object obj : value) {
+            if (!first) {
+                writer.append(',');
+            } else {
+                first = false;
+            }
+            if (indent >= 0) {
+                indent(writer, indent + 1);
+            }
+            write(writer, obj, indent + 1);
+        }
+        if (indent >= 0) {
+            indent(writer, indent);
+        }
+        writer.append(']');
+    }
+
+    private static void writeNull(Writer writer) throws IOException {
+        writer.append("null");
+    }
+
+    static char[] INDENT;
+    static {
+        INDENT = new char[1];
+        Arrays.fill(INDENT, '\t');
+    }
+
+    private static void indent(Writer writer, int indent) throws IOException {
+        writer.write("\n");
+        while (indent > INDENT.length) {
+            char[] a = new char[INDENT.length * 2];
+            Arrays.fill(a, '\t');
+            INDENT = a;
+        }
+        writer.write(INDENT, 0, indent);
+    }
+}