com.ikanow.aleph2.v1.document_db.utils.JsonNodeBsonUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.ikanow.aleph2.v1.document_db.utils.JsonNodeBsonUtils.java

Source

/*******************************************************************************
 * Copyright 2015, The IKANOW Open Source Project.
 *
 * Licensed 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 com.ikanow.aleph2.v1.document_db.utils;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.bson.BSONObject;
import org.bson.BasicBSONObject;
import org.bson.types.BasicBSONList;
import org.bson.types.ObjectId;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.mongodb.BasicDBObject;

/** Utilities for creating JsonNodes lazily from MapWritables
 * @author Alex
 *
 */
public class JsonNodeBsonUtils {

    /** Creates a lazy object node from a MapWritable 
     * @param m
     * @return
     */
    public static ObjectNode from(final BSONObject b) {
        return new ObjectNodeWrapper(JsonNodeFactory.instance, b);
    }

    /** Utility that goes from various MongoDB artefacts to JsonNode
     * @param x
     * @return
     */
    protected static JsonNode transform(Object x, JsonNodeFactory nc) {
        if (null == x) { // missing => missing
            return null;
        } else if (x instanceof ObjectId) {
            return nc.textNode(((ObjectId) x).toString());
        } else if (x instanceof Boolean) {
            return nc.booleanNode(((Boolean) x));
        } else if (x instanceof String) {
            return nc.textNode(((String) x));
        } else if (x instanceof Date) {
            return nc.numberNode(((Date) x).getTime());
        } else if (x instanceof Double) {
            return nc.numberNode(((Double) x));
        } else if (x instanceof Float) {
            return nc.numberNode(((Float) x));
        } else if (x instanceof Long) {
            return nc.numberNode(((Long) x));
        } else if (x instanceof Integer) {
            return nc.numberNode(((Integer) x));
        } else if (x instanceof byte[]) {
            return nc.binaryNode(((byte[]) x));
        } else if (x instanceof BasicBSONList) {
            // (don't do this lazily, construct entire thing once requested)
            return new ArrayNodeWrapper(nc, (BasicBSONList) x);
        } else if (x instanceof BSONObject) { // recurse! (ish)
            return new ObjectNodeWrapper(nc, (BSONObject) x);
        } else
            return nc.nullNode();
    }

    /////////////////////////////////////////////////

    // Utility classes

    /** Lazy map that can have a mix of String/JsonObject and Text/Writable in it
     * @author Alex
     */
    public static class LazyTransformingMap implements Map<String, JsonNode> {
        private final static LinkedHashMap<String, JsonNode> _EMPTY_MAP = new LinkedHashMap<>();
        private final static BasicBSONObject _EMPTY_WMAP = new BasicDBObject();
        protected final JsonNodeFactory _nc;
        protected BasicBSONObject _delegate;
        protected LinkedHashMap<String, JsonNode> _new_vals = _EMPTY_MAP;

        LazyTransformingMap(BSONObject delegate, JsonNodeFactory nc) {
            _delegate = (BasicBSONObject) delegate;
            _nc = nc;
        }

        @Override
        public int size() {
            return _new_vals.size() + _delegate.size();
        }

        @Override
        public boolean isEmpty() {
            return _new_vals.isEmpty() && _delegate.isEmpty();
        }

        @Override
        public boolean containsKey(Object key) {
            return _new_vals.containsKey(key) || _delegate.containsField((String) key);
        }

        @Override
        public boolean containsValue(Object value) {
            return _new_vals.containsValue(value) || _delegate.values().stream()
                    .map(x -> JsonNodeBsonUtils.transform(x, _nc)).anyMatch(x -> x.equals(value));
        }

        @Override
        public JsonNode get(Object key) {
            return (JsonNode) _new_vals.getOrDefault(key,
                    JsonNodeBsonUtils.transform(_delegate.toMap().get((String) key), _nc));
        }

        @Override
        public JsonNode put(String key, JsonNode value) {
            createNewVals();
            JsonNode ret_val = this.remove(key);
            _new_vals.put(key, value);
            return ret_val;
        }

        @Override
        public JsonNode remove(Object key) {
            Object try1 = _new_vals.remove(key);

            if (null == try1) {
                try1 = _delegate.removeField((String) key);
                if (null != try1) {
                    try1 = JsonNodeBsonUtils.transform(try1, _nc);
                }
            }
            return (JsonNode) try1;
        }

        @Override
        public void putAll(Map<? extends String, ? extends JsonNode> m) {
            createNewVals();
            _new_vals.putAll(m);
        }

        @Override
        public void clear() {
            _new_vals = _EMPTY_MAP;
            _delegate = _EMPTY_WMAP;
        }

        private void createNewVals() {
            if (_EMPTY_MAP == _new_vals) {
                _new_vals = new LinkedHashMap<>();
            }
        }

        @SuppressWarnings("unchecked")
        private void swap() {
            createNewVals();
            _delegate.toMap().forEach((k, v) -> _new_vals.put(k.toString(), transform(v, _nc)));
            _delegate = _EMPTY_WMAP; // (to avoid mutating _delegate unless we have to)
        }

        @Override
        public Set<String> keySet() {
            swap();
            return _new_vals.keySet();
        }

        @Override
        public Collection<JsonNode> values() {
            swap();
            return _new_vals.values();
        }

        @Override
        public Set<java.util.Map.Entry<String, JsonNode>> entrySet() {
            swap();
            return _new_vals.entrySet();
        }
    }

    /** Object node mapper for MapWritable
     * @author Alex
     */
    public static class ObjectNodeWrapper extends ObjectNode {
        private Map<String, JsonNode> _my_children;

        /** User c'tor
         * @param nc
         * @param kids
         */
        @SuppressWarnings("unchecked")
        public ObjectNodeWrapper(JsonNodeFactory nc, BSONObject delegate) {
            super(nc);
            try {
                // Ugh, children is private
                final Field f = ObjectNode.class.getDeclaredField("_children");
                f.setAccessible(true);
                f.set(this, new LazyTransformingMap(delegate, nc));
                _my_children = (Map<String, JsonNode>) f.get(this);
            } catch (Exception e) {
            }
        }

        public boolean containsKey(Object key) {
            return _my_children.containsKey(key);
        }

        public boolean containsValue(Object val) {
            return _my_children.containsValue(val);
        }

        public boolean isEmpty() {
            return _my_children.isEmpty();
        }
    }

    /** Array node mapper for ArrayWritable
     * @author Alex
     */
    public static class ArrayNodeWrapper extends ArrayNode {

        public ArrayNodeWrapper(JsonNodeFactory nc, BasicBSONList delegate) {
            super(nc);
            try {
                // Ugh, children is private
                final Field f = ArrayNode.class.getDeclaredField("_children");
                f.setAccessible(true);
                f.set(this, com.google.common.collect.Lists.transform(delegate, x -> transform(x, nc)));
            } catch (Exception e) {
            }
        }
    }

}