org.rapla.rest.server.jsonpatch.JsonMergePatch.java Source code

Java tutorial

Introduction

Here is the source code for org.rapla.rest.server.jsonpatch.JsonMergePatch.java

Source

package org.rapla.rest.server.jsonpatch;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

/*
* Modified original version of Francis Galiegue (fgaliegue@gmail.com)
* which software is dual-licensed under:
*
* - the Lesser General Public License (LGPL) version 3.0 or, at your option, any
* later version;
* - the Apache Software License (ASL) version 2.0.
*
* The text of both licenses is available under the src/resources/ directory of
* this project (under the names LGPL-3.0.txt and ASL-2.0.txt respectively).
*
* Direct link to the sources:
*
* - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt
* - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt
*/

public abstract class JsonMergePatch {
    protected final JsonElement origPatch;

    protected JsonMergePatch(final JsonElement node) {
        origPatch = node;
    }

    public abstract JsonElement apply(final JsonElement input) throws JsonPatchException;

    public static JsonMergePatch fromJson(final JsonElement input) throws JsonPatchException {
        if (input.isJsonPrimitive()) {
            throw new JsonPatchException("Only json containers are supported");
        }
        if (input.isJsonArray()) {
            final JsonElement content = clearNulls(input);
            return new JsonMergePatch(input) {
                @Override
                public JsonElement apply(final JsonElement input) throws JsonPatchException {
                    return content;
                }
            };
        } else {
            return new ObjectMergePatch(input);
        }
    }

    static final class ObjectMergePatch extends JsonMergePatch {
        private final Map<String, JsonElement> fields;
        private final Set<String> removals;

        ObjectMergePatch(final JsonElement content) {
            super(content);
            fields = asMap(content);
            removals = new HashSet<String>();

            for (final Map.Entry<String, JsonElement> entry : fields.entrySet())
                if (entry.getValue() == null)
                    removals.add(entry.getKey());

            fields.keySet().removeAll(removals);
        }

        @Override
        public JsonElement apply(final JsonElement input) throws JsonPatchException {
            if (!input.isJsonObject())
                return mapToNode(fields);

            final Map<String, JsonElement> map = asMap(input);

            // Remove all entries which must be removed
            map.keySet().removeAll(removals);

            // Now cycle through what is left
            String memberName;
            JsonElement patchNode;

            for (final Map.Entry<String, JsonElement> entry : map.entrySet()) {
                memberName = entry.getKey();
                patchNode = fields.get(memberName);

                // Leave untouched if no mention in the patch
                if (patchNode == null)
                    continue;

                // If the patch node is a primitive type, replace in the result.
                // Reminder: there cannot be a JSON null anymore
                if (patchNode.isJsonPrimitive()) {
                    entry.setValue(patchNode); // no need for .deepCopy()
                    continue;
                }

                final JsonMergePatch patch = fromJson(patchNode);
                entry.setValue(patch.apply(entry.getValue()));
            }

            // Finally, if there are members in the patch not present in the input,
            // fill in members
            for (final String key : difference(fields.keySet(), map.keySet()))
                map.put(key, clearNulls(fields.get(key)));

            return mapToNode(map);
        }

        private Set<String> difference(Set<String> keySet, Set<String> keySet2) {
            LinkedHashSet<String> result = new LinkedHashSet<String>();
            for (String key : keySet) {
                if (!keySet2.contains(key)) {
                    result.add(key);
                }
            }
            return result;
        }

        private Map<String, JsonElement> asMap(JsonElement input) {
            Map<String, JsonElement> result = new LinkedHashMap<String, JsonElement>();
            for (Map.Entry<String, JsonElement> entry : input.getAsJsonObject().entrySet()) {
                JsonElement value = entry.getValue();
                String key = entry.getKey();
                result.put(key, value);
            }
            return result;
        }

    }

    private static JsonElement mapToNode(final Map<String, JsonElement> map) {
        final JsonObject ret = new JsonObject();
        for (String key : map.keySet()) {
            JsonElement value = map.get(key);
            ret.add(key, value);
        }
        return ret;
    }

    /**
     * Clear "null values" from a JSON value
     *
     * <p>Non container values are unchanged. For arrays, null elements are
     * removed. From objects, members whose values are null are removed.</p>
     *
     * <p>This method is recursive, therefore arrays within objects, or objects
     * within arrays, or arrays within arrays etc are also affected.</p>
     *
     * @param node the original JSON value
     * @return a JSON value without null values (see description)
     */
    protected static JsonElement clearNulls(final JsonElement node) {
        if (node.isJsonPrimitive())
            return node;

        return node.isJsonArray() ? clearNullsFromArray((JsonArray) node) : clearNullsFromObject((JsonObject) node);
    }

    private static JsonElement clearNullsFromArray(final JsonArray node) {
        final JsonArray ret = new JsonArray();

        /*
        * Cycle through array elements. If the element is a null node itself,
        * skip it. Otherwise, add a "cleaned up" element to the result.
        */
        for (final JsonElement element : node)
            if (!element.isJsonNull())
                ret.add(clearNulls(element));

        return ret;
    }

    private static JsonElement clearNullsFromObject(final JsonObject node) {
        final JsonObject ret = new JsonObject();
        final Iterator<Map.Entry<String, JsonElement>> iterator = node.entrySet().iterator();

        Map.Entry<String, JsonElement> entry;
        JsonElement value;

        /*
        * When faces with an object, cycle through this object's entries.
        *
        * If the value of the entry is a JSON null, don't include it in the
        * result. If not, include a "cleaned up" value for this key instead of
        * the original element.
        */
        while (iterator.hasNext()) {
            entry = iterator.next();
            value = entry.getValue();
            if (value != null) {
                String key = entry.getKey();
                JsonElement clearNulls = clearNulls(value);
                ret.add(key, clearNulls);
            }
        }

        return ret;
    }

    public String toString() {
        return origPatch.toString();
    }

}