jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From thom...@apache.org
Subject svn commit: r1158602 - in /jackrabbit/sandbox/microkernel/src: main/java/org/apache/jackrabbit/mk/mem/ test/java/org/apache/jackrabbit/mk/index/ test/java/org/apache/jackrabbit/mk/json/
Date Wed, 17 Aug 2011 09:48:51 GMT
Author: thomasm
Date: Wed Aug 17 09:48:50 2011
New Revision: 1158602

URL: http://svn.apache.org/viewvc?rev=1158602&view=rev
Log:
A simple indexing mechanism (work in progress).

Added:
    jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/index/
    jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/index/IndexTest.java
Modified:
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/NodeImpl.java
    jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/json/JsopTest.java

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/NodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/NodeImpl.java?rev=1158602&r1=1158601&r2=1158602&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/NodeImpl.java
(original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/NodeImpl.java
Wed Aug 17 09:48:50 2011
@@ -267,7 +267,9 @@ public class NodeImpl {
                     node.addChildNode(key, false, null, parse(t, revId));
                 } else {
                     String value = t.readRawValue().trim();
-                    node.setProperty(key, value);
+                    if (!key.equals(":childNodeCount")) {
+                        node.setProperty(key, value);
+                    }
                 }
             } while (t.matches(','));
             t.read('}');
@@ -283,4 +285,16 @@ public class NodeImpl {
         return path;
     }
 
+    public String[] getChildNodeNames() {
+        if (childNodes == null || childNodes.size() == 0) {
+            return new String[0];
+        }
+        String[] names = new String[childNodes.size()];
+        int i = 0;
+        for (String c : childNodes.keySet()) {
+            names[i++] = c;
+        }
+        return names;
+    }
+
 }

Added: jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/index/IndexTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/index/IndexTest.java?rev=1158602&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/index/IndexTest.java
(added)
+++ jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/index/IndexTest.java
Wed Aug 17 09:48:50 2011
@@ -0,0 +1,578 @@
+/*
+ * 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.jackrabbit.mk.index;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import org.apache.jackrabbit.mk.MicroKernelFactory;
+import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.mk.json.JsopBuilder;
+import org.apache.jackrabbit.mk.json.JsopTest;
+import org.apache.jackrabbit.mk.json.JsopTokenizer;
+import org.apache.jackrabbit.mk.mem.NodeImpl;
+import org.apache.jackrabbit.mk.util.PathUtils;
+
+/**
+ * An indexing mechanism.
+ */
+public class IndexTest {
+
+    public static void main(String... args) {
+        test("mem:");
+
+        // TODO doesn't work yet (problems with move operations?)
+        // test("fs:{homeDir}/target;clean");
+
+    }
+
+    static void log(String s) {
+        System.out.println(s);
+    }
+
+    private static void test(String url) {
+        MicroKernel mk = MicroKernelFactory.getInstance(url);
+        String head = mk.getHeadRevision();
+        Index index = new Index(mk, "test", "id");
+        for (int i=0; i<1000; i++) {
+            NodeImpl n;
+            n = new NodeImpl(0);
+            n = n.cloneAndSetProperty("id", "" + i, 0);
+            n.setPath("i" + i);
+            log("#insert " + i);
+            index.add(n);
+        }
+        head = mk.getHeadRevision();
+        String tree = mk.getNodes("/index", head, 100, 0, -1);
+        log(JsopTest.format(tree));
+        Cursor c = index.findFirst("1");
+        while (c.hasNext()) {
+            log(c.next());
+        }
+        mk.dispose();
+    }
+
+    static class IndexComparator {
+        String propertyName;
+        IndexComparator(String propertyName) {
+            this.propertyName = propertyName;
+        }
+        boolean isIndexed(NodeImpl a) {
+            return a.hasProperty(propertyName);
+        }
+        int find(NodeImpl n, String[] data) {
+            String v = n.getProperty(propertyName);
+            return Arrays.binarySearch(data, v);
+        }
+        int compare(NodeImpl a, NodeImpl b) {
+            if (a == b) {
+                return 0;
+            }
+            String pa = a.getProperty(propertyName);
+            String pb = b.getProperty(propertyName);
+            if (pa == null) {
+                return pb == null ? 0 : 1;
+            } else if (pb == null) {
+                return -1;
+            }
+            return pa.compareTo(pb);
+        }
+
+        String data(NodeImpl n) {
+            return n.getProperty(propertyName);
+        }
+
+        public String[] remove(String[] data, int pos) {
+            String[] data2 = new String[data.length - 1];
+            System.arraycopy(data, 0, data2, 0, pos);
+            System.arraycopy(data, pos + 1, data2, pos, data.length - pos);
+            return data2;
+        }
+
+    }
+
+    static class Index {
+        MicroKernel mk;
+        String revision;
+        String name;
+        IndexComparator comparator;
+        boolean unique = true;
+        RuntimeException uniqueKeyViolation = new RuntimeException("Unique key violation");
+        private String indexRootNode;
+        private StringBuilder buffer;
+
+        static String[] insert(String[] data, int pos, String d) {
+            String[] data2 = new String[data.length + 1];
+            if (pos > 0 && data.length > 0) {
+                System.arraycopy(data, 0, data2, 0, pos);
+            }
+            data2[pos] = d;
+            if (pos < data.length) {
+                System.arraycopy(data, pos, data2, pos + 1, data.length - pos);
+            }
+            return data2;
+        }
+
+        IndexPage getPage(String parent, String path) {
+            String p;
+            if (parent == null) {
+                parent = "";
+            }
+            p = PathUtils.concat(parent, path);
+            String index = PathUtils.concat(indexRootNode, name);
+            String json = mk.getNodes("/" + PathUtils.concat(index, p), revision);
+            IndexPage page;
+            if (json == null) {
+                page = new IndexLeaf(this, parent);
+            } else {
+                JsopTokenizer t = new JsopTokenizer(json);
+                t.read('{');
+                NodeImpl n = NodeImpl.parse(t, 0);
+                String data = n.getProperty("data");
+                String paths = n.getProperty("paths");
+                String children = n.getProperty("children");
+                if (children != null) {
+                    IndexNode node = new IndexNode(this, parent);
+                    node.data = readArray(data);
+                    node.children = readArray(children);
+                    page = node;
+                } else {
+                    IndexLeaf leaf = new IndexLeaf(this, parent);
+                    leaf.data = readArray(data);
+                    leaf.paths = readArray(paths);
+                    page = leaf;
+                }
+            }
+            page.path = p;
+            return page;
+        }
+
+        static String[] readArray(String json) {
+            if (json == null) {
+                return new String[0];
+            }
+            ArrayList<String> dataList = new ArrayList<String>();
+            JsopTokenizer t = new JsopTokenizer(json);
+            t.read('[');
+            if (!t.matches(']')) {
+                do {
+                    dataList.add(t.readString());
+                } while (t.matches(','));
+                t.read(']');
+            }
+            String[] data = new String[dataList.size()];
+            dataList.toArray(data);
+            return data;
+        }
+
+        Index(MicroKernel mk, String name, String propertyName) {
+            this.mk = mk;
+            this.name = name;
+            comparator = new IndexComparator(propertyName);
+            revision = mk.getHeadRevision();
+            indexRootNode = "index";
+            if (!mk.nodeExists(PathUtils.concat("/" + indexRootNode, name), revision)) {
+                if (!mk.nodeExists("/" + indexRootNode, revision)) {
+                    JsopBuilder jsop = new JsopBuilder();
+                    jsop.append("+ ").key(indexRootNode).append("{}");
+                    revision = mk.commit("/", jsop.toString(), revision, null);
+                }
+                JsopBuilder jsop = new JsopBuilder();
+                jsop.append("+ ").key(name).append("{}");
+                revision = mk.commit("/" + indexRootNode, jsop.toString(), revision, null);
+            }
+        }
+
+        Cursor findFirst(String value) {
+            Cursor c = new Cursor();
+            NodeImpl find = new NodeImpl(0);
+            find.cloneAndSetProperty(comparator.propertyName, value, 0);
+            IndexPage node = getPage(null, "");
+            while (true) {
+                int pos = node.find(find);
+                if (pos >= 0) {
+                    c.pos = pos;
+                } else {
+                    pos = -pos - 1;
+                }
+                if (node instanceof IndexLeaf) {
+                    c.current = (IndexLeaf) node;
+                    break;
+                } else {
+                    node = ((IndexNode) node).getChild(pos);
+                }
+            }
+            return c;
+        }
+
+        void bufferSetArray(String path, String propertyName, String[] data) {
+            JsopBuilder jsop = new JsopBuilder();
+            path = PathUtils.concat(name, path);
+            jsop.append("^ ").key(PathUtils.concat(path, propertyName)).array();
+            for (String d : data) {
+                jsop.value(d);
+            }
+            jsop.endArray();
+            jsop.appendWhitespace("\n");
+            buffer(jsop.toString());
+        }
+
+        void bufferMove(String path, String newPath) {
+            JsopBuilder jsop = new JsopBuilder();
+            jsop.append("> ").key(path).value(newPath);
+            jsop.appendWhitespace("\n");
+            buffer(jsop.toString());
+        }
+
+        void buffer(String diff) {
+            if (buffer == null) {
+                buffer = new StringBuilder(diff.length());
+            }
+            buffer.append(diff);
+        }
+
+        void add(NodeImpl add) {
+            if (!comparator.isIndexed(add)) {
+                return;
+            }
+            IndexNode parent = null;
+            int parentPos = 0;
+            IndexPage n = getPage(null, "");
+            while (true) {
+                if (n.size() >= IndexPage.MAX_SIZE) {
+                    // split
+                    int split = IndexPage.MIN_SIZE;
+                    if (parent == null) {
+                        // new root
+                        IndexNode root = new IndexNode(this, null);
+                        root.children = new String[] { "0", "1" };
+                        root.data = new String[] { n.data[split] };
+                        root.path = "";
+                        n.split(root, "0", split, "1");
+                        n = root;
+                    } else {
+                        String data = n.data[split];
+                        String path = parent.getNextChildPath();
+                        n.split(null, null, split, path);
+                        parent.data = Index.insert(parent.data, parentPos, data);
+                        parent.children = Index.insert(parent.children, parentPos + 1, PathUtils.getName(path));
+                        parent.writeData();
+                        // go back one step (the entry might be in the
+                        // other node now)
+                        n = parent;
+                    }
+                    // subsequent operations are based on the new structure
+                    commit();
+                }
+                if (n instanceof IndexNode) {
+                    IndexNode page = (IndexNode) n;
+                    int pos = page.find(add);
+                    if (pos < 0) {
+                        pos = -pos - 1;
+                    }
+                    parent = page;
+                    parentPos = pos;
+                    n = page.getChild(pos);
+                } else {
+                    IndexLeaf page = (IndexLeaf) n;
+                    int pos = page.find(add);
+                    if (pos < 0) {
+                        pos = -pos - 1;
+                    } else {
+                        if (unique) {
+                            throw uniqueKeyViolation;
+                        }
+                    }
+                    page.insert(pos, add);
+                    page.writeData();
+                    break;
+                }
+            }
+            commit();
+        }
+
+        private void commit() {
+            if (buffer != null) {
+                String jsop = buffer.toString();
+                log(jsop);
+                revision = mk.commit("/" + indexRootNode, jsop, revision, null);
+                buffer = null;
+            }
+        }
+
+        void remove(NodeImpl node) {
+            if (!comparator.isIndexed(node)) {
+                return;
+            }
+        }
+
+        public String getName() {
+            return name;
+        }
+
+    }
+
+    abstract static class IndexPage {
+
+        final static int MIN_SIZE = 2;
+        final static int MAX_SIZE = 2 * MIN_SIZE + 1;
+        protected final Index index;
+
+        // TODO use the name, and keep the parent list in the cursor
+        // paths are not stable because of split / join operations,
+        // and use more space
+        String path;
+
+        String[] data;
+
+        IndexPage(Index index, String path) {
+            this.index = index;
+            this.path = path;
+        }
+
+
+        abstract void writeCreate();
+        abstract void split(IndexNode root, String newPath, int pos, String siblingPath);
+        abstract IndexLeaf firstLeaf();
+
+        int size() {
+            return data.length;
+        }
+
+        int find(NodeImpl n) {
+            return index.comparator.find(n, data);
+        }
+
+    }
+
+    static class IndexLeaf extends IndexPage {
+
+        String[] paths = new String[0];
+
+        IndexLeaf(Index index, String path) {
+            super(index, path);
+        }
+
+        IndexLeaf nextLeaf() {
+            String p = PathUtils.getParentPath(path);
+            String name = PathUtils.getName(p);
+            p = PathUtils.getParentPath(p);
+            IndexNode parent = (IndexNode) index.getPage(p, name);
+            if (parent == null) {
+                return null;
+            }
+            return parent.next(this);
+        }
+
+        IndexLeaf firstLeaf() {
+            return this;
+        }
+
+        void split(IndexNode root, String newPath, int pos, String siblingPath) {
+            if (newPath != null) {
+                index.bufferMove(
+                        PathUtils.concat(index.getName(), path),
+                        "temp");
+                root.writeCreate();
+                index.bufferMove(
+                        "temp",
+                        PathUtils.concat(index.getName(), newPath));
+                path = newPath;
+            }
+            IndexLeaf n2 = new IndexLeaf(index, siblingPath);
+            n2.data = Arrays.copyOfRange(data, pos, data.length, String[].class);
+            data = Arrays.copyOfRange(data, 0, pos, String[].class);
+            n2.paths = Arrays.copyOfRange(paths, pos, paths.length, String[].class);
+            paths = Arrays.copyOfRange(paths, 0, pos, String[].class);
+            writeData();
+            n2.writeCreate();
+        }
+
+        void insert(int pos, NodeImpl n) {
+            data = Index.insert(data, pos, index.comparator.data(n));
+            paths = Index.insert(paths, pos, n.getPath());
+        }
+
+        void writeData() {
+            index.bufferSetArray(path, "data", data);
+            index.bufferSetArray(path, "paths", paths);
+        }
+
+        void writeCreate() {
+            JsopBuilder jsop = new JsopBuilder();
+            jsop.append("+ ").key(PathUtils.concat(index.getName(), path)).object();
+            jsop.key("data").array();
+            for (String d : data) {
+                jsop.value(d);
+            }
+            jsop.endArray();
+            jsop.key("paths").array();
+            for (String d : paths) {
+                jsop.value(d);
+            }
+            jsop.endArray();
+            jsop.endObject();
+            jsop.appendWhitespace("\n");
+            index.buffer(jsop.toString());
+        }
+
+    }
+
+    static class IndexNode extends IndexPage {
+
+        String[] children = new String[0];
+
+        IndexNode(Index index, String path) {
+            super(index, path);
+        }
+
+        void insert(int pos, NodeImpl n) {
+            data = Index.insert(data, pos, n.getPath());
+        }
+
+        public String getNextChildPath() {
+            int max = 0;
+            for(String c : children) {
+                int x = Integer.parseInt(c);
+                if (x > max) {
+                    max = x;
+                }
+            }
+            String p = Integer.toString(max + 1);
+            if (path.length() == 0) {
+                return p;
+            }
+            return path + "/" + p;
+        }
+
+        IndexLeaf firstLeaf() {
+            return index.getPage(path, children[0]).firstLeaf();
+        }
+
+        void split(IndexNode root, String newPath, int pos, String siblingPath) {
+            if (newPath != null) {
+                index.bufferMove(
+                        PathUtils.concat(index.getName(), path),
+                        "temp");
+                root.writeCreate();
+                index.bufferMove(
+                        "temp",
+                        PathUtils.concat(index.getName(), newPath));
+                path = newPath;
+            }
+            IndexNode n2 = new IndexNode(index, siblingPath);
+            n2.data = Arrays.copyOfRange(data, pos + 1, data.length, String[].class);
+            data = Arrays.copyOfRange(data, 0, pos, String[].class);
+            n2.children = Arrays.copyOfRange(children, pos + 1, children.length, String[].class);
+            children = Arrays.copyOfRange(children, 0, pos + 1, String[].class);
+            n2.writeCreate();
+            for (String c : n2.children) {
+                index.bufferMove(
+                        PathUtils.concat(index.getName(), PathUtils.concat(path, c)),
+                        PathUtils.concat(index.getName(), PathUtils.concat(siblingPath, c))
+                );
+            }
+            writeData();
+        }
+
+        IndexLeaf next(IndexPage child) {
+            int i=0;
+            String childName = PathUtils.getName(child.path);
+            for (; i<children.length; i++) {
+                if (children[i].equals(childName)) {
+                    break;
+                }
+            }
+            if (i == children.length - 1) {
+                if (path.length() == 0) {
+                    return null;
+                }
+                String p = PathUtils.getParentPath(path);
+                String name = PathUtils.getName(p);
+                p = PathUtils.getParentPath(p);
+                IndexNode parent = (IndexNode) index.getPage(p, name);
+                return parent.next(this);
+            }
+            return index.getPage(path, children[i + 1]).firstLeaf();
+        }
+
+        public IndexPage getChild(int pos) {
+            return index.getPage(path, children[pos]);
+        }
+
+        void writeData() {
+            index.bufferSetArray(path, "data", data);
+            index.bufferSetArray(path, "children", children);
+        }
+
+        void writeCreate() {
+            JsopBuilder jsop = new JsopBuilder();
+            jsop.append("+ ").key(PathUtils.concat(index.getName(), path)).object();
+            jsop.key("data").array();
+            for (String d : data) {
+                jsop.value(d);
+            }
+            jsop.endArray();
+            // could just use child node list, but then
+            // new children need to be ordered at the right position,
+            // and we would need a way to distinguish empty lists
+            // from a leaf
+            jsop.key("children").array();
+            for (String d : children) {
+                jsop.value(d);
+            }
+            jsop.endArray();
+            jsop.endObject();
+            jsop.appendWhitespace("\n");
+            index.buffer(jsop.toString());
+        }
+
+    }
+
+    static class Cursor implements Iterator<String> {
+
+        IndexLeaf current;
+        int pos;
+
+        public boolean hasNext() {
+            return current != null;
+        }
+
+        public String next() {
+            if (current == null) {
+                return null;
+            }
+            String data = current.data[pos];
+            step();
+            return data;
+        }
+
+        private void step() {
+            pos++;
+            if (pos == current.size()) {
+                pos = 0;
+                current = current.nextLeaf();
+            }
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+}

Modified: jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/json/JsopTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/json/JsopTest.java?rev=1158602&r1=1158601&r2=1158602&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/json/JsopTest.java
(original)
+++ jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/json/JsopTest.java
Wed Aug 17 09:48:50 2011
@@ -94,8 +94,8 @@ public class JsopTest extends TestCase {
         test("/error/1", "nil 1");
         test("/error/", "\"invalid");
         test("- \"test/test\"", "-\"test\\/test\"");
-        test(" {\n\"x\": 1, \"y\": 2\n}\n", "{\"x\":1, \"y\":2}");
-        test(" [\ntrue, false, null\n]\n", "[true, false, null]");
+        test(" {\n\"x\": 1,\n\"y\": 2\n}\n", "{\"x\":1, \"y\":2}");
+        test("[true, false, null]", "[true, false, null]");
         test("\"\"", "\"\"");
         test("\"\\u0003\"", "\"\\u0003\"");
         test("\"\\u0012\"", "\"\\u0012\"");
@@ -103,9 +103,9 @@ public class JsopTest extends TestCase {
         test("\"\\u1234\"", "\"\\u1234\"");
         test("\"-\\\\-\\\"-\\b-\\f-\\n-\\r-\\t\"", "\"-\\\\-\\\"-\\b-\\f-\\n-\\r-\\t\"");
         test("\"-\\b-\\f-\\n-\\r-\\t\"", "\"-\b-\f-\n-\r-\t\"");
-        test(" [\n0, 12, -1, 0.1, -0.1, -2.3e1, 1e+1, 1.e-20\n]\n", "[0,12,-1,0.1,-0.1,-2.3e1,1e+1,1.e-20]");
+        test("[0, 12, -1, 0.1, -0.1, -2.3e1, 1e+1, 1.e-20]", "[0,12,-1,0.1,-0.1,-2.3e1,1e+1,1.e-20]");
         test("\"Hello\"", "\"Hello\"");
-        test(" [\n\n]\n", "[]");
+        test("[]", "[]");
         test(" {\n\n}\n", "{}");
         test(" {\n\"a\": /* test */ 10\n}\n", "{ \"a\": /* test */ 10}");
         test("+ - / ^ ", "+ - / ^");
@@ -139,14 +139,16 @@ public class JsopTest extends TestCase {
         StringBuilder buff = new StringBuilder();
         JsopTokenizer t = new JsopTokenizer(jsop);
         while (true) {
-            prettyPrint(buff, t);
+            prettyPrint(buff, t, "");
             if (t.getTokenType() == JsopTokenizer.END) {
                 return buff.toString();
             }
         }
     }
 
-    static String prettyPrint(StringBuilder buff, JsopTokenizer t) {
+    static String prettyPrint(StringBuilder buff, JsopTokenizer t, String ident) {
+        String space = "";
+        boolean inArray = false;
         while (true) {
             switch (t.read()) {
             case JsopTokenizer.END:
@@ -173,19 +175,26 @@ public class JsopTest extends TestCase {
                 buff.append("/*").append(t.getToken()).append("*/ ");
                 break;
             case '{':
-                buff.append(" {\n");
+                buff.append(" {\n").append(space += ident);
                 break;
             case '}':
-                buff.append("\n}\n");
+                space = space.substring(0, space.length() - ident.length());
+                buff.append('\n').append(space).append("}\n").append(space);
                 break;
             case '[':
-                buff.append(" [\n");
+                inArray = true;
+                buff.append("[");
                 break;
             case ']':
-                buff.append("\n]\n");
+                inArray = false;
+                buff.append("]");
                 break;
             case ',':
-                buff.append(", ");
+                if (!inArray) {
+                    buff.append(",\n").append(space);
+                } else {
+                    buff.append(", ");
+                }
                 break;
             case ':':
                 buff.append(": ");
@@ -261,4 +270,9 @@ public class JsopTest extends TestCase {
         assertEquals("\"back\\\\slash\":\"\\\\\",\"back\\\\\\\\slash\":\"\\\\\\\\\"", buff.toString());
     }
 
+    public static String format(String json) {
+        return prettyPrint(new StringBuilder(),
+                new JsopTokenizer(json), "    ");
+    }
+
 }



Mime
View raw message