You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by md...@apache.org on 2011/10/29 02:18:08 UTC

svn commit: r1190707 [1/2] - in /jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src: main/java/org/apache/jackrabbit/spi2microkernel/json/ test/java/org/apache/jackrabbit/spi2microkernel/json/ test/resources/

Author: mduerig
Date: Sat Oct 29 00:18:05 2011
New Revision: 1190707

URL: http://svn.apache.org/viewvc?rev=1190707&view=rev
Log:
Microkernel based Jackrabbit prototype (WIP)
Add Json parser

Added:
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/DefaultJsonTokenizer.java
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/FullJsonParser.java
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonHandler.java
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonParser.java
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonTokenizer.java
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonValue.java
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/LevelOrderJsonParser.java
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/ParseException.java
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/Token.java
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/UnescapingJsonTokenizer.java
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/java/org/apache/jackrabbit/spi2microkernel/json/
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/java/org/apache/jackrabbit/spi2microkernel/json/DefaultJsonTokenizerTest.java
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/java/org/apache/jackrabbit/spi2microkernel/json/JsonParserTest.java
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/java/org/apache/jackrabbit/spi2microkernel/json/UnescapingJsonTokenizerTest.java
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/resources/test1.json
    jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/resources/test2.json

Added: jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/DefaultJsonTokenizer.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/DefaultJsonTokenizer.java?rev=1190707&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/DefaultJsonTokenizer.java (added)
+++ jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/DefaultJsonTokenizer.java Sat Oct 29 00:18:05 2011
@@ -0,0 +1,152 @@
+/*
+ * 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.spi2microkernel.json;
+
+import org.apache.jackrabbit.spi2microkernel.json.Token.Type;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DefaultJsonTokenizer extends JsonTokenizer {
+    private final String json;
+
+    private int pos;
+
+    public DefaultJsonTokenizer(String json) {
+        this.json = json;
+    }
+
+    protected DefaultJsonTokenizer(DefaultJsonTokenizer tokenizer) {
+        super(tokenizer);
+        json = tokenizer.json;
+        pos = tokenizer.pos;
+    }
+
+    @Override
+    protected Token nextToken() {
+        skipWhiteSpace();
+        if (pos < json.length()) {
+            switch (json.charAt(pos)) {
+                case '{': return createToken(Type.BEGIN_OBJECT, "{", pos++);
+                case '}': return createToken(Type.END_OBJECT, "}", pos++);
+                case '[': return createToken(Type.BEGIN_ARRAY, "[", pos++);
+                case ']': return createToken(Type.END_ARRAY, "]", pos++);
+                case ':': return createToken(Type.COLON, ":", pos++);
+                case ',': return createToken(Type.COMMA, ",", pos++);
+                case 't': return readLiteral(Type.TRUE, "true");
+                case 'f': return readLiteral(Type.FALSE, "false");
+                case 'n': return readLiteral(Type.NULL, "null");
+                case '"': return readString();
+                default:  return readNumber();
+            }
+        }
+        else {
+            return createToken(Type.EOF, "", pos);
+        }
+    }
+
+    @Override
+    public int pos() {
+        return peek().pos();
+    }
+
+    @Override
+    public void setPos(int pos) {
+        currentToken = null;
+        this.pos = pos;
+    }
+
+    @Override
+    public String toString() {
+        return (currentToken == null ? "" : currentToken) + " " + json.substring(pos);
+    }
+
+    @Override
+    public JsonTokenizer copy() {
+        return new DefaultJsonTokenizer(this);
+    }
+
+    //------------------------------------------< protected >---
+
+    protected void skipWhiteSpace() {
+        while (pos < json.length() && Character.isWhitespace(json.charAt(pos))) {
+            pos++;
+        }
+    }
+
+    protected Token createToken(Type type, String text, int pos) {
+        return new Token(type, text, pos);
+    }
+
+    protected Token readLiteral(Type type, String text) {
+        if (json.substring(pos).startsWith(text)) {
+            Token token = createToken(type, text, pos);
+            pos += text.length();
+            return token;
+        }
+        else {
+            throw new ParseException(pos, "Expected '" + text + ",' found: " + excerpt(json, pos, 40));
+        }
+    }
+
+    protected Token readString() {
+        int i;
+        boolean found = false;
+        boolean even = true;
+
+        // starting at pos + 1, find index i of the first quote character in json which
+        // is preceded by an even number of backslash characters
+        for (i = pos + 1; i < json.length() && !(found = json.charAt(i) == '"' && even); i++) {
+            even = json.charAt(i) != '\\' || !even;
+        }
+
+        if (found) {
+            String text = json.substring(pos + 1, i);
+            Token token = createToken(Type.STRING, text, pos);
+            pos = i + 1;
+            return token;
+        }
+        else {
+            throw new ParseException(pos, "Expected string, found. " + excerpt(json, pos, 40));
+        }
+    }
+    
+    private static final Pattern NUMBER_PATTERN = Pattern.compile(
+            "(\\+|-)?(\\d+)((\\.)(\\d+))?(((e|E)(\\+|-)?)(\\d+))?");
+
+    protected Token readNumber() {
+        Matcher matcher = NUMBER_PATTERN.matcher(json.substring(pos));
+        if (matcher.lookingAt()) {
+            Token token = createToken(Type.NUMBER, matcher.group(), pos);
+            pos += matcher.end();
+            return token;
+        }
+        else {
+            throw new ParseException(pos, "Expected number, found: " + excerpt(json, pos, 40));
+        }
+    }
+
+    //------------------------------------------< private >---
+
+    private static String excerpt(String string, int pos, int len) {
+        return string.substring(pos, Math.min(string.length(), pos + len)) + "...";
+    }
+
+}

Added: jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/FullJsonParser.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/FullJsonParser.java?rev=1190707&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/FullJsonParser.java (added)
+++ jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/FullJsonParser.java Sat Oct 29 00:18:05 2011
@@ -0,0 +1,91 @@
+/*
+ * 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.spi2microkernel.json;
+
+import org.apache.jackrabbit.spi2microkernel.json.JsonValue.JsonArray;
+import org.apache.jackrabbit.spi2microkernel.json.JsonValue.JsonAtom;
+import org.apache.jackrabbit.spi2microkernel.json.JsonValue.JsonObject;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+
+public class FullJsonParser {
+    private FullJsonParser() { }
+
+    public static JsonObject parseObject(JsonTokenizer tokenizer) {
+        ObjectHandler objectHandler = new ObjectHandler();
+        new JsonParser(objectHandler).parseObject(tokenizer);
+        return objectHandler.getObject();
+    }
+
+    public static JsonArray parseArray(JsonTokenizer tokenizer) {
+        ArrayHandler arrayHandler = new ArrayHandler();
+        new JsonParser(arrayHandler).parseArray(tokenizer);
+        return arrayHandler.getArray();
+    }
+
+    public static class ObjectHandler extends JsonHandler {
+        private final JsonObject object = new JsonObject(new LinkedHashMap<String, JsonValue>());
+
+        @Override
+        public void atom(Token key, Token value) {
+            object.put(key.text(), new JsonAtom(value));
+        }
+
+        @Override
+        public void object(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+            object.put(key.text(), parseObject(tokenizer));
+        }
+
+        @Override
+        public void array(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+            object.put(key.text(), parseArray(tokenizer));
+        }
+
+        public JsonObject getObject() {
+            return object;
+        }
+
+    }
+
+    public static class ArrayHandler extends JsonHandler {
+        private final JsonArray array = new JsonArray(new ArrayList<JsonValue>());
+
+        @Override
+        public void atom(Token key, Token value) {
+            array.add(new JsonAtom(value));
+        }
+
+        @Override
+        public void object(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+            array.add(parseObject(tokenizer));
+        }
+
+        @Override
+        public void array(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+            array.add(parseArray(tokenizer));
+        }
+
+        public JsonArray getArray() {
+            return array;
+        }
+    }
+
+}

Added: jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonHandler.java?rev=1190707&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonHandler.java (added)
+++ jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonHandler.java Sat Oct 29 00:18:05 2011
@@ -0,0 +1,39 @@
+/*
+ * 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.spi2microkernel.json;
+
+public class JsonHandler {
+    public static final JsonHandler INSTANCE = new JsonHandler();
+    
+    public void atom(Token key, Token value) { }
+    public void comma(Token token) { }
+
+    public void pair(JsonParser parser, JsonTokenizer tokenizer) {
+        parser.parsePair(tokenizer);
+    }
+
+    public void object(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+        parser.parseObject(tokenizer);
+    }
+
+    public void array(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+        parser.parseArray(tokenizer);
+    }
+}

Added: jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonParser.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonParser.java?rev=1190707&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonParser.java (added)
+++ jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonParser.java Sat Oct 29 00:18:05 2011
@@ -0,0 +1,122 @@
+/*
+ * 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.spi2microkernel.json;
+
+import org.apache.jackrabbit.spi2microkernel.json.Token.Type;
+
+/**
+ * OBJECT    ::= { (PAIR (, PAIR)*)? }
+ * PAIR      ::= STRING : VALUE
+ * VALUE     ::= OBJECT | ARRAY | STRING | NUMBER | true | false | null
+ * ARRAY     ::= [ (VALUE (, VALUE)*)? ]
+ */
+public final class JsonParser {
+    private final JsonHandler jsonHandler;
+
+    public JsonParser(JsonHandler jsonHandler) {
+        this.jsonHandler = jsonHandler;
+    }
+
+    /* OBJECT    ::= { (PAIR (, PAIR)*)? } */
+    public void parseObject(JsonTokenizer tokenizer) {
+        tokenizer.read(Type.BEGIN_OBJECT);
+
+        if (tryParsePair(tokenizer)) {
+            while (tokenizer.peek(Type.COMMA)) {
+                jsonHandler.comma(tokenizer.read());
+                if (!tryParsePair(tokenizer)) {
+                    throw new ParseException(tokenizer.pos(),  "Expected pair, found: " + tokenizer.peek());
+                }
+            }
+        }
+
+        tokenizer.read(Type.END_OBJECT);
+    }
+
+    /* PAIR      ::= STRING: VALUE */
+    public void parsePair(JsonTokenizer tokenizer) {
+        if (!tokenizer.peek(Type.STRING)) {
+            throw new ParseException(tokenizer.pos(), "Expected string, found: " + tokenizer.peek());
+        }
+
+        Token key = tokenizer.read();
+        tokenizer.read(Type.COLON);
+        parseValue(key, tokenizer);
+    }
+
+    /* VALUE     ::= OBJECT | ARRAY | STRING | NUMBER | true | false | null */
+    public void parseValue(Token key, JsonTokenizer tokenizer) {
+        switch (tokenizer.peek().type()) {
+            case BEGIN_OBJECT:
+                jsonHandler.object(this, key, tokenizer);
+                break;
+            case BEGIN_ARRAY:
+                jsonHandler.array(this, key, tokenizer);
+                break;
+            case STRING:
+            case NUMBER:
+            case TRUE:
+            case FALSE:
+            case NULL:
+                jsonHandler.atom(key, tokenizer.read());
+                break;
+            default:
+                throw new ParseException(tokenizer.pos(), "Expected value, found: " + tokenizer.peek());
+        }
+    }
+
+    /* ARRAY     ::= [ (VALUE (, VALUE)*)? ] */
+    public void parseArray(JsonTokenizer tokenizer) {
+        tokenizer.read(Type.BEGIN_ARRAY);
+
+        if (tryParseValue(tokenizer)) {
+            while (tokenizer.peek(Type.COMMA)) {
+                jsonHandler.comma(tokenizer.read());
+                if (!tryParseValue(tokenizer)) {
+                    throw new ParseException(tokenizer.pos(), "Expected value, found: " + tokenizer.peek()); 
+                }
+            }
+        }
+
+        tokenizer.read(Type.END_ARRAY);
+    }
+
+    //------------------------------------------< private >---
+    
+    private boolean tryParsePair(JsonTokenizer tokenizer) {
+        if (tokenizer.peek(Type.STRING)) {
+            jsonHandler.pair(this, tokenizer);
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+    private boolean tryParseValue(JsonTokenizer tokenizer) {
+        if (tokenizer.peek(Type.END_ARRAY)) {
+            return false;
+        } else {
+            parseValue(null, tokenizer);
+            return true;
+        }
+    }
+
+}

Added: jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonTokenizer.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonTokenizer.java?rev=1190707&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonTokenizer.java (added)
+++ jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonTokenizer.java Sat Oct 29 00:18:05 2011
@@ -0,0 +1,80 @@
+/*
+ * 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.spi2microkernel.json;
+
+import org.apache.jackrabbit.spi2microkernel.json.Token.Type;
+
+public abstract class JsonTokenizer {
+    protected Token currentToken;
+
+    protected JsonTokenizer(JsonTokenizer tokenizer) {
+        currentToken = tokenizer.currentToken;
+    }
+
+    protected JsonTokenizer() { }
+
+    public Token peek() {
+        if (currentToken == null) {
+            currentToken = nextToken();
+        }
+
+        return currentToken;
+    }
+
+    public boolean peek(Type type) {
+        return peek().type() == type;
+    }
+
+    public Token read() {
+        if (currentToken == null) {
+            return nextToken();
+        }
+        else {
+            Token token = currentToken;
+            currentToken = null;
+            return token;
+        }
+    }
+
+    public Token read(Type type) {
+        Token token = peek();
+        if (token.type() == type) {
+            return read();
+        }
+        else {
+            throw new ParseException(token.pos(), "Expected token type " + type + ", found: " + token); 
+        }
+    }
+
+    public boolean skip(Type type) {
+        if (peek(type)) {
+            read();
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+    public abstract int pos();
+    public abstract void setPos(int pos);
+    public abstract JsonTokenizer copy();
+    protected abstract Token nextToken();
+}

Added: jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonValue.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonValue.java?rev=1190707&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonValue.java (added)
+++ jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/JsonValue.java Sat Oct 29 00:18:05 2011
@@ -0,0 +1,383 @@
+/*
+ * 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.spi2microkernel.json;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public abstract class JsonValue {
+    public enum Type {
+        STRING(false),
+        NUMBER(false),
+        BOOLEAN(false),
+        NULL(false),
+        OBJECT(true),
+        ARRAY(true);
+
+        private final boolean compound;
+
+        Type(boolean compound) {
+            this.compound = compound;
+        }
+
+        public boolean compound() {
+            return compound;
+        }
+    }
+
+    public abstract static class Visitor {
+        public void visit(JsonAtom atom) { }
+        public void visit(JsonArray array) { }
+        public void visit(JsonObject object) { }
+    }
+
+    public static String toJson(JsonValue jsonValue) {
+        final StringBuilder sb = new StringBuilder();
+        jsonValue.accept(new Visitor() {
+            @Override
+            public void visit(JsonAtom atom) {
+                sb.append(toJson(atom));
+            }
+
+            @Override
+            public void visit(JsonArray array) {
+                sb.append('[');
+                String comma = "";
+                for (JsonValue value : array.value()) {
+                    sb.append(comma);
+                    comma = ",";
+                    value.accept(this);
+                }
+                sb.append(']');
+            }
+
+            @Override
+            public void visit(JsonObject object) {
+                sb.append('{');
+                String comma = "";
+                for (Entry<String, JsonValue> entry : object.value().entrySet()) {
+                    sb.append(comma);
+                    comma = ",";
+                    sb.append(quote(entry.getKey())).append(':');
+                    entry.getValue().accept(this);
+                }
+                sb.append('}');
+            }
+        });
+        return sb.toString();
+    }
+
+    /**
+     * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters (U+0000 through U+001F).
+     */
+    public static String escape(String string) {
+        if (string == null) {
+            return null;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < string.length(); i++) {
+            char ch = string.charAt(i);
+            switch (ch) {
+                case '"':
+                    sb.append("\\\"");
+                    break;
+                case '\\':
+                    sb.append("\\\\");
+                    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;
+                default:
+                    //Reference: http://www.unicode.org/versions/Unicode5.1.0/
+                    if (ch >= '\u0000' && ch <= '\u001F' ||
+                        ch >= '\u007F' && ch <= '\u009F' ||
+                        ch >= '\u2000' && ch <= '\u20FF') {
+
+                        String ss = Integer.toHexString(ch);
+                        sb.append("\\u");
+                        for (int k = 0; k < 4 - ss.length(); k++) {
+                            sb.append('0');
+                        }
+                        sb.append(ss.toUpperCase());
+                    } else {
+                        sb.append(ch);
+                    }
+            }
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * @throws StringIndexOutOfBoundsException
+     * @throws NumberFormatException
+     */
+    public static String unescape(String text) {
+        if (text.length()  == 0) {
+            return text;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (int k = 0; k < text.length(); k++) {
+            char c = text.charAt(k);
+            if (c == '\\') {
+                c = text.charAt(++k);
+                switch (c) {
+                    case 'b':
+                        sb.append('\b');
+                        break;
+                    case 't':
+                        sb.append('\t');
+                        break;
+                    case 'n':
+                        sb.append('\n');
+                        break;
+                    case 'f':
+                        sb.append('\f');
+                        break;
+                    case 'r':
+                        sb.append('\r');
+                        break;
+                    case 'u':
+                        String u = text.substring(++k, k += 4);
+                        sb.append((char) Integer.parseInt(u, 16));
+                        break;
+                    case 'x':
+                        String x = text.substring(++k, k += 2);
+                        sb.append((char) Integer.parseInt(x, 16));
+                        break;
+                    default:
+                        sb.append(c);
+                }
+            }
+            else {
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+    public abstract Object value();
+    public abstract Type type();
+    public abstract void accept(Visitor visitor);
+
+    public String toJson() {
+        return toJson(this);
+    }
+
+    public static class JsonAtom extends JsonValue {
+        private final String value;
+        private final Type type;
+
+        public JsonAtom(String value, Type type) {
+            this.value = value;
+            this.type = type;
+        }
+
+        public JsonAtom(Token token) {
+            this(token.text(), valueType(token.type()));
+        }
+
+        @Override
+        public String value() {
+            return value;
+        }
+
+        @Override
+        public Type type() {
+            return type;
+        }
+
+        @Override
+        public void accept(Visitor visitor) {
+            visitor.visit(this);
+        }
+
+        @Override
+        public String toString() {
+            return value + ": " + type;
+        }
+
+        @Override
+        public int hashCode() {
+            return 37 * (37 * (17 + value().hashCode()) + type().hashCode());
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other instanceof JsonAtom) {
+                JsonAtom that = (JsonAtom) other;
+                return that.value().equals(value()) && that.type() == type();
+            }
+            else {
+                return false;
+            }
+        }
+
+        //------------------------------------------< private >--- 
+
+        private static JsonValue.Type valueType(Token.Type type) {
+            switch (type) {
+                case TRUE:
+                case FALSE:
+                    return JsonValue.Type.BOOLEAN;
+                case NULL:
+                    return JsonValue.Type.NULL;
+                case STRING:
+                    return JsonValue.Type.STRING;
+                case NUMBER:
+                    return JsonValue.Type.NUMBER;
+                default:
+                    throw new IllegalArgumentException("Cannot map token type " + type + " to value type");
+            }
+        }
+    }
+
+    public static class JsonArray extends JsonValue {
+        private final List<JsonValue> values;
+
+        public JsonArray(List<JsonValue> values) {
+            this.values = values;
+        }
+
+        public void add(JsonValue value) {
+            values.add(value);
+        }
+
+        public JsonValue get(int index) {
+            return values.get(index);
+        }
+
+        @Override
+        public List<JsonValue> value() {
+            return values;
+        }
+
+        @Override
+        public Type type() {
+            return Type.ARRAY;
+        }
+
+        @Override
+        public void accept(Visitor visitor) {
+            visitor.visit(this);
+        }
+
+        @Override
+        public String toString() {
+            return values.toString();
+        }
+
+        @Override
+        public int hashCode() {
+            return value().hashCode();
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other instanceof JsonArray) {
+                JsonArray that = (JsonArray) other;
+                return that.value().equals(value());
+            }
+            else {
+                return false;
+            }
+        }
+    }
+
+    public static class JsonObject extends JsonValue {
+        private final Map<String, JsonValue> values;
+
+        public JsonObject(Map<String, JsonValue> values) {
+            this.values = values;
+        }
+
+        public void put(String key, JsonValue value) {
+            values.put(key, value);
+        }
+
+        public JsonValue get(String key) {
+            return values.get(key);
+        }
+
+        @Override
+        public Map<String, JsonValue> value() {
+            return values;
+        }
+
+        @Override
+        public Type type() {
+            return Type.OBJECT;
+        }
+
+        @Override
+        public void accept(Visitor visitor) {
+            visitor.visit(this);
+        }
+
+        @Override
+        public String toString() {
+            return values.toString();
+        }
+
+        @Override
+        public int hashCode() {
+            return value().hashCode();
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other instanceof JsonObject) {
+                JsonObject that = (JsonObject) other;
+                return that.value().equals(value());
+            }
+            else {
+                return false;
+            }
+        }
+    }
+
+    //------------------------------------------< private >---
+
+    private static String toJson(JsonAtom atom) {
+        return atom.type() == Type.STRING
+            ? quote(escape(atom.value()))
+            : atom.value();
+    }
+
+    private static String quote(String text) {
+        return '\"' + text + '\"';
+    }
+
+}

Added: jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/LevelOrderJsonParser.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/LevelOrderJsonParser.java?rev=1190707&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/LevelOrderJsonParser.java (added)
+++ jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/LevelOrderJsonParser.java Sat Oct 29 00:18:05 2011
@@ -0,0 +1,153 @@
+/*
+ * 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.spi2microkernel.json;
+
+import org.apache.jackrabbit.spi2microkernel.json.JsonValue.JsonArray;
+import org.apache.jackrabbit.spi2microkernel.json.JsonValue.JsonAtom;
+import org.apache.jackrabbit.spi2microkernel.json.JsonValue.JsonObject;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public final class LevelOrderJsonParser {
+    private LevelOrderJsonParser() { }
+    
+    public static JsonObject parseObject(JsonTokenizer tokenizer) {
+        ObjectHandler objectHandler = new ObjectHandler();
+        new JsonParser(objectHandler).parseObject(tokenizer);
+        return objectHandler.getObject();
+    }
+
+    public static JsonArray parseArray(JsonTokenizer tokenizer) {
+        ArrayHandler arrayHandler = new ArrayHandler();
+        new JsonParser(arrayHandler).parseArray(tokenizer);
+        return arrayHandler.getArray();
+    }
+
+    public static class ObjectHandler extends JsonHandler {
+        private final JsonObject object = new JsonObject(new LinkedHashMap<String, JsonValue>());
+
+        @Override
+        public void atom(Token key, Token value) {
+            object.put(key.text(), new JsonAtom(value));
+        }
+
+        @Override
+        public void object(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+            object.put(key.text(), new DeferredObjectValue(tokenizer.copy()));
+            tokenizer.setPos(getNextPairPos(tokenizer.copy()));
+        }
+
+        @Override
+        public void array(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+            object.put(key.text(), parseArray(tokenizer));
+        }
+
+        public JsonObject getObject() {
+            return object;
+        }
+
+    }
+
+    public static class ArrayHandler extends JsonHandler {
+        private final JsonArray array = new JsonArray(new ArrayList<JsonValue>());
+
+        @Override
+        public void atom(Token key, Token value) {
+            array.add(new JsonAtom(value));
+        }
+
+        @Override
+        public void object(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+            array.add(new DeferredObjectValue(tokenizer.copy()));
+            tokenizer.setPos(getNextPairPos(tokenizer.copy()));
+        }
+
+        @Override
+        public void array(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+            array.add(parseArray(tokenizer));
+        }
+
+        public JsonArray getArray() {
+            return array;
+        }
+    }
+
+    //------------------------------------------< private >---
+
+    private static int getNextPairPos(JsonTokenizer tokenizer) {
+        SkipObjectHandler skipObjectHandler = new SkipObjectHandler(tokenizer.pos());
+        try {
+            new JsonParser(skipObjectHandler).parseObject(tokenizer);
+        }
+        catch (ParseException e) {
+            return skipObjectHandler.newPos;
+        }
+        return tokenizer.pos();
+    }
+
+    private static class DeferredObjectValue extends JsonObject {
+        private final JsonTokenizer tokenizer;
+
+        public DeferredObjectValue(JsonTokenizer tokenizer) {
+            super(null);
+            this.tokenizer = tokenizer;
+        }
+
+        @Override
+        public void put(String key, JsonValue value) {
+            throw new IllegalStateException("Cannot add value");
+        }
+
+        @Override
+        public JsonValue get(String key) {
+            throw new IllegalStateException("Cannot get single value");
+        }
+
+        @Override
+        public Map<String, JsonValue> value() {
+            return parseObject(tokenizer.copy()).value();
+        }
+
+        @Override
+        public String toString() {
+            return "<deferred>";
+        }
+
+    }
+
+    private static class SkipObjectHandler extends JsonHandler {
+        private final int startPos;
+        private int newPos;
+
+        public SkipObjectHandler(int startPos) {
+            this.startPos = startPos;
+        }
+
+        @Override
+        public void atom(Token key, Token value) {
+            if (key != null && ":size".equals(key.text()) && Token.Type.NUMBER == value.type()) {
+                newPos = startPos + Integer.parseInt(value.text());
+                throw new ParseException(-1, null);
+            }
+        }
+    }
+}

Added: jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/ParseException.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/ParseException.java?rev=1190707&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/ParseException.java (added)
+++ jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/ParseException.java Sat Oct 29 00:18:05 2011
@@ -0,0 +1,30 @@
+/*
+ * 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.spi2microkernel.json;
+
+public class ParseException extends RuntimeException {
+    public ParseException(int pos, String message) {
+        super(pos + ": " + message);
+    }
+
+    public ParseException(int pos, String message, Throwable cause) {
+        super(pos + ": " + message, cause);
+    }
+}

Added: jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/Token.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/Token.java?rev=1190707&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/Token.java (added)
+++ jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/Token.java Sat Oct 29 00:18:05 2011
@@ -0,0 +1,68 @@
+/*
+ * 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.spi2microkernel.json;
+
+public final class Token {
+    private final Type type;
+    private final String text;
+    private final int pos;
+
+    public enum Type {BEGIN_OBJECT, END_OBJECT, BEGIN_ARRAY, END_ARRAY, COLON, COMMA, EOF, TRUE,
+        FALSE, NULL, STRING, NUMBER}
+
+    public Token(Type type, String text, int pos) {
+        this.type = type;
+        this.text = text;
+        this.pos = pos;
+    }
+
+    public Type type() {
+        return type;
+    }
+
+    public String text() {
+        return text;
+    }
+
+    public int pos() {
+        return pos;
+    }
+
+    @Override
+    public String toString() {
+        return "Token[" + type + ", " + text + ", " + pos + ']';
+    }
+
+    @Override
+    public int hashCode() {
+        return 37 * (37 * (17 + type().hashCode()) + text().hashCode());
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof Token) {
+            Token that = (Token) other;
+            return that.type == type && that.text.equals(text);
+        }
+        else {
+            return false;
+        }
+    }
+}

Added: jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/UnescapingJsonTokenizer.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/UnescapingJsonTokenizer.java?rev=1190707&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/UnescapingJsonTokenizer.java (added)
+++ jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/main/java/org/apache/jackrabbit/spi2microkernel/json/UnescapingJsonTokenizer.java Sat Oct 29 00:18:05 2011
@@ -0,0 +1,60 @@
+/*
+ * 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.spi2microkernel.json;
+
+import org.apache.jackrabbit.spi2microkernel.json.Token.Type;
+
+public class UnescapingJsonTokenizer extends DefaultJsonTokenizer {
+    public UnescapingJsonTokenizer(String json) {
+        super(json);
+    }
+
+    protected UnescapingJsonTokenizer(UnescapingJsonTokenizer tokenizer) {
+        super(tokenizer);    
+    }
+
+    @Override
+    public JsonTokenizer copy() {
+        return new UnescapingJsonTokenizer(this);
+    }
+
+    //------------------------------------------< protected >---
+
+    @Override
+    protected Token createToken(Type type, String text, int pos) {
+        return super.createToken(type, type == Type.STRING ? unescape(text) : text, pos);
+    }
+
+    //------------------------------------------< private >---
+    
+    private String unescape(String text) {
+        try {
+            return JsonValue.unescape(text);
+        }
+        catch (IndexOutOfBoundsException e) {
+            throw new ParseException(pos(), "Invalid character escaping in string", e);
+        }
+        catch (NumberFormatException e) {
+            throw new ParseException(pos(), "Invalid character escaping in string", e);
+        }
+    }
+
+
+}

Added: jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/java/org/apache/jackrabbit/spi2microkernel/json/DefaultJsonTokenizerTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/java/org/apache/jackrabbit/spi2microkernel/json/DefaultJsonTokenizerTest.java?rev=1190707&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/java/org/apache/jackrabbit/spi2microkernel/json/DefaultJsonTokenizerTest.java (added)
+++ jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/java/org/apache/jackrabbit/spi2microkernel/json/DefaultJsonTokenizerTest.java Sat Oct 29 00:18:05 2011
@@ -0,0 +1,119 @@
+/*
+ * 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.spi2microkernel.json;
+
+import org.apache.jackrabbit.spi2microkernel.json.Token.Type;
+import org.junit.Test;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+public class DefaultJsonTokenizerTest {
+
+    private static final Token[] TOKENS = new Token[] {
+        new Token(Type.BEGIN_OBJECT, "{", 0),
+        new Token(Type.END_OBJECT, "}", 2),
+        new Token(Type.BEGIN_ARRAY, "[", 4),
+        new Token(Type.END_ARRAY, "]", 6),
+        new Token(Type.COLON, ":", 8),
+        new Token(Type.COMMA, ",", 10),
+        new Token(Type.TRUE, "true", 12),
+        new Token(Type.FALSE, "false", 17),
+        new Token(Type.NULL, "null", 23),
+        new Token(Type.STRING, "string", 28),
+        new Token(Type.NUMBER, "-12.34e-56", 37),
+        new Token(Type.STRING, "ab\\\"", 48),
+        new Token(Type.STRING, "ab\\\\", 55),
+        new Token(Type.STRING, "", 62),
+        new Token(Type.STRING, "\\\"", 65),
+        new Token(Type.STRING, "\\\\", 70),
+};
+
+    private static final String TOKEN_STRING = join(TOKENS);
+
+    private static final Token EOF_TOKEN = new Token(Type.EOF, "", TOKEN_STRING.length());
+
+    @Test
+    public void testRead() {
+        JsonTokenizer tokenizer = new DefaultJsonTokenizer(TOKEN_STRING);
+
+        for (Token token : TOKENS) {
+            int pos = tokenizer.pos();
+            assertEquals(token, tokenizer.read());
+            assertEquals(pos, token.pos());
+        }
+        assertEquals(EOF_TOKEN, tokenizer.read());
+    }
+
+    @Test
+    public void testPeek() {
+        JsonTokenizer tokenizer = new DefaultJsonTokenizer(TOKEN_STRING);
+
+        for (Token token : TOKENS) {
+            Token peeked = tokenizer.peek();
+            assertEquals(token, peeked);
+            assertEquals(token.pos(), peeked.pos());
+
+            for (Token t : TOKENS) {
+                if (t.type() != token.type()) {
+                    assertFalse(tokenizer.peek(t.type()));
+                }
+            }
+            assertEquals(peeked, tokenizer.read(peeked.type()));
+        }
+        assertEquals(EOF_TOKEN, tokenizer.peek());
+    }
+
+    @Test
+    public void testSkip() {
+        JsonTokenizer tokenizer = new DefaultJsonTokenizer(TOKEN_STRING);
+
+        for (Token token : TOKENS) {
+            Token peeked = tokenizer.peek();
+            assertEquals(token, peeked);
+
+            for (Token t : TOKENS) {
+                if (t.type() != token.type()) {
+                    assertFalse(tokenizer.skip(t.type()));
+                }
+            }
+            assertTrue(tokenizer.skip(token.type()));
+        }
+        assertEquals(EOF_TOKEN, tokenizer.peek());
+    }
+
+    //------------------------------------------< private >---
+
+    private static String join(Token[] tokens) {
+        StringBuilder json = new StringBuilder();
+        for (Token token : tokens) {
+            json.append(getString(token)).append(' ');
+        }
+        return json.toString();
+    }
+
+    private static String getString(Token token) {
+        return token.type() == Type.STRING
+            ? '\"' + token.text() + '\"'
+            : token.text();
+    }
+
+}

Added: jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/java/org/apache/jackrabbit/spi2microkernel/json/JsonParserTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/java/org/apache/jackrabbit/spi2microkernel/json/JsonParserTest.java?rev=1190707&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/java/org/apache/jackrabbit/spi2microkernel/json/JsonParserTest.java (added)
+++ jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/java/org/apache/jackrabbit/spi2microkernel/json/JsonParserTest.java Sat Oct 29 00:18:05 2011
@@ -0,0 +1,208 @@
+/*
+ * 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.spi2microkernel.json;
+
+import org.apache.jackrabbit.spi2microkernel.json.JsonValue.JsonArray;
+import org.apache.jackrabbit.spi2microkernel.json.JsonValue.JsonAtom;
+import org.apache.jackrabbit.spi2microkernel.json.JsonValue.JsonObject;
+import org.apache.jackrabbit.spi2microkernel.json.JsonValue.Visitor;
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.Stack;
+
+import static junit.framework.Assert.assertEquals;
+
+public class JsonParserTest {
+    private static final String JSON_IN = readFile("/test1.json");
+
+    @Test
+    public void testParser() {
+        final StringBuilder jsonOut = new StringBuilder("{");
+        new JsonParser(new JsonHandler() {
+            @Override
+            public void atom(Token key, Token value) {
+                jsonOut.append(createKey(key));
+                if (value.type() == Token.Type.STRING) {
+                    jsonOut.append(quoteAndEscape(value.text()));
+                }
+                else {
+                    jsonOut.append(value.text());
+                }
+            }
+
+            @Override
+            public void comma(Token token) {
+                jsonOut.append(',');
+            }
+
+            @Override
+            public void object(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+                jsonOut.append(createKey(key)).append('{');
+                super.object(parser, key, tokenizer);
+                jsonOut.append('}');
+            }
+
+            @Override
+            public void array(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+                jsonOut.append(createKey(key)).append('[');
+                super.array(parser, key, tokenizer);
+                jsonOut.append(']');
+            }
+
+        }).parseObject(new UnescapingJsonTokenizer(JSON_IN));
+        jsonOut.append('}');
+
+        assertEquals(JSON_IN, jsonOut.toString());
+    }
+
+    @Test
+    public void testParserWithBuilder() {
+        final Stack<JsonValue> objects = new Stack<JsonValue>();
+        objects.push(new JsonObject(new LinkedHashMap<String, JsonValue>()));
+
+        new JsonParser(new JsonHandler() {
+            @Override
+            public void atom(Token key, Token value) {
+               put(key, new JsonAtom(value));
+            }
+
+            @Override
+            public void object(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+                JsonObject object = new JsonObject(new LinkedHashMap<String, JsonValue>());
+                put(key, object);
+                objects.push(object);
+                super.object(parser, key, tokenizer);
+                objects.pop();
+            }
+
+            @Override
+            public void array(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+                JsonArray array = new JsonArray(new ArrayList<JsonValue>());
+                put(key, array);
+                objects.push(array);
+                super.array(parser, key, tokenizer);
+                objects.pop();
+            }
+
+            private void put(final Token key, final JsonValue value) {
+                objects.peek().accept(new Visitor(){
+                    @Override
+                    public void visit(JsonArray array) {
+                        array.add(value);
+                    }
+                    @Override
+                    public void visit(JsonObject object) {
+                        object.put(key.text(), value);
+                    }
+                });
+            }
+
+        }).parseObject(new UnescapingJsonTokenizer(JSON_IN));
+
+        assertEquals(1, objects.size());
+        assertEquals(JSON_IN, objects.peek().toJson());
+    }
+
+
+    @Test(expected = ParseException.class)
+    public void testParseExceptionValue1() {
+        JsonParser parser = new JsonParser(new JsonHandler());
+        parser.parseObject(new UnescapingJsonTokenizer("{\"key\":}"));
+    }
+
+    @Test(expected = ParseException.class)
+    public void testParseExceptionValue2() {
+        JsonParser parser = new JsonParser(new JsonHandler());
+        parser.parseObject(new UnescapingJsonTokenizer("{\"key\":[1,]}"));
+    }
+
+    @Test(expected = ParseException.class)
+    public void testParseExceptionPair1() {
+        JsonParser parser = new JsonParser(new JsonHandler());
+        parser.parseObject(new UnescapingJsonTokenizer("{\"key\":1,}"));
+    }
+
+    @Test(expected = ParseException.class)
+    public void testParseExceptionPair2() {
+        JsonParser parser = new JsonParser(new JsonHandler());
+        parser.parsePair(new UnescapingJsonTokenizer(""));
+    }
+
+    @Test
+    public void testJson1() {
+        JsonObject object1 = FullJsonParser.parseObject(new UnescapingJsonTokenizer(JSON_IN));
+        assertEquals(JSON_IN, object1.toJson());
+
+        JsonObject object2 = LevelOrderJsonParser.parseObject(new UnescapingJsonTokenizer(JSON_IN));
+        assertEquals(JSON_IN, object2.toJson());
+
+        assertEquals(object1, object2);
+        assertEquals(object1.toJson(), object2.toJson());
+    }
+
+    @Test
+    public void testJson2() {
+        String json = readFile("/test2.json");
+        JsonObject object1 = FullJsonParser.parseObject(new UnescapingJsonTokenizer(json));
+        JsonObject object2 = LevelOrderJsonParser.parseObject(new UnescapingJsonTokenizer(json));
+        assertEquals(object1, object2);
+        assertEquals(object1.toJson(), object2.toJson());
+    }
+
+    //------------------------------------------< private >---
+
+    private static String readFile(String fileName) {
+        InputStream is = JsonParserTest.class.getResourceAsStream(fileName);
+        if (is == null) {
+            throw new RuntimeException("Resource not found: " + fileName);
+        }
+
+        try {
+            BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+
+            String s;
+            StringBuilder sb = new StringBuilder();
+            while((s = reader.readLine()) != null) {
+                sb.append(s);
+            }
+
+            reader.close();
+            return sb.toString();
+        }
+        catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static String createKey(Token key) {
+        return key == null ? "" : quoteAndEscape(key.text()) + ':';
+    }
+
+    private static String quoteAndEscape(String text) {
+        return '\"' + JsonValue.escape(text) + '\"';
+    }
+
+}

Added: jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/java/org/apache/jackrabbit/spi2microkernel/json/UnescapingJsonTokenizerTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/java/org/apache/jackrabbit/spi2microkernel/json/UnescapingJsonTokenizerTest.java?rev=1190707&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/java/org/apache/jackrabbit/spi2microkernel/json/UnescapingJsonTokenizerTest.java (added)
+++ jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/java/org/apache/jackrabbit/spi2microkernel/json/UnescapingJsonTokenizerTest.java Sat Oct 29 00:18:05 2011
@@ -0,0 +1,59 @@
+/*
+ * 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.spi2microkernel.json;
+
+import org.apache.jackrabbit.spi2microkernel.json.Token.Type;
+import org.junit.Test;
+
+import static junit.framework.Assert.assertEquals;
+
+public class UnescapingJsonTokenizerTest {
+
+    private static final Token[] TOKENS = new Token[] {
+        new Token(Type.STRING, "foobar", 0),
+        new Token(Type.STRING, "foo\\bar", 9),
+        new Token(Type.STRING, "foobar", 20),
+        new Token(Type.STRING, "foobar", 33),
+        new Token(Type.STRING, "foo\bbar", 48),
+        new Token(Type.STRING, "foo\tbar", 59),
+        new Token(Type.STRING, "foo\nbar", 70),
+        new Token(Type.STRING, "foo\fbar", 81),
+        new Token(Type.STRING, "foo\rbar", 92),
+        new Token(Type.STRING, "foo\"bar", 103),
+    };
+
+    private static final Token EOF_TOKEN = new Token(Type.EOF, "", 113);
+
+    @Test
+    public void test() {
+        JsonTokenizer tokenizer = new UnescapingJsonTokenizer("\"foobar\" \"foo\\\\bar\" " +
+                "\"foo\\x62bar\" \"foo\\u0062bar\" \"foo\\bbar\" \"foo\\tbar\" \"foo\\nbar\" " +
+                "\"foo\\fbar\" \"foo\\rbar\" \"foo\\\"bar\"");
+
+        for (Token token : TOKENS) {
+            Token t = tokenizer.read();
+            assertEquals(token, t);
+            assertEquals(token.pos(),  t.pos());
+        }
+        Token t = tokenizer.read();
+        assertEquals(EOF_TOKEN, t);
+        assertEquals(EOF_TOKEN.pos(), t.pos());
+    }
+}

Added: jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/resources/test1.json
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/resources/test1.json?rev=1190707&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/resources/test1.json (added)
+++ jackrabbit/sandbox/jackrabbit-mk/jackrabbit-spi2microkernel/src/test/resources/test1.json Sat Oct 29 00:18:05 2011
@@ -0,0 +1 @@
+{"string":"va\"lue","empty":"","number":1.42,"null":null,"false":false,"true":true,"emptyArray":[],"nestedArray":[[],[[],[]]],"array":[1,2,3],"array2":["aa","bb","cc"],"emptyObject":{},"almostEmptyObject":{":size":12},"object":{":size":212,"string":"value","empty":"","number":1.42,"null":null,"false":false,"true":true,"emptyArray":[],"array1":[1,2,3,{}],"array2":[1,2,3,{"number":1.42,"array99":[1,2,3,{}]}],"emptyObject":{},"object":{}}}
\ No newline at end of file