jackrabbit-oak-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mdue...@apache.org
Subject svn commit: r1300973 [2/3] - in /jackrabbit/oak/trunk/oak-jcr: ./ src/main/java/org/apache/jackrabbit/oak/jcr/ src/main/java/org/apache/jackrabbit/oak/jcr/configuration/ src/main/java/org/apache/jackrabbit/oak/jcr/json/ src/main/java/org/apache/jackrab...
Date Thu, 15 Mar 2012 13:33:33 GMT
Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/JsonValue.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/JsonValue.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/JsonValue.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/JsonValue.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,597 @@
+/*
+ * 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.oak.jcr.json;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A {@code JsonValue} represents either an {@link JsonArray array}, an
+ * {@link JsonObject object} or a {@link JsonAtom primitive value} (atom)
+ * of a JSON document.
+ */
+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;
+        }
+
+        /**
+         * @return {@code true} for {@link Type#ARRAY} and {@link Type#OBJECT},
+         * {@code false} otherwise.
+         */
+        public boolean compound() {
+            return compound;
+        }
+    }
+
+    /**
+     * Visitor for dispatching compound {@code JsonValue}s.
+     */
+    public abstract static class Visitor {
+        public void visit(JsonAtom atom) { }
+        public void visit(JsonArray array) { }
+        public void visit(JsonObject object) { }
+    }
+
+    /**
+     * Convert {@code jsonValue} to its JSON representation.
+     * @param jsonValue
+     * @return a JSON representation of {@code jsonValue}
+     */
+    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) in {@code text}.
+     * @param text
+     * @return {@code text} with control characters escaped
+     */
+    public static String escape(String text) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < text.length(); i++) {
+            char ch = text.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();
+    }
+
+    /**
+     * Unescape escaped control characters in {@code text}
+     * @param text
+     * @return {@code text} with control characters escaped
+     * @throws StringIndexOutOfBoundsException  on unterminated escape sequences
+     * @throws NumberFormatException  on invalid escape sequences
+     */
+    public static String unescape(String text) {
+        if (text.isEmpty()) {
+            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();
+    }
+
+    /**
+     * @return the value carried by this {@code JsonValue}
+     */
+    public abstract Object value();
+
+    /**
+     * @return the type of this {@code JsonValue}
+     */
+    public abstract Type type();
+
+    /**
+     * Dispatch this {@code JsonValue} using {@code visitor}
+     * @param visitor
+     */
+    public abstract void accept(Visitor visitor);
+
+    /**
+     * @return {@code true} iff {@code this} is an instance of {@code JsonAtom}
+     */
+    public boolean isAtom() {
+        return !type().compound();
+    }
+
+    /**
+     * @return {@code true} iff {@code this} is an instance of {@code JsonArray}
+     */
+    public boolean isArray() {
+        return type() == Type.ARRAY;
+    }
+
+    /**
+     * @return {@code true} iff {@code this} is an instance of {@code JsonObject}
+     */
+    public boolean isObject() {
+        return type() == Type.OBJECT;
+    }
+
+    /**
+     * @return {@code true} iff {@code this} represents a JSON {@code null} value
+     */
+    public boolean isNull() {
+        return this == JsonAtom.NULL || equals(JsonAtom.NULL);
+    }
+
+    /**
+     * @return {@code true} iff {@code this} represents a JSON {@code true} value
+     */
+    public boolean isTrue() {
+        return this == JsonAtom.TRUE || equals(JsonAtom.TRUE);
+    }
+
+    /**
+     * @return {@code true} iff {@code this} represents a JSON {@code false} value
+     */
+    public boolean isFalse() {
+        return this == JsonAtom.FALSE || equals(JsonAtom.FALSE);
+    }
+
+    /**
+     * @return {@code this} as {@code JsonAtom}
+     * @throws UnsupportedOperationException if {@code this} is not an instance of {@code JsonAtom}
+     */
+    public JsonAtom asAtom() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @return {@code this} as {@code JsonArray}
+     * @throws UnsupportedOperationException if {@code this} is not an instance of {@code JsonArray}
+     */
+    public JsonArray asArray() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @return {@code this} as {@code JsonObject}
+     * @throws UnsupportedOperationException if {@code this} is not an instance of {@code JsonObject}
+     */
+    public JsonObject asObject() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Convert this {@code JsonValue} to its JSON representation.
+     * @return a JSON representation of this {@code JsonValue}
+     */
+    public String toJson() {
+        return toJson(this);
+    }
+
+    /**
+     * This class represents primitive JSON values (atoms). These are values of type
+     * {@link Type#STRING} {@link Type#NUMBER} {@link Type#BOOLEAN} and {@link Type#NULL}.
+     */
+    public static class JsonAtom extends JsonValue {
+        public static final JsonAtom NULL = new JsonAtom("null", Type.NULL);
+        public static final JsonAtom TRUE = new JsonAtom("true", Type.BOOLEAN);
+        public static final JsonAtom FALSE = new JsonAtom("false", Type.BOOLEAN);
+
+        private final String value;
+        private final Type type;
+
+        public JsonAtom(String value, Type type) {
+            this.value = value;
+            this.type = type;
+        }
+
+        public static JsonAtom string(String value) {
+            return new JsonAtom(value, Type.STRING);
+        }
+
+        public static JsonAtom number(double value) {
+            if (Double.isNaN(value) || Double.isInfinite(value)) {
+                throw new IllegalArgumentException(Double.toString(value));
+            }
+            return new JsonAtom(Double.toString(value), Type.NUMBER);
+        }
+
+        public static JsonAtom number(long value) {
+            return new JsonAtom(Long.toString(value), Type.NUMBER);
+        }
+
+        public static JsonAtom number(BigDecimal value) {
+            return new JsonAtom(value.toString(), Type.NUMBER);
+        }
+
+        /**
+         * Create a new {@code JsonAtom} from {@code token}.
+         * @param token
+         * @throws IllegalArgumentException  if {@code token} does not represent
+         * an primitive type (atom).
+         */
+        public JsonAtom(Token token) {
+            this(token.text(), valueType(token.type()));
+        }
+
+        @Override
+        public JsonAtom asAtom() {
+            return this;
+        }
+
+        @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");
+            }
+        }
+    }
+
+    /**
+     * This class represents JSON arrays.
+     */
+    public static class JsonArray extends JsonValue {
+        public static JsonArray EMPTY = new JsonArray(Collections.<JsonValue>emptyList());
+        
+        private final List<JsonValue> values;
+
+        public JsonArray(List<JsonValue> values) {
+            this.values = values;
+        }
+
+        public JsonArray() {
+            this(new ArrayList<JsonValue>());
+        }
+
+        /**
+         * Append {@code value} to the end of this array.
+         * @param value
+         */
+        public void add(JsonValue value) {
+            values.add(value);
+        }
+
+        /**
+         * Removes a value from this array
+         * @param value
+         * @return  {@code true} iff the array contains {@code value}
+         */
+        public boolean remove(JsonValue value) {
+            return values.remove(value);
+        }
+
+        /**
+         * @param index
+         * @return the {@code JsonValue} at {@code index}.
+         * @throws IndexOutOfBoundsException  if {@code index} is out of range
+         */
+        public JsonValue get(int index) {
+            return values.get(index);
+        }
+
+        @Override
+        public JsonArray asArray() {
+            return this;
+        }
+
+        @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;
+            }
+        }
+    }
+
+    /**
+     * This class represents JSON objects.
+     */
+    public static class JsonObject extends JsonValue {
+        public static final JsonObject EMPTY = new JsonObject(Collections.<String, JsonValue>emptyMap());
+
+        private final Map<String, JsonValue> values;
+
+        public JsonObject(Map<String, JsonValue> values) {
+            this.values = values;
+        }
+
+        public JsonObject() {
+            this(new HashMap<String, JsonValue>());
+        }
+
+        /**
+         * Put {@code value} into this object
+         * @param key
+         * @param value
+         */
+        public void put(String key, JsonValue value) {
+            values.put(key, value);
+        }
+
+        /**
+         * @param key
+         * @return  the {@code JsonValue} identified by {@code key} or {@code null}
+         * if no value exists for {@code key}.
+         */
+        public JsonValue get(String key) {
+            return values.get(key);
+        }
+
+        /**
+         * Remove {@code key} from this object
+         * @param key
+         * @return  the {@code JsonValue} identified by {@code key} or {@code null}
+         * if no value exists for {@code key}.
+         */
+        public JsonValue remove(String key) {
+            return values.remove(key);
+        }
+
+        public boolean isEmpty() {
+            return values.isEmpty();
+        }
+
+        @Override
+        public JsonObject asObject() {
+            return this;
+        }
+
+        @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/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/JsopHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/JsopHandler.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/JsopHandler.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/JsopHandler.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,48 @@
+/*
+ * 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.oak.jcr.json;
+
+public class JsopHandler {
+
+    public void add(Token path, JsonTokenizer value) {
+        new JsonParser(JsonHandler.INSTANCE).parseObject(value);
+    }
+
+    public void add(Token path, Token value) { }
+    public void add(Token path, Token[] values) { }
+    public void remove(Token path) { }
+    public void set(Token path, Token value) { }
+    public void set(Token path, Token[] values) { }
+    public void reorder(Token path, Token position, Token target) { }
+    public void move(Token path, Token target) { }
+    public void test(Token path, Token value) { }
+    public void test(Token path, Token[] values) { }
+
+    public void metaData(JsonTokenizer value) {
+        new JsonParser(JsonHandler.INSTANCE).parseObject(value);
+    }
+
+    public void extension(char op, Token path, JsonTokenizer value) {
+        new JsonParser(JsonHandler.INSTANCE).parseObject(value);
+    }
+
+    public void extension(char op, Token[] values) { }
+    public void extension(char op, Token value) { }
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/JsopParser.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/JsopParser.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/JsopParser.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/JsopParser.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,199 @@
+/*
+ * 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.oak.jcr.json;
+
+import org.apache.jackrabbit.oak.jcr.json.Token.Type;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/*
+ * DIFFS     ::= DIFF*
+ * DIFF      ::= ADD | SET | REMOVE | MOVE | TEST | METADATA | EXTENSION
+ * ADD       ::= + STRING : (OBJECT | ATOM | ARRAY)
+ * SET       ::= ^ STRING : ATOM | ARRAY
+ * REMOVE    ::= - STRING
+ * MOVE      ::= > STRING : (STRING | { STRING : STRING })
+ * TEST      ::= = STRING : ATOM | ARRAY
+ * METADATA  ::= @ OBJECT
+ * EXTENSION ::= OP STRING ":" (OBJECT | ATOM | ARRAY)
+ */
+public class JsopParser {
+    private final JsopHandler jsopHandler;
+
+    public JsopParser(JsopHandler jsopHandler) {
+        this.jsopHandler = jsopHandler;
+    }
+
+    /* DIFFS    ::= DIFF* */
+    public void parseJsop(JsonTokenizer tokenizer) {
+        for (Token token = tokenizer.peek(); token.type() != Type.EOF; token = tokenizer.peek()) {
+            parseDiff(tokenizer);
+        }
+    }
+
+    /* DIFF    ::= ADD | SET | REMOVE | MOVE */
+    public void parseDiff(JsonTokenizer tokenizer) {
+        Token token = tokenizer.read(Type.UNKNOWN);
+        String text = token.text();
+        if (text.length() != 1) {
+            throw new ParseException(token.pos(), "Expected one of +, -, ^ or >. Found: " + token);
+        }
+
+        switch (text.charAt(0)) {
+            case '+':
+                parseAdd(tokenizer);
+                break;
+            case '-':
+                parseRemove(tokenizer);
+                break;
+            case '^':
+                parseSet(tokenizer);
+                break;
+            case '>':
+                parseMove(tokenizer);
+                break;
+            case '=':
+                parseTest(tokenizer);
+                break;
+            case '@':
+                parseMetadata(tokenizer);
+                break;
+            default:
+                parseExtension(text.charAt(0), tokenizer);
+        }
+    }
+
+    /* ADD      ::= + STRING : (OBJECT | ATOM | ARRAY) */
+    public void parseAdd(JsonTokenizer tokenizer) {
+        Token path = tokenizer.read(Type.STRING);
+        tokenizer.read(Type.COLON);
+        switch (tokenizer.peek().type()) {
+            case BEGIN_OBJECT:
+                jsopHandler.add(path, tokenizer);
+                break;
+            case BEGIN_ARRAY:
+                jsopHandler.add(path, parseArray(tokenizer));
+                break;
+            default:
+                jsopHandler.add(path, parseAtom(tokenizer));
+        }
+    }
+
+    /* REMOVE   ::= - STRING */
+    public void parseRemove(JsonTokenizer tokenizer) {
+        jsopHandler.remove(tokenizer.read(Type.STRING));
+    }
+
+    /* SET      ::= ^ STRING : ATOM | ARRAY*/
+    public void parseSet(JsonTokenizer tokenizer) {
+        Token path = tokenizer.read(Type.STRING);
+        tokenizer.read(Type.COLON);
+        Token token = tokenizer.peek();
+        switch (token.type()) {
+            case BEGIN_OBJECT:
+                throw new ParseException(token.pos(), "Expected one of atom or array. Found: " + token);
+            case BEGIN_ARRAY:
+                jsopHandler.set(path, parseArray(tokenizer));
+                break;
+            default:
+                jsopHandler.set(path, parseAtom(tokenizer));
+        }
+    }
+
+    /* MOVE     ::= > STRING : (STRING | { STRING : STRING }) */
+    public void parseMove(JsonTokenizer tokenizer) {
+        Token path = tokenizer.read(Type.STRING);
+        tokenizer.read(Type.COLON);
+        if (tokenizer.peek(Type.BEGIN_OBJECT)) {
+            tokenizer.read(Type.BEGIN_OBJECT);
+            Token position = tokenizer.read(Type.STRING);
+            tokenizer.read(Type.COLON);
+            Token target = tokenizer.read(Type.STRING);
+            tokenizer.read(Type.END_OBJECT);
+            jsopHandler.reorder(path, position, target);
+        }
+        else {
+            jsopHandler.move(path, tokenizer.read(Type.STRING));
+        }
+    }
+
+    /* TEST     ::= = STRING : ATOM | ARRAY */
+    public void parseTest(JsonTokenizer tokenizer) {
+        Token path = tokenizer.read(Type.STRING);
+        tokenizer.read(Type.COLON);
+        Token token = tokenizer.peek();
+        switch (token.type()) {
+            case BEGIN_OBJECT:
+                throw new ParseException(token.pos(), "Expected one of atom or array. Found: " + token);
+            case BEGIN_ARRAY:
+                jsopHandler.test(path, parseArray(tokenizer));
+                break;
+            default:
+                jsopHandler.test(path, parseAtom(tokenizer));
+        }
+    }
+
+    /* METADATA ::= @ OBJECT */
+    public void parseMetadata(JsonTokenizer tokenizer) {
+        jsopHandler.metaData(tokenizer);
+    }
+
+    /* EXTENSION ::= OP STRING ":" (OBJECT | ATOM | ARRAY) */
+    public void parseExtension(char op, JsonTokenizer tokenizer) {
+        Token path = tokenizer.read(Type.STRING);
+        tokenizer.read(Type.COLON);
+        switch (tokenizer.peek().type()) {
+            case BEGIN_OBJECT:
+                jsopHandler.extension(op, path, tokenizer);
+                break;
+            case BEGIN_ARRAY:
+                jsopHandler.extension(op, parseArray(tokenizer));
+                break;
+            default:
+                jsopHandler.extension(op, parseAtom(tokenizer));
+        }
+    }
+
+    //------------------------------------------< private >---
+
+    private static Token parseAtom(JsonTokenizer tokenizer) {
+        Token token = tokenizer.peek();
+        Type type = token.type();
+        if (type != Type.TRUE && type != Type.FALSE && type != Type.NULL && type != Type.STRING && type != Type.NUMBER) {
+            throw new ParseException(token.pos(), "Expected one of atom. Found: " + token);
+        }
+        return tokenizer.read();
+    }
+
+    private static Token[] parseArray(JsonTokenizer tokenizer) {
+        final List<Token> values = new ArrayList<Token>();
+        
+        new JsonParser(new JsonHandler(){
+            @Override
+            public void atom(Token key, Token value) {
+                values.add(value);
+            }
+        }).parseArray(tokenizer);
+        
+        return values.toArray(new Token[values.size()]);
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/LevelOrderJsonParser.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/LevelOrderJsonParser.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/LevelOrderJsonParser.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/LevelOrderJsonParser.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,193 @@
+/*
+ * 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.oak.jcr.json;
+
+import org.apache.jackrabbit.oak.jcr.json.JsonValue.JsonArray;
+import org.apache.jackrabbit.oak.jcr.json.JsonValue.JsonAtom;
+import org.apache.jackrabbit.oak.jcr.json.JsonValue.JsonObject;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Utility class for parsing JSON objects and arrays into {@link JsonObject}s
+ * and {@link JsonArray}s, respectively. In contrast to {@link FullJsonParser},
+ * this implementation resolves nested structures lazily. That, is it does a
+ * level order traverse of the JSON tree.
+ * <p/>
+ * The parser looks for 'hints' in the JSON text to speed up parsing: when it
+ * encounters an integer value with the key ":size" in an object, that value
+ * is used for the size of the entire object (including sub-objects).
+ *
+ * @see FullJsonParser
+ */
+public final class LevelOrderJsonParser {
+    private LevelOrderJsonParser() { }
+    
+    /**
+     * Parse a JSON object from {@code tokenizer}
+     * @param tokenizer
+     * @return a {@code JsonObject}
+     * @throws ParseException
+     */
+    public static JsonObject parseObject(JsonTokenizer tokenizer) {
+        ObjectHandler objectHandler = new ObjectHandler();
+        new JsonParser(objectHandler).parseObject(tokenizer);
+        return objectHandler.getObject();
+    }
+
+    /**
+     * Parse a JSON array from {@code tokenizer}
+     * @param tokenizer
+     * @return a {@code JsonArray}
+     * @throws ParseException
+     */
+    public static JsonArray parseArray(JsonTokenizer tokenizer) {
+        ArrayHandler arrayHandler = new ArrayHandler();
+        new JsonParser(arrayHandler).parseArray(tokenizer);
+        return arrayHandler.getArray();
+    }
+
+    /**
+     * This implementation of a {@code JsonHandler} builds up a {@code JsonObject}
+     * from its constituents. Nested objects are not fully parsed though, but a
+     * reference to the parser is kept which is only invoked when that nested object
+     * is actually accessed.
+     */
+    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;
+        }
+
+    }
+
+    /**
+     * This implementation of a {@code JsonHandler} builds up a {@code JsonArray}
+     * from its constituents. Nested objects are not fully parsed though, but a
+     * reference to the parser is kept which is only invoked when that nested object
+     * is actually accessed.
+     */
+    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 class BreakException extends RuntimeException{
+        private static final BreakException BREAK = new BreakException();
+    }
+
+    private static int getNextPairPos(JsonTokenizer tokenizer) {
+        SkipObjectHandler skipObjectHandler = new SkipObjectHandler(tokenizer.pos());
+        try {
+            new JsonParser(skipObjectHandler).parseObject(tokenizer);
+        }
+        catch (BreakException 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) {
+            return value().get(key);
+        }
+
+        @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 BreakException.BREAK;
+            }
+        }
+    }
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/ParseException.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/ParseException.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/ParseException.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/ParseException.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,34 @@
+/*
+ * 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.oak.jcr.json;
+
+/**
+ * This exception is thrown when a lexical or a syntax error occurs
+ * while parsing a JSON document. 
+ */
+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/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/Token.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/Token.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/Token.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/Token.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,79 @@
+/*
+ * 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.oak.jcr.json;
+
+/**
+ * A token represents the smallest lexical unit in a JSON document.
+ * A token has a {@link Type type}, a {@link #text() text} and a
+ * {@link #pos() position} which refers to its place in the originating
+ * JSON document. Note that the position is <em>not</em> taken into account
+ * for equality.
+ */
+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, UNKNOWN}
+
+    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());
+    }
+
+    /**
+     * Two tokens are equal if and only if their texts and their types
+     * are equal.
+     */
+    @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/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/UnescapingJsonTokenizer.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/UnescapingJsonTokenizer.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/UnescapingJsonTokenizer.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/json/UnescapingJsonTokenizer.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,67 @@
+/*
+ * 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.oak.jcr.json;
+
+import org.apache.jackrabbit.oak.jcr.json.Token.Type;
+
+/**
+ * This JSON tokenizer operates on a string as its input. In contrast to
+ * {@link DefaultJsonTokenizer} it <em>does</em> unescape JSON string values.
+ */
+public class UnescapingJsonTokenizer extends DefaultJsonTokenizer {
+    public UnescapingJsonTokenizer(String json) {
+        super(json);
+    }
+
+    /**
+     * @see JsonTokenizer#JsonTokenizer(JsonTokenizer)
+     */
+    protected UnescapingJsonTokenizer(UnescapingJsonTokenizer tokenizer) {
+        super(tokenizer);    
+    }
+
+    @Override
+    public UnescapingJsonTokenizer 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/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/Authenticator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/Authenticator.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/Authenticator.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/Authenticator.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,27 @@
+/*
+ * 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.oak.jcr.security;
+
+import javax.jcr.Credentials;
+import javax.jcr.LoginException;
+
+public interface Authenticator {
+    CredentialsInfo authenticate(Credentials credentials) throws LoginException;
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/AuthenticatorImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/AuthenticatorImpl.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/AuthenticatorImpl.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/AuthenticatorImpl.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,99 @@
+/*
+ * 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.oak.jcr.security;
+
+
+import javax.jcr.Credentials;
+import javax.jcr.GuestCredentials;
+import javax.jcr.LoginException;
+import javax.jcr.SimpleCredentials;
+import java.util.Arrays;
+
+import static java.text.MessageFormat.format;
+
+public class AuthenticatorImpl implements Authenticator {
+    public static final Authenticator INSTANCE = new AuthenticatorImpl();
+
+    private AuthenticatorImpl() {}
+
+    @Override
+    public CredentialsInfo authenticate(Credentials credentials) throws LoginException {
+        // todo implement authentication, split into aggregate of SimpleAuthenticator, GuestAuthenticator, etc
+        if (credentials == null) {
+            return new CredentialsInfo() {
+                @Override
+                public String getUserId() {
+                    return "null";
+                }
+
+                @Override
+                public String[] getAttributeNames() {
+                    return new String[0];
+                }
+
+                @Override
+                public Object getAttribute(String name) {
+                    return null;
+                }
+            };
+        }
+        else if (credentials instanceof SimpleCredentials) {
+            final SimpleCredentials c = (SimpleCredentials) credentials;
+            Arrays.fill(c.getPassword(), '\0');
+            return new CredentialsInfo() {
+                @Override
+                public String getUserId() {
+                    return c.getUserID();
+                }
+
+                @Override
+                public String[] getAttributeNames() {
+                    return c.getAttributeNames();
+                }
+
+                @Override
+                public Object getAttribute(String name) {
+                    return c.getAttribute(name);
+                }
+            };
+        }
+        else if (credentials instanceof GuestCredentials) {
+            return new CredentialsInfo() {
+                @Override
+                public String getUserId() {
+                    return "anonymous";
+                }
+
+                @Override
+                public String[] getAttributeNames() {
+                    return new String[0]; 
+                }
+
+                @Override
+                public Object getAttribute(String name) {
+                    return null; 
+                }
+            };
+        }
+        else {
+            throw new LoginException(format("Login failed for {0}", credentials));
+        }
+    }
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/CredentialsInfo.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/CredentialsInfo.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/CredentialsInfo.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/CredentialsInfo.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,26 @@
+/*
+ * 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.oak.jcr.security;
+
+public interface CredentialsInfo {
+    String getUserId();
+    String[] getAttributeNames();
+    Object getAttribute(String name);
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/ChangeLog.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/ChangeLog.java?rev=1300973&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/ChangeLog.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/state/ChangeLog.java Thu Mar 15 13:33:32 2012
@@ -0,0 +1,633 @@
+/*
+ * 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.oak.jcr.state;
+
+import org.apache.jackrabbit.oak.jcr.json.JsonValue;
+import org.apache.jackrabbit.oak.jcr.json.JsonValue.JsonAtom;
+import org.apache.jackrabbit.oak.jcr.util.Path;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.apache.jackrabbit.oak.jcr.state.ChangeLog.Operation.ID;
+
+
+/**
+ * Instance of this class represent a list of add node, remove node,
+ * move node, set property and remove property operations.
+ * The change log is consolidated at any time. That is, a change log
+ * transforms any valid list of operation into an minimal list of
+ * operations which is equivalent to the initial list. A list of operation
+ * is valid, if can be applied to some hierarchy of nodes and properties.
+ * Two list of operations are equivalent if they have the same effect on
+ * any hierarchy of node and properties. A list of operations is minimal
+ * amongst some other list of operations if none of the other lists
+ * contain more operations.
+ */
+public class ChangeLog {
+    private final List<Operation> operations = new ArrayList<Operation>();
+
+    /**
+     * Clear this change log to its empty state
+     */
+    public void clear() {
+        operations.clear();
+    }
+
+    /**
+     * Add a add node operation to this change log
+     * @param path  path of the added node
+     */
+    public void addNode(Path path) {
+        addOperation(Operation.addNode(path));
+    }
+
+    /**
+     * Add remove node operation to this change log
+     * @param path  path of the removed node
+     */
+    public void removeNode(Path path) {
+        addOperation(Operation.removeNode(path));
+    }
+
+    /**
+     * Add a move node operation to this change log
+     * @param from  path of the node to move
+     * @param to  path of the moves node
+     */
+    public void moveNode(Path from, Path to) {
+        addOperation(Operation.moveNode(from, to));
+    }
+
+    /**
+     * Add a set property operation to this change log
+     * @param parent  parent of the property
+     * @param name  name of the property
+     * @param value  value of the property
+     */
+    public void setProperty(Path parent, String name, JsonValue value) {
+        if (value == null) {
+            value = JsonAtom.NULL;
+        }
+        addOperation(Operation.setProperty(parent, name, value));
+    }
+
+    /**
+     * Add a remove property operation to this change log
+     * @param parent  parent of the property
+     * @param name  name of the property
+     */
+    public void removeProperty(Path parent, String name) {
+        setProperty(parent, name, JsonAtom.NULL);
+    }
+
+    private void addOperation(Operation operation) {
+        operations.add(operation);
+        reduce(operations, operations.size() - 1);
+    }
+
+    /**
+     * @return  JSOP representation of the consolidated list of operations
+     */
+    public String toJsop() {
+        StringBuilder sb = new StringBuilder();
+        for (Operation op : operations) {
+            sb.append(op.toJsop());
+        }
+        return sb.toString();
+    }
+    
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        for (Operation op : operations) {
+            sb.append(op.toString()).append('\n');
+        }
+        return sb.toString();
+    }
+
+    //------------------------------------------< private/internal >---
+
+    /*
+      The change log consolidation algorithm implemented in the reduce method
+      is based on algebraic properties of move operations on paths. The other
+      operations (add node, remove node and set property) are generalized to
+      move operations. Consolidation relies on reduction and commutation rules
+      of move operations.
+
+      A move operation resembles a map on a hierarchy (of nodes and properties).
+      A change log consisting of k move operations m_1 to m_k is thus the composition
+      of the individual moves: m_1 *, ..., * m_k (using * for function composition:
+      f(g(x)) = (f * g)(x)).
+
+      Some definitions, notation and propositions:
+
+      * Let NIL denote a path which never occurs in any hierarchy.
+
+      * Order on paths: let p, q be paths.
+        - p < q iff p != NIL and q != NIL and p is an ancestor of q.
+        - p <= q iff p < q or p == q != NIL
+
+      * Conflict of paths: let p, q be paths.
+        - p ~ q (p conflicts with q) iff either p <= q or q <= p
+
+      * Substitution in paths: let p, q, r be paths.
+        - [p -> q]r = r if p is not an ancestor of r and
+        - [p -> q]r = s where s is the path resulting from replacing the ancestor
+          p in r with q otherwise.
+
+      * Let p, q be paths. Then p:q denotes a move operation where the node at
+        p is moved to a new node at q.
+
+      * Valid moves: leq p, q be paths.
+        - p:q is valid iff p !~ q or p = q
+        - if p:q is not valid, it is invalid
+
+      Invalid moves are exactly those which would result in a node being moved
+      to an ancestor/descendant of itself.
+
+      * Identity on moves: let p, q be paths.
+        - p:q = ID iff p = q.
+
+      * Conflict on moves: let p, q, r, s be paths.
+        - p:q ~ r:s (p:q conflicts with r:s) iff either p ~ r or p ~ s or q ~ r
+          or q ~ s.
+
+      * Strict commutativity of moves: let m, n be moves.
+        - m * n = n * m iff m !~ n
+
+      * Substitutions in moves: let p, q, r, s be paths.
+        - [p -> q]r:s = [p -> q]r:[p -> q]s
+
+      * Let p be a path and let +p denote an add node operation and let -p
+        denote a remove node operation for a node at path p.
+        - +p = NIL:p That is, adding a node is represented by a move from a
+          unknown source.
+        - p = p:NIL. That is, removing a node is represented by a move to an
+          unknown sink.
+
+
+      Let m = p:q, n = r:s with p, q, r, s != NIL be valid moves with m != ID and
+      n != ID. Then the following reduction and commutation rules apply:
+
+      1.  p!~ r:  m * n = n * m
+      2.  p < r:  illegal (since this implies q <= r which implies p ~ q and thus m invalid)
+      3.  p = r:  illegal (since this implies q <= r which implies p ~ q and this m invalid)
+      4.  p > r:  does not commute if q < s. Otherwise m * n = n * [r -> s]m
+      5.  p!~ s:  m * n = n * m
+      6.  p < s:  illegal (since this implies p ~ q and thus m invalid)
+      7.  p = s:  does not commute
+      8.  p > s:  illegal (since p > s implies there is an s already which will conflict with r:s)
+      9.  q!~ r:  m * n = n * m
+      10. q < r:  m * n = [q -> p]n * m
+      11. q = r:  m * n = p:s (transitivity of moves)
+      12. q > r:  m * n = n * [r -> s]m
+      13. q!~ s:  m * n = n * m
+      14. q < s:  does not commute if p > r. Otherwise m * n = [q -> p]n * m
+      15. q = s:  illegal (since s conflicts with r:s)
+      16. q > s:  illegal (since s conflicts with r:s)
+
+      Allowing add node and remove node operations the following additional conditions apply:
+
+      Let m = p:q, n = r:s be valid moves with m != ID and n != ID. Then the reduction
+      and commutations rules 1. to 16. apply with extra conditions on 4., 10., 12. and 14.:
+
+      4'.  if s = NIL and q = NIL then m * n = -r. Otherwise if s = NIL then m, n do not commute.
+      10'. illegal if p = NIL
+      12'. if s = NIL then m * n = -r * -p
+      14'. if p = NIL: does not commute if the parent of s is q. Illegal otherwise
+
+      The cases which are marked illegal cannot occur in valid change logs. That is, if such a
+      case occurred, the containing change log would be invalid.
+
+      Following some examples in JSOP notation for each of the above cases and its respective
+      special cases.
+
+      1.   >/a:/b >/c:/d     =  >/c:/d >/a:b
+      2.   >/a:/b >/a/b:/c      illegal
+      3.   >/a:/b >/a:/c        illegal
+      4.   >/a/b:/c >/a:/d   =  >/a:/d >/d/b:/c
+      4.   >/a/b:/c >/a:/c/d    does not commute  (q < s)
+      4'.  -/a/b -/a         =  -/a               (s = NIL and q = NIL)
+      4'.  >/a/b:/c -/a      =  does not commute  (s = NIL)
+      5.   >/a:/b >/c:/d     =  >/c:/d >/a:b
+      6.   >/a:/b >/c:/a/d      illegal
+      7.   >/a:/b >/c:/a        does not commute
+      8.   >/a/d:/b >/c:/a      illegal
+      9.   >/a:/b >/c:/d     =  >/c:/d >/a:b
+      10.  >/a:/b >/b/c:/d   =  >/a/c:/d >/a:/b
+      10'. +/b:{} >/b/c:/d      illegal
+      11.  >/a:/b >/b:/c     =  >/a:/c
+      12.  >/a:/b/c >/b:/d   =  >/b:/d >/a:/d/c
+      12'. >/a:/b/c -/b      =  -/b -/a = -/a -/b
+      13:  >/a:/b >/c:/d     =  >/c:/d >/a:b
+      14.  >/a:/b >/c:/b/d   =  >/c:/a/d >/a:/b
+      14.  >/a/b:/b >/a:/b/d    does not commute  (p > r)
+      14'. +/b:{} >/c:/b/c      does not commute  (parent of s = q and p = NIL)
+      14'. +/b:{} >/c:/b/c/d    illegal           (p = NIL)
+      15.  >/a:/b >/c:/b        illegal
+      16.  >/a:/b/d >/c:/b      illegal
+     */
+
+    /**
+     * Special path element representing source and sink in add node
+     * and remove node operations, respectively. NIL is not part of
+     * the {@code leq}, {@code lt} and {@code conflict} relations below.
+     */
+    private static final Path NIL = Path.create("", "/*");
+
+    /**
+     * Partial order on paths: {@code p} <= {@code q} iff {@code p} is an ancestor
+     * of {@code q} or {@code p} == {@code q}
+     */
+    private static boolean leq(Path p, Path q) {
+        return p != NIL && q != NIL && (p.equals(q) || p.isAncestorOf(q));
+    }
+
+    /**
+     * Strict partial order on paths: {@code p} < {@code q} iff {@code p} is an
+     * ancestor of {@code q}
+     */
+    private static boolean lt(Path p, Path q) {
+        return p != NIL && q != NIL && p.isAncestorOf(q);
+    }
+
+    /**
+     * Conflict of paths: {@code p} and {@code q} conflict iff either
+     * {@code p} <= {@code q} or {@code p} >= {@code q}
+     */
+    private static boolean conflict(Path p, Path q) {
+        return leq(p, q) || leq(q, p);
+    }
+
+    /**
+     * Substitution of ancestor path: replaces {@code from} with {@code to}
+     * in {@code path} if {@code from} is an ancestor of {@code path}
+     */
+    private static Path subst(Path from, Path to, Path path) {
+        return path == NIL ? path : path.move(from, to);
+    }
+
+    /**
+     * Instances of this class represent operations in the change log.
+     * The underlying abstraction models operations as a moves: remove
+     * node is represented as move to {@code NIL} and add node and add
+     * property are represented as move from {@code NIL}. Add property
+     * operations carry a value and the property names is disambiguated
+     * (leading star) in order to avoid conflicts with node names.
+     */
+    static final class Operation {
+        public static final Operation ID = new Operation(NIL, NIL, null);
+
+        private final Path from;
+        private final Path to;
+        private final JsonValue value;
+
+        private Operation(Path from, Path to, JsonValue value) {
+            if (from == null || to == null) {
+                throw new IllegalArgumentException("path is null");
+            }
+
+            this.from = from;
+            this.to = to;
+            this.value = value;
+        }
+
+        /**
+         * Create a new move node operation.
+         * @param from  source of the move
+         * @param to  target of the move
+         * @return  new move node operation or {@code ID} if {@code from} and {@code to}
+         * are the same path.
+         * @throws IllegalArgumentException  if {@code from} an {@code to} conflict: moving
+         * a node to its own ancestor/descendant is not possible.
+         */
+        public static Operation moveNode(Path from, Path to) {
+            if (from.equals(to)) {
+                return ID;
+            }
+
+            if (conflict(from, to)) {
+                // Cannot move node to own ancestor/descendant
+                throw new IllegalArgumentException("Cannot move " + from + " to " + to);
+            }
+            else {
+                return new Operation(from, to, null);
+            }
+        }
+
+        /**
+         * Create a new add node operation.
+         * @param path  path of the node
+         * @return  new add node operation or {@code ID} if {@code path} is {@code NIL}
+         */
+        public static Operation addNode(Path path) {
+            return path.equals(NIL) ? ID : new Operation(NIL, path, null);
+        }
+
+        /**
+         * Create a new remove node operation.
+         * @param path  path of the node
+         * @return  new remove node operation or {@code ID} if {@code path} is {@code NIL}
+         */
+        public static Operation removeNode(Path path) {
+            return path.equals(NIL) ? ID : new Operation(path, NIL, null);
+        }
+
+        /**
+         * Create a new set property operation.
+         * @param parent  parent of the property
+         * @param name  name of the property
+         * @param value  value of the property
+         * @return  new set property operation
+         */
+        public static Operation setProperty(Path parent, String name, JsonValue value) {
+            return new Operation(NIL, encodeProperty(parent, name), value);
+        }
+
+        /**
+         * Move this move operation to another ancestor path
+         * @param source  source path
+         * @param target  target path
+         * @return  move operation where {@code target} is substituted for {@code source}
+         * in both {@code from} and {@code to} of this operation.
+         */
+        public Operation move(Path source, Path target) {
+            return new Operation(subst(source, target, from), subst(source, target, to), value);
+        }
+
+        /**
+         * @return  JSOP representation of this operation
+         */
+        public String toJsop() {
+            if (from == NIL && to == NIL) {
+                return "";
+            }
+            else if (value != null) {
+                return "^\"" + decodeProperty(to).toMkPath() + "\":" + value.toJson();
+            }
+            else if (from == NIL) {
+                return "+\"" + to.toMkPath() + "\":{}";
+            }
+            else if (to == NIL) {
+                return "-\"" + from.toMkPath() + '"';
+            }
+            else {
+                return ">\"" + from.toMkPath() + "\":\"" + to.toMkPath() + '"';
+            }
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (!(other instanceof Operation)) {
+                return false;
+            }
+
+            Operation that = (Operation) other;
+            return from.equals(that.from) && to.equals(that.to)
+                    && value == null ? that.value == null : value.equals(that.value);
+
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * (31 * from.hashCode() + to.hashCode()) + (value == null ? 0 : value.hashCode());
+        }
+
+        @Override
+        public String toString() {
+            if (from == NIL && to == NIL) {
+                return "ID";
+            }
+            else if (value != null) {
+                return '^' + decodeProperty(to).toMkPath() + ':' + value.toJson();
+            }
+            else if (from == NIL) {
+                return '+' + to.toMkPath() + ":{}";
+            }
+            else if (to == NIL) {
+                return '-' + from.toMkPath();
+            }
+            else {
+                return '>' + from.toMkPath() + ':' + to.toMkPath();
+            }
+        }
+
+        private static Path encodeProperty(Path parent, String name) {
+            return parent.concat('*' + name);
+        }
+
+        private static Path decodeProperty(Path path) {
+            return path.getParent().concat(path.getName().substring(1));
+        }
+    }
+
+    /**
+     * Try to commute the two operations at {@code index} and {@code index + 1} in
+     * the list of operations {@code ops}. Commuting operations might result in
+     * changes to these operations. The list is modified in place.
+     * @return {@code true} if commuting was successful, {@code false} otherwise.
+     */
+    private static boolean commute(List<Operation> ops, int index) {
+        Operation m = ops.get(index);
+        Operation n = ops.get(index + 1);
+
+        if (!conflict(m.from, n.from) && !conflict(m.from, n.to) &&
+            !conflict(m.to, n.from) && !conflict(m.to, n.to))
+        {
+            // Strict commutativity. See 1., 5., 9., 13.
+            ops.set(index, n);
+            ops.set(index + 1, m);
+            return true;
+        }
+
+        if (lt(n.from, m.from)) {  // p > r
+            // See 4'. The case s = NIL and q = NIL is handled in reduceTuple
+            if (lt(m.to, n.to) || n.to == NIL) {  // q < s || s == NIL
+                return false;
+            }
+            else {
+                ops.set(index, n);
+                ops.set(index + 1, m.move(n.from, n.to));
+                return true;
+            }
+        }
+        else if (m.from != NIL && m.from.equals(n.to)) {  // p = s
+            // See 7.
+            return false;
+        }
+        else if (lt(m.to, n.from)) {  // q < r
+            // See 10'.
+            if (m.from == NIL) {
+                throw new IllegalArgumentException(m + ", " + n);
+            }
+
+            ops.set(index, n.move(m.to, m.from));
+            ops.set(index + 1, m);
+            return true;
+        }
+        else if (m.to.equals(n.from)) {  // q = r
+            // See 11. This case is handled in reduceTuple
+            return false;
+        }
+        else if (lt(n.from, m.to)) {  // q > r
+            // See 12'.
+            if (n.to == NIL) {
+                ops.set(index, Operation.removeNode(m.from));
+                ops.set(index + 1, Operation.removeNode(n.from));
+                return true;
+            }
+            else {
+                ops.set(index, n);
+                ops.set(index + 1, m.move(n.from, n.to));
+                return true;
+            }
+        }
+        else if (lt(m.to, n.to)) {  // q < s
+            // See 14'.
+            if (m.from == NIL) {
+                Path p = n.to.getParent();
+                if (p.equals(m.to)) {
+                    return false;
+                }
+                else {
+                    throw new IllegalArgumentException(m + ", " + n);
+                }
+            }
+            else {
+                ops.set(index, n.move(m.to, m.from));
+                ops.set(index + 1, m);
+                return true;
+            }
+        }
+        else { // See 2., 3., 6., 8., 15. and 16.
+            throw new IllegalArgumentException(m + ", " + n);
+        }
+    }
+
+    /**
+     * Try to reduce the single operation at {@code index} in the list of
+     * operations {@code ops}. The list is modified in place, i.e. reduced
+     * operations are removed.
+     * @return  {@code true} if a reduction occurred, {@code false} otherwise
+     */
+    private static boolean reduceSingleton(List<Operation> ops, int index) {
+        if (ops.get(index) == ID) {
+            ops.remove(index);
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+    /**
+     * Try to reduce the two operations at {@code index} and {@code index + 1}
+     * in the list of operations {@code ops} to a single operation. The list is
+     * modified in place, i.e. reduced operations are removed and replaced with
+     * the result of the reduction.
+     * @return  index of the operations in {@code ops} which contains the result
+     * of the reduction or {@code -1} if no reduction occurred.
+     */
+    private static int reduceTuple(List<Operation> ops, int index) {
+        Operation m = ops.get(index);
+        Operation n = ops.get(index + 1);
+
+        if (m == ID) {
+            // left absorption: ID * x = x
+            ops.remove(index);
+            return index;
+        }
+        else if (n == ID) {
+            // right absorption: x * ID = x
+            ops.remove(index + 1);
+            return index;
+        }
+        else if (m.to != NIL && m.to.equals(n.from)) {
+            // transitivity: a:b * b:c = a:c  (See 11.)
+            ops.set(index, Operation.moveNode(m.from, n.to));
+            ops.remove(index + 1);
+            return index;
+        }
+        else if (m.to == NIL && n.to == NIL && lt(n.from, m.from)) {  // p > r
+            // remove absorption: -a/b * -a = -a  (See 4'.)
+            ops.remove(index);
+            return index;
+        }
+        else if (m.from == NIL && n.to == NIL && lt(n.from, m.to)) {  // q > r
+            // add absorption: +a/b * -a = -a  (See 12'.)
+            ops.remove(index);
+            return index;
+        }
+        else if (m.value != null && n.value != null && m.to.equals(n.to)) {
+            // set property absorption: ^a:x * ^a:y = ^a:y
+            ops.remove(index);
+            return index;
+        }
+        else {
+            return -1;
+        }
+    }
+
+    /**
+     * Reduce a list of operations. Let {@code ops.remove(index)} be
+     * minimal amongst all its equivalent lists of operations. Then this
+     * method reduces {@code ops} to a minimal list of operations which
+     * is equivalent to {@code ops}.
+     */
+    private static boolean reduce(List<Operation> ops, int index) {
+        // If the operation at index can be eliminated, we are done
+        if (reduceSingleton(ops, index)) {
+            return true;
+        }
+
+        int reduced = -1;  // Index of the operation resulting from reducing two adjacent operations
+
+        // Starting at the new operation, go backward until either a reduction of two
+        // adjacent operations is found or the two adjacent operations don't commute
+        int k = index;
+        do {
+            if (--k < 0) {
+                break;
+            }
+            reduced = reduceTuple(ops, k);
+        } while (reduced < 0 && commute(ops, k));
+
+        // If no reduction found so far...
+        if (reduced < 0) {
+            // ...starting at the new operation, go forward until either a reduction of two
+            // adjacent operations is found or the two adjacent operations don't commute
+            k = index;
+            do {
+                if (++k >= ops.size()) {
+                    break;
+                }
+                reduced = reduceTuple(ops, k - 1);
+            } while (reduced < 0 && commute(ops, k - 1));
+        }
+
+        // If a reduction has been found, reduce recursively treating the result
+        // of the reduction as new operation
+        return reduced >= 0 && !ops.isEmpty() && reduce(ops, reduced);
+    }
+}



Mime
View raw message