diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 87d17607..00000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: java -jdk: - - openjdk8 - - openjdk11 -install: {} -script: - - ./gradlew assemble check - -before_cache: - - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ - -cache: - directories: - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ - diff --git a/project.gradle b/project.gradle index e306fce2..2776303a 100644 --- a/project.gradle +++ b/project.gradle @@ -20,7 +20,7 @@ /* * Project-specific settings. Unfortunately we cannot put the name in there! */ -group = "com.github.java-json-tools"; +group = "com.gravity9.java-json-tools"; sourceCompatibility = JavaVersion.VERSION_1_7; targetCompatibility = JavaVersion.VERSION_1_7; // defaults to sourceCompatibility project.ext.description = "JSON Patch (RFC 6902) and JSON Merge Patch (RFC 7386) implementation in Java, using extended TMF620 JsonPath syntax"; diff --git a/src/main/java/com/github/fge/jsonpatch/AddOperation.java b/src/main/java/com/github/fge/jsonpatch/AddOperation.java deleted file mode 100644 index 4fc3613f..00000000 --- a/src/main/java/com/github/fge/jsonpatch/AddOperation.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) - * - * This 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 this file and of both licenses is available at the root of this - * project or, if you have the jar distribution, in directory META-INF/, 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 - */ - -package com.github.fge.jsonpatch; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.jayway.jsonpath.DocumentContext; -import com.jayway.jsonpath.JsonPath; - - -/** - * JSON Patch {@code add} operation - * - *

For this operation, {@code path} is the JSON Pointer where the value - * should be added, and {@code value} is the value to add.

- * - *

Note that if the target value pointed to by {@code path} already exists, - * it is replaced. In this case, {@code add} is equivalent to {@code replace}. - *

- * - *

Note also that a value will be created at the target path if and only - * if the immediate parent of that value exists (and is of the correct - * type).

- * - *

Finally, if the last reference token of the JSON Pointer is {@code -} and - * the immediate parent is an array, the given value is added at the end of the - * array. For instance, applying:

- * - *
- *     { "op": "add", "path": "/-", "value": 3 }
- * 
- * - *

to:

- * - *
- *     [ 1, 2 ]
- * 
- * - *

will give:

- * - *
- *     [ 1, 2, 3 ]
- * 
- */ -public final class AddOperation extends PathValueOperation { - public static final String LAST_ARRAY_ELEMENT_SYMBOL = "-"; - - @JsonCreator - public AddOperation(@JsonProperty("path") final String path, - @JsonProperty("value") final JsonNode value) { - super("add", path, value); - } - - @Override - public JsonNode applyInternal(final JsonNode node) throws JsonPatchException { - if (path.isEmpty()) { - return value; - } - PathDetails pathDetails = PathParser.getParentPathAndNewNodeName(path); - final String pathToParent = pathDetails.getPathToParent(); - final String newNodeName = pathDetails.getNewNodeName(); - - final DocumentContext nodeContext = JsonPath.parse(node.deepCopy()); - final JsonNode evaluatedJsonParents = nodeContext.read(pathToParent); - if (evaluatedJsonParents == null) { - throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.noSuchParent")); - } - if (!evaluatedJsonParents.isContainerNode()) { - throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.parentNotContainer")); - } - - if (pathDetails.doesContainFiltersOrMultiIndexesNotation()) { // json filter result is always a list - for (int i = 0; i < evaluatedJsonParents.size(); i++) { - JsonNode parentNode = evaluatedJsonParents.get(i); - if (!parentNode.isContainerNode()) { - throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.parentNotContainer")); - } - DocumentContext containerContext = JsonPath.parse(parentNode); - if (parentNode.isArray()) { - addToArray(containerContext, "$", newNodeName); - } else { - addToObject(containerContext, "$", newNodeName); - } - } - return nodeContext.read("$"); - } else { - return evaluatedJsonParents.isArray() - ? addToArray(nodeContext, pathToParent, newNodeName) - : addToObject(nodeContext, pathToParent, newNodeName); - } - } - - private JsonNode addToArray(final DocumentContext node, String jsonPath, String newNodeName) throws JsonPatchException { - if (newNodeName.equals(LAST_ARRAY_ELEMENT_SYMBOL)) { - return node.add(jsonPath, value).read("$", JsonNode.class); - } - - final int size = node.read(jsonPath, JsonNode.class).size(); - final int index = verifyAndGetArrayIndex(newNodeName, size); - - ArrayNode updatedArray = node.read(jsonPath, ArrayNode.class).insert(index, value); - return "$".equals(jsonPath) ? updatedArray : node.set(jsonPath, updatedArray).read("$", JsonNode.class); - } - - private JsonNode addToObject(final DocumentContext node, String jsonPath, String newNodeName) { - return node - .put(jsonPath, newNodeName, value) - .read("$", JsonNode.class); - } - - private int verifyAndGetArrayIndex(String stringIndex, int size) throws JsonPatchException { - int index; - try { - index = Integer.parseInt(stringIndex); - } catch (NumberFormatException ignored) { - throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.notAnIndex")); - } - if (index < 0 || index > size) { - throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.noSuchIndex")); - } - return index; - } -} diff --git a/src/main/java/com/github/fge/jsonpatch/DualPathOperation.java b/src/main/java/com/github/fge/jsonpatch/DualPathOperation.java deleted file mode 100644 index 41d4a49d..00000000 --- a/src/main/java/com/github/fge/jsonpatch/DualPathOperation.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) - * - * This 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 this file and of both licenses is available at the root of this - * project or, if you have the jar distribution, in directory META-INF/, 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 - */ - -package com.github.fge.jsonpatch; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.jsontype.TypeSerializer; -import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; - -import java.io.IOException; - -/** - * Base class for JSON Patch operations taking two JSON Pointers as arguments - */ -public abstract class DualPathOperation extends JsonPatchOperation { - @JsonSerialize(using = ToStringSerializer.class) - protected final String from; - - /** - * Protected constructor - * - * @param op operation name - * @param from source path - * @param path destination path - */ - protected DualPathOperation(final String op, final String from, final String path) { - super(op, path); - this.from = from; - } - - @Override - public final void serialize(final JsonGenerator jgen, final SerializerProvider provider) throws IOException, JsonProcessingException { - jgen.writeStartObject(); - jgen.writeStringField("op", op); - jgen.writeStringField("path", path); - jgen.writeStringField("from", from); - jgen.writeEndObject(); - } - - @Override - public final void serializeWithType(final JsonGenerator jgen, final SerializerProvider provider, - final TypeSerializer typeSer) throws IOException, JsonProcessingException { - serialize(jgen, provider); - } - - public final String getFrom() { - return from; - } - - @Override - public final String toString() { - return "op: " + op + "; from: \"" + from + "\"; path: \"" + path + '"'; - } -} diff --git a/src/main/java/com/github/fge/jsonpatch/Iterables.java b/src/main/java/com/github/fge/jsonpatch/Iterables.java deleted file mode 100644 index 79b67e1b..00000000 --- a/src/main/java/com/github/fge/jsonpatch/Iterables.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.github.fge.jsonpatch; - -import java.util.Iterator; -import java.util.NoSuchElementException; - -/** - * Iterables utility class - * @author {@literal @}soberich on 30-Nov-18 - */ -public final class Iterables { - - private Iterables() {} - - /** - * Returns the last element of {@code iterable}. - * - * @param underlying type being iterated - * @param iterable type of iterable - * @return the last element of {@code iterable} - * @throws NoSuchElementException if the iterable is empty - */ - public static T getLast(Iterable iterable) { - Iterator iterator = iterable.iterator(); - while (true) { - T current = iterator.next(); - if (!iterator.hasNext()) { - return current; - } - } - } -} diff --git a/src/main/java/com/github/fge/jsonpatch/JsonPathParser.java b/src/main/java/com/github/fge/jsonpatch/JsonPathParser.java deleted file mode 100644 index 23d6819d..00000000 --- a/src/main/java/com/github/fge/jsonpatch/JsonPathParser.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.github.fge.jsonpatch; - -public class JsonPathParser { - - private static final String ARRAY_ELEMENT_REGEX = "(?<=\\.)(\\d+)"; - - public static String tmfStringToJsonPath(String path) throws JsonPatchException { - if (path.startsWith("$")) { - return path; - } else if (path.contains("?")) { - throw new JsonPatchException("Invalid path, `?` are not allowed in JsonPointer expressions."); - } else if (path.contains("//")) { - throw new JsonPatchException("Invalid path, `//` is not allowed in JsonPointer expressions."); - } - - return "$" + path.replace('/', '.') - .replace("~1", "/") // / must be escaped in JsonPointer using ~1 - .replace("~0", "~") // ~ must be escaped in JsonPointer using ~0 - .replaceAll(ARRAY_ELEMENT_REGEX, "[$1]"); - } -} diff --git a/src/main/java/com/github/fge/jsonpatch/Patch.java b/src/main/java/com/github/fge/jsonpatch/Patch.java deleted file mode 100644 index adce2042..00000000 --- a/src/main/java/com/github/fge/jsonpatch/Patch.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.github.fge.jsonpatch; - -import com.fasterxml.jackson.databind.JsonNode; - -public interface Patch { - - JsonNode apply(JsonNode node) throws JsonPatchException; -} diff --git a/src/main/java/com/github/fge/jsonpatch/PathDetails.java b/src/main/java/com/github/fge/jsonpatch/PathDetails.java deleted file mode 100644 index d8ecf7fb..00000000 --- a/src/main/java/com/github/fge/jsonpatch/PathDetails.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.github.fge.jsonpatch; - -public class PathDetails { - - private final String pathToParent; - - private final String newNodeName; - - private final boolean containsFiltersOrMultiIndexesNotation; - - public PathDetails(String pathToParent, String newNodeName, boolean containsFiltersOrMultiIndexesNotation) { - this.pathToParent = pathToParent; - this.newNodeName = newNodeName; - this.containsFiltersOrMultiIndexesNotation = containsFiltersOrMultiIndexesNotation; - } - - public String getPathToParent() { - return pathToParent; - } - - public String getNewNodeName() { - return newNodeName; - } - - public boolean doesContainFiltersOrMultiIndexesNotation() { - return containsFiltersOrMultiIndexesNotation; - } - - @Override - public String toString() { - return "PathDetails{" + - "pathToParent='" + pathToParent + '\'' + - ", newNodeName='" + newNodeName + '\'' + - ", containsFiltersOrMultiIndexesNotation=" + containsFiltersOrMultiIndexesNotation + - '}'; - } -} diff --git a/src/main/java/com/github/fge/jsonpatch/PathParser.java b/src/main/java/com/github/fge/jsonpatch/PathParser.java deleted file mode 100644 index 0ae4aa37..00000000 --- a/src/main/java/com/github/fge/jsonpatch/PathParser.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.github.fge.jsonpatch; - -import com.jayway.jsonpath.internal.Path; -import com.jayway.jsonpath.internal.path.PathCompiler; - -import java.util.ArrayList; -import java.util.List; - -public class PathParser { - - private static final String FILTER_PLACEHOLDER = "[?]"; - - /** - * This method parses JsonPath to find node name that needs to be added and path to the parent of new node. - * Additionally, it finds if path contains filter or multi index notation (like [1:5]) - * - * @param path Path in JsonPath or JsonPointer notation - * @return PathDetails containing path to parent, name of new node and boolean value if path contains filter or multi - * index notation - * @throws JsonPatchException when invalid path provided - * */ - public static PathDetails getParentPathAndNewNodeName(String path) throws JsonPatchException { - final String fullJsonPath = JsonPathParser.tmfStringToJsonPath(path); - final Path compiledPath = compilePath(fullJsonPath); - String[] splitJsonPath = splitJsonPath(compiledPath); - - int filterCounter = 0; - List filters = getFiltersOperations(fullJsonPath); - boolean containsFiltersOrMultiIndexNotation = false; - StringBuilder sb = new StringBuilder(); - sb.append("$"); - for (int i = 0; i < splitJsonPath.length - 1; i++) { - if (splitJsonPath[i].isEmpty()) { - continue; - } - if (splitJsonPath[i].equals(FILTER_PLACEHOLDER)) { - sb.append(filters.get(filterCounter++)); - containsFiltersOrMultiIndexNotation = true; - } else if (isArrayPart(splitJsonPath[i])) { - sb.append(splitJsonPath[i]); - if (isMultiIndexNotation(splitJsonPath[i])) { - containsFiltersOrMultiIndexNotation = true; - } - } else if (isDoubleDot(splitJsonPath[i])) { - sb.append(splitJsonPath[i]); - } else { - sb.append("[").append(splitJsonPath[i]).append("]"); - } - } - final String pathToParent = sb.toString(); - final String newNodeName = getNewNodeName(splitJsonPath); - return new PathDetails(pathToParent, newNodeName, containsFiltersOrMultiIndexNotation); - } - - private static boolean isMultiIndexNotation(String path) { - String pathWithoutBracket = path - .replace("[", "") - .replace("]", ""); - return !pathWithoutBracket.startsWith("'") && !pathWithoutBracket.matches("[0-9]+"); - } - - private static String getNewNodeName(String[] splitJsonPath) { - return splitJsonPath[splitJsonPath.length - 1] - .replace("'", "") - .replace("[", "") - .replace("]", ""); - } - - /** - * Removes $ sign from the beginning of the path for easier processing - it will be added later on - * This method is called after PathCompiler.compile, so we are sure now, that the path is correct - * and bracket notation is used. - * We can now split JsonPath using positive lookahead regex (without removing separator). - * */ - private static String[] splitJsonPath(Path compiledPath) { - return compiledPath.toString() - .replace("$", "") - .split("(?=\\[)"); - } - - private static Path compilePath(String fullJsonPath) throws JsonPatchException { - try { - return PathCompiler.compile(fullJsonPath); - } catch (Exception e) { - throw new JsonPatchException("Non-compilable path provided"); - } - } - - private static boolean isDoubleDot(String jsonPathPart) { - return jsonPathPart.equals(".."); - } - - private static boolean isArrayPart(String jsonPathPart) { - return jsonPathPart.startsWith("[") && jsonPathPart.endsWith("]"); - } - - private static List getFiltersOperations(String jsonPath) { - if (!jsonPath.contains("[?(")) { - return new ArrayList<>(); - } - List filters = new ArrayList<>(); - int openingBracketPosition = -1; - int counter = 0; - for (int i = 0; i < jsonPath.length(); i++) { - if (jsonPath.charAt(i) == '[' && jsonPath.charAt(i+1) == '?') { - if (openingBracketPosition == -1) { - openingBracketPosition = i; - } - counter++; - } - if (jsonPath.charAt(i) == ']' && counter > 0) { - counter--; - if (counter == 0) { - filters.add(jsonPath.substring(openingBracketPosition, i+1)); - openingBracketPosition = -1; - } - } - } - return filters; - } -} diff --git a/src/main/java/com/github/fge/jsonpatch/PathValueOperation.java b/src/main/java/com/github/fge/jsonpatch/PathValueOperation.java deleted file mode 100644 index e3dab45b..00000000 --- a/src/main/java/com/github/fge/jsonpatch/PathValueOperation.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) - * - * This 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 this file and of both licenses is available at the root of this - * project or, if you have the jar distribution, in directory META-INF/, 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 - */ - -package com.github.fge.jsonpatch; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.jsontype.TypeSerializer; - -import java.io.IOException; - -/** - * Base class for patch operations taking a value in addition to a path - */ -public abstract class PathValueOperation - extends JsonPatchOperation { - @JsonSerialize - protected final JsonNode value; - - /** - * Protected constructor - * - * @param op operation name - * @param path affected path - * @param value JSON value - */ - protected PathValueOperation(final String op, final String path, - final JsonNode value) { - super(op, path); - this.value = value.deepCopy(); - } - - @Override - public final void serialize(final JsonGenerator jgen, final SerializerProvider provider) throws IOException, JsonProcessingException { - jgen.writeStartObject(); - jgen.writeStringField("op", op); - jgen.writeStringField("path", path.toString()); - jgen.writeFieldName("value"); - jgen.writeTree(value); - jgen.writeEndObject(); - } - - @Override - public final void serializeWithType(final JsonGenerator jgen, final SerializerProvider provider, - final TypeSerializer typeSer) throws IOException, JsonProcessingException { - serialize(jgen, provider); - } - - public final JsonNode getValue() { - return value.deepCopy(); - } - - @Override - public final String toString() { - return "op: " + op + "; path: \"" + path + "\"; value: " + value; - } -} diff --git a/src/main/java/com/github/fge/jsonpatch/diff/DiffOperation.java b/src/main/java/com/github/fge/jsonpatch/diff/DiffOperation.java deleted file mode 100644 index 9cbe5505..00000000 --- a/src/main/java/com/github/fge/jsonpatch/diff/DiffOperation.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) - * - * This 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 this file and of both licenses is available at the root of this - * project or, if you have the jar distribution, in directory META-INF/, 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 - */ - -package com.github.fge.jsonpatch.diff; - -import com.fasterxml.jackson.databind.JsonNode; -import com.github.fge.jackson.jsonpointer.JsonPointer; -import com.github.fge.jsonpatch.AddOperation; -import com.github.fge.jsonpatch.CopyOperation; -import com.github.fge.jsonpatch.JsonPatchOperation; -import com.github.fge.jsonpatch.MoveOperation; -import com.github.fge.jsonpatch.RemoveOperation; -import com.github.fge.jsonpatch.ReplaceOperation; - -final class DiffOperation -{ - private final Type type; - /* An op's "from", if any */ - private final JsonPointer from; - /* Value displaced by this operation, if any */ - private final JsonNode oldValue; - /* An op's "path", if any */ - private final JsonPointer path; - /* An op's "value", if any */ - private final JsonNode value; - - static DiffOperation add(final JsonPointer path, - final JsonNode value) - { - return new DiffOperation(Type.ADD, null, null, path, value); - } - - static DiffOperation copy(final JsonPointer from, - final JsonPointer path, final JsonNode value) - { - return new DiffOperation(Type.COPY, from, null, path, - value); - } - - static DiffOperation move(final JsonPointer from, - final JsonNode oldValue, final JsonPointer path, - final JsonNode value) - { - return new DiffOperation(Type.MOVE, from, oldValue, path, - value); - } - - static DiffOperation remove(final JsonPointer from, - final JsonNode oldValue) - { - return new DiffOperation(Type.REMOVE, from, oldValue, null, null); - } - - static DiffOperation replace(final JsonPointer from, - final JsonNode oldValue, final JsonNode value) - { - return new DiffOperation(Type.REPLACE, from, oldValue, null, - value); - } - - private DiffOperation(final Type type, final JsonPointer from, - final JsonNode oldValue, final JsonPointer path, - final JsonNode value) - { - this.type = type; - this.from = from; - this.oldValue = oldValue; - this.path = path; - this.value = value; - } - - Type getType() - { - return type; - } - - JsonPointer getFrom() - { - return from; - } - - JsonNode getOldValue() - { - return oldValue; - } - - JsonPointer getPath() - { - return path; - } - - JsonNode getValue() - { - return value; - } - - JsonPatchOperation asJsonPatchOperation() - { - return type.toOperation(this); - } - - enum Type { - ADD - { - @Override - JsonPatchOperation toOperation(final DiffOperation op) - { - return new AddOperation(op.path.toString(), op.value); - } - }, - COPY - { - @Override - JsonPatchOperation toOperation(final DiffOperation op) - { - return new CopyOperation(op.from.toString(), op.path.toString()); - } - }, - MOVE - { - @Override - JsonPatchOperation toOperation(final DiffOperation op) - { - return new MoveOperation(op.from.toString(), op.path.toString()); - } - }, - REMOVE - { - @Override - JsonPatchOperation toOperation(final DiffOperation op) - { - return new RemoveOperation(op.from.toString()); - } - }, - REPLACE - { - @Override - JsonPatchOperation toOperation(final DiffOperation op) - { - return new ReplaceOperation(op.from.toString(), op.value); - } - }, - ; - - abstract JsonPatchOperation toOperation(final DiffOperation op); - } -} diff --git a/src/main/java/com/github/fge/jsonpatch/diff/DiffProcessor.java b/src/main/java/com/github/fge/jsonpatch/diff/DiffProcessor.java deleted file mode 100644 index 299d29a1..00000000 --- a/src/main/java/com/github/fge/jsonpatch/diff/DiffProcessor.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) - * - * This 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 this file and of both licenses is available at the root of this - * project or, if you have the jar distribution, in directory META-INF/, 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 - */ - -package com.github.fge.jsonpatch.diff; - -import com.fasterxml.jackson.databind.JsonNode; -import com.github.fge.jackson.JsonNumEquals; -import com.github.fge.jackson.jsonpointer.JsonPointer; -import com.github.fge.jsonpatch.JsonPatch; -import com.github.fge.jsonpatch.JsonPatchOperation; - -import javax.annotation.Nullable; -import java.util.*; - -// TODO: cleanup -final class DiffProcessor -{ - private static final JsonNumEquals EQUIVALENCE - = JsonNumEquals.getInstance(); - - private final Map unchanged; - - private final List diffs = new ArrayList(); - - DiffProcessor(final Map unchanged) - { - this.unchanged = Collections.unmodifiableMap(new HashMap(unchanged)); - } - - void valueReplaced(final JsonPointer pointer, final JsonNode oldValue, - final JsonNode newValue) - { - diffs.add(DiffOperation.replace(pointer, oldValue, newValue)); - } - - void valueRemoved(final JsonPointer pointer, final JsonNode value) - { - diffs.add(DiffOperation.remove(pointer, value)); - } - - void valueAdded(final JsonPointer pointer, final JsonNode value) - { - final int removalIndex = findPreviouslyRemoved(value); - if (removalIndex != -1) { - final DiffOperation removed = diffs.get(removalIndex); - diffs.remove(removalIndex); - diffs.add(DiffOperation.move(removed.getFrom(), - value, pointer, value)); - return; - } - final JsonPointer ptr = findUnchangedValue(value); - final DiffOperation op = ptr != null - ? DiffOperation.copy(ptr, pointer, value) - : DiffOperation.add(pointer, value); - - diffs.add(op); - } - - JsonPatch getPatch() - { - final List list = new ArrayList(); - - for (final DiffOperation op: diffs) - list.add(op.asJsonPatchOperation()); - - return new JsonPatch(list); - } - - @Nullable - private JsonPointer findUnchangedValue(final JsonNode value) - { - for (final Map.Entry entry: unchanged.entrySet()) - if (EQUIVALENCE.equivalent(value, entry.getValue())) - return entry.getKey(); - return null; - } - - private int findPreviouslyRemoved(final JsonNode value) - { - DiffOperation op; - - for (int i = 0; i < diffs.size(); i++) { - op = diffs.get(i); - if (op.getType() == DiffOperation.Type.REMOVE - && EQUIVALENCE.equivalent(value, op.getOldValue())) - return i; - } - return -1; - } -} diff --git a/src/main/java/com/github/fge/jsonpatch/diff/JsonDiff.java b/src/main/java/com/github/fge/jsonpatch/diff/JsonDiff.java deleted file mode 100644 index 302a79c2..00000000 --- a/src/main/java/com/github/fge/jsonpatch/diff/JsonDiff.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) - * - * This 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 this file and of both licenses is available at the root of this - * project or, if you have the jar distribution, in directory META-INF/, 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 - */ - -package com.github.fge.jsonpatch.diff; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.github.fge.jackson.JacksonUtils; -import com.github.fge.jackson.JsonNumEquals; -import com.github.fge.jackson.NodeType; -import com.github.fge.jackson.jsonpointer.JsonPointer; -import com.github.fge.jsonpatch.JsonPatch; -import com.github.fge.jsonpatch.JsonPatchMessages; -import com.github.fge.msgsimple.bundle.MessageBundle; -import com.github.fge.msgsimple.load.MessageBundles; - -import javax.annotation.ParametersAreNonnullByDefault; -import java.io.IOException; -import java.util.*; - -/** - * JSON "diff" implementation - * - *

This class generates a JSON Patch (as in, an RFC 6902 JSON Patch) given - * two JSON values as inputs. The patch can be obtained directly as a {@link - * JsonPatch} or as a {@link JsonNode}.

- * - *

Note: there is no guarantee about the usability of the generated - * patch for any other source/target combination than the one used to generate - * the patch.

- * - *

This class always performs operations in the following order: removals, - * additions and replacements. It then factors removal/addition pairs into - * move operations, or copy operations if a common element exists, at the same - * {@link JsonPointer pointer}, in both the source and destination.

- * - *

You can obtain a diff either as a {@link JsonPatch} directly or, for - * backwards compatibility, as a {@link JsonNode}.

- * - * @since 1.2 - */ -@ParametersAreNonnullByDefault -public final class JsonDiff -{ - private static final MessageBundle BUNDLE - = MessageBundles.getBundle(JsonPatchMessages.class); - private static final ObjectMapper MAPPER = JacksonUtils.newMapper(); - - private static final JsonNumEquals EQUIVALENCE - = JsonNumEquals.getInstance(); - - private JsonDiff() - { - } - - /** - * Generate a JSON patch for transforming the source node into the target - * node - * - * @param source the node to be patched - * @param target the expected result after applying the patch - * @return the patch as a {@link JsonPatch} - * - * @since 1.9 - */ - public static JsonPatch asJsonPatch(final JsonNode source, - final JsonNode target) - { - BUNDLE.checkNotNull(source, "common.nullArgument"); - BUNDLE.checkNotNull(target, "common.nullArgument"); - final Map unchanged - = getUnchangedValues(source, target); - final DiffProcessor processor = new DiffProcessor(unchanged); - - generateDiffs(processor, JsonPointer.empty(), source, target); - return processor.getPatch(); - } - - /** - * Generate a JSON patch for transforming the source node into the target - * node - * - * @param source the node to be patched - * @param target the expected result after applying the patch - * @return the patch as a {@link JsonNode} - */ - public static JsonNode asJson(final JsonNode source, final JsonNode target) - { - final String s; - try { - s = MAPPER.writeValueAsString(asJsonPatch(source, target)); - return MAPPER.readTree(s); - } catch (IOException e) { - throw new RuntimeException("cannot generate JSON diff", e); - } - } - - private static void generateDiffs(final DiffProcessor processor, - final JsonPointer pointer, final JsonNode source, final JsonNode target) - { - if (EQUIVALENCE.equivalent(source, target)) - return; - - final NodeType firstType = NodeType.getNodeType(source); - final NodeType secondType = NodeType.getNodeType(target); - - /* - * Node types differ: generate a replacement operation. - */ - if (firstType != secondType) { - processor.valueReplaced(pointer, source, target); - return; - } - - /* - * If we reach this point, it means that both nodes are the same type, - * but are not equivalent. - * - * If this is not a container, generate a replace operation. - */ - if (!source.isContainerNode()) { - processor.valueReplaced(pointer, source, target); - return; - } - - /* - * If we reach this point, both nodes are either objects or arrays; - * delegate. - */ - if (firstType == NodeType.OBJECT) - generateObjectDiffs(processor, pointer, (ObjectNode) source, - (ObjectNode) target); - else // array - generateArrayDiffs(processor, pointer, (ArrayNode) source, - (ArrayNode) target); - } - - private static void generateObjectDiffs(final DiffProcessor processor, - final JsonPointer pointer, final ObjectNode source, - final ObjectNode target) - { - final Set firstFields - = collect(source.fieldNames(), new TreeSet()); - final Set secondFields - = collect(target.fieldNames(), new TreeSet()); - - final Set copy1 = new HashSet(firstFields); - copy1.removeAll(secondFields); - - for (final String field: Collections.unmodifiableSet(copy1)) - processor.valueRemoved(pointer.append(field), source.get(field)); - - final Set copy2 = new HashSet(secondFields); - copy2.removeAll(firstFields); - - - for (final String field: Collections.unmodifiableSet(copy2)) - processor.valueAdded(pointer.append(field), target.get(field)); - - final Set intersection = new HashSet(firstFields); - intersection.retainAll(secondFields); - - for (final String field: intersection) - generateDiffs(processor, pointer.append(field), source.get(field), - target.get(field)); - } - - private static Set collect(Iterator from, Set to) { - if (from == null) { - throw new NullPointerException(); - } - if (to == null) { - throw new NullPointerException(); - } - while (from.hasNext()) { - to.add(from.next()); - } - return Collections.unmodifiableSet(to); - } - - - - private static void generateArrayDiffs(final DiffProcessor processor, - final JsonPointer pointer, final ArrayNode source, - final ArrayNode target) - { - final int firstSize = source.size(); - final int secondSize = target.size(); - final int size = Math.min(firstSize, secondSize); - - /* - * Source array is larger; in this case, elements are removed from the - * target; the index of removal is always the original arrays's length. - */ - for (int index = size; index < firstSize; index++) - processor.valueRemoved(pointer.append(size), source.get(index)); - - for (int index = 0; index < size; index++) - generateDiffs(processor, pointer.append(index), source.get(index), - target.get(index)); - - // Deal with the destination array being larger... - for (int index = size; index < secondSize; index++) - processor.valueAdded(pointer.append("-"), target.get(index)); - } - - - static Map getUnchangedValues(final JsonNode source, - final JsonNode target) - { - final Map ret = new HashMap(); - computeUnchanged(ret, JsonPointer.empty(), source, target); - return ret; - } - - private static void computeUnchanged(final Map ret, - final JsonPointer pointer, final JsonNode first, final JsonNode second) - { - if (EQUIVALENCE.equivalent(first, second)) { - ret.put(pointer, second); - return; - } - - final NodeType firstType = NodeType.getNodeType(first); - final NodeType secondType = NodeType.getNodeType(second); - - if (firstType != secondType) - return; // nothing in common - - // We know they are both the same type, so... - - switch (firstType) { - case OBJECT: - computeObject(ret, pointer, first, second); - break; - case ARRAY: - computeArray(ret, pointer, first, second); - break; - default: - /* nothing */ - } - } - - private static void computeObject(final Map ret, - final JsonPointer pointer, final JsonNode source, - final JsonNode target) - { - final Iterator firstFields = source.fieldNames(); - - String name; - - while (firstFields.hasNext()) { - name = firstFields.next(); - if (!target.has(name)) - continue; - computeUnchanged(ret, pointer.append(name), source.get(name), - target.get(name)); - } - } - - private static void computeArray(final Map ret, - final JsonPointer pointer, final JsonNode source, final JsonNode target) - { - final int size = Math.min(source.size(), target.size()); - - for (int i = 0; i < size; i++) - computeUnchanged(ret, pointer.append(i), source.get(i), - target.get(i)); - } -} diff --git a/src/main/java/com/github/fge/jsonpatch/mergepatch/JsonMergePatchDeserializer.java b/src/main/java/com/github/fge/jsonpatch/mergepatch/JsonMergePatchDeserializer.java deleted file mode 100644 index 27b15d08..00000000 --- a/src/main/java/com/github/fge/jsonpatch/mergepatch/JsonMergePatchDeserializer.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) - * - * This 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 this file and of both licenses is available at the root of this - * project or, if you have the jar distribution, in directory META-INF/, 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 - */ - -package com.github.fge.jsonpatch.mergepatch; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.ObjectCodec; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.NullNode; -import com.github.fge.jackson.JacksonUtils; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -final class JsonMergePatchDeserializer - extends JsonDeserializer -{ - /* - * FIXME! UGLY! HACK! - * - * We MUST have an ObjectCodec ready so that the parser in .deserialize() - * can actually do something useful -- for instance, deserializing even a - * JsonNode. - * - * Jackson does not do this automatically; I don't know why... - */ - private static final ObjectCodec CODEC = JacksonUtils.newMapper(); - - @Override - public JsonMergePatch deserialize(final JsonParser jp, - final DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - // FIXME: see comment above - jp.setCodec(CODEC); - final JsonNode node = jp.readValueAsTree(); - - /* - * Not an object: the simple case - */ - if (!node.isObject()) - return new NonObjectMergePatch(node); - - /* - * The complicated case... - * - * We have to build a set of removed members, plus a map of modified - * members. - */ - - final Set removedMembers = new HashSet(); - final Map modifiedMembers = new HashMap(); - final Iterator> iterator = node.fields(); - - Map.Entry entry; - - while (iterator.hasNext()) { - entry = iterator.next(); - if (entry.getValue().isNull()) - removedMembers.add(entry.getKey()); - else { - final JsonMergePatch value - = deserialize(entry.getValue().traverse(), ctxt); - modifiedMembers.put(entry.getKey(), value); - } - } - - return new ObjectMergePatch(removedMembers, modifiedMembers); - } - - /* - * This method MUST be overriden... The default is to return null, which is - * not what we want. - */ - @Override - @SuppressWarnings("deprecation") - public JsonMergePatch getNullValue() - { - return new NonObjectMergePatch(NullNode.getInstance()); - } -} diff --git a/src/main/java/com/github/fge/jsonpatch/mergepatch/ObjectMergePatch.java b/src/main/java/com/github/fge/jsonpatch/mergepatch/ObjectMergePatch.java deleted file mode 100644 index 009c3347..00000000 --- a/src/main/java/com/github/fge/jsonpatch/mergepatch/ObjectMergePatch.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) - * - * This 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 this file and of both licenses is available at the root of this - * project or, if you have the jar distribution, in directory META-INF/, 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 - */ - -package com.github.fge.jsonpatch.mergepatch; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.jsontype.TypeSerializer; -import com.fasterxml.jackson.databind.node.NullNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.github.fge.jackson.JacksonUtils; -import com.github.fge.jsonpatch.JsonPatchException; - -import javax.annotation.ParametersAreNonnullByDefault; -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -@ParametersAreNonnullByDefault -final class ObjectMergePatch - extends JsonMergePatch -{ - private final Set removedMembers; - private final Map modifiedMembers; - - ObjectMergePatch(final Set removedMembers, - final Map modifiedMembers) - { - this.removedMembers = Collections.unmodifiableSet(new HashSet(removedMembers)); - this.modifiedMembers = Collections.unmodifiableMap(new HashMap(modifiedMembers)); - } - - @Override - public JsonNode apply(final JsonNode input) - throws JsonPatchException - { - BUNDLE.checkNotNull(input, "jsonPatch.nullValue"); - /* - * If the input is an object, we make a deep copy of it - */ - final ObjectNode ret = input.isObject() ? (ObjectNode) input.deepCopy() - : JacksonUtils.nodeFactory().objectNode(); - - /* - * Our result is now a JSON Object; first, add (or modify) existing - * members in the result - */ - String key; - JsonNode value; - for (final Map.Entry entry: - modifiedMembers.entrySet()) { - key = entry.getKey(); - /* - * FIXME: ugly... - * - * We treat missing keys as null nodes; this "works" because in - * the modifiedMembers map, values are JsonMergePatch instances: - * - * * if it is a NonObjectMergePatch, the value is replaced - * unconditionally; - * * if it is an ObjectMergePatch, we get back here; the value will - * be replaced with a JSON Object anyway before being processed. - */ - final JsonNode jsonNode = ret.get(key); - value = jsonNode != null ? jsonNode : NullNode.getInstance(); - ret.replace(key, entry.getValue().apply(value)); - } - - ret.remove(removedMembers); - - return ret; - } - - @Override - public void serialize(final JsonGenerator jgen, - final SerializerProvider provider) - throws IOException, JsonProcessingException - { - jgen.writeStartObject(); - - /* - * Write removed members as JSON nulls - */ - for (final String member: removedMembers) - jgen.writeNullField(member); - - /* - * Write modified members; delegate to serialization for writing values - */ - for (final Map.Entry entry: - modifiedMembers.entrySet()) { - jgen.writeFieldName(entry.getKey()); - entry.getValue().serialize(jgen, provider); - } - - jgen.writeEndObject(); - } - - @Override - public void serializeWithType(final JsonGenerator jgen, - final SerializerProvider provider, final TypeSerializer typeSer) - throws IOException, JsonProcessingException - { - serialize(jgen, provider); - } -} diff --git a/src/main/java/com/gravity9/jsonpatch/AddOperation.java b/src/main/java/com/gravity9/jsonpatch/AddOperation.java new file mode 100644 index 00000000..9a7fb5c1 --- /dev/null +++ b/src/main/java/com/gravity9/jsonpatch/AddOperation.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) + * + * This 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 this file and of both licenses is available at the root of this + * project or, if you have the jar distribution, in directory META-INF/, 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 + */ + +package com.gravity9.jsonpatch; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; + + +/** + * JSON Patch {@code add} operation + * + *

For this operation, {@code path} is the JSON Pointer where the value + * should be added, and {@code value} is the value to add.

+ * + *

Note that if the target value pointed to by {@code path} already exists, + * it is replaced. In this case, {@code add} is equivalent to {@code replace}. + *

+ * + *

Note also that a value will be created at the target path if and only + * if the immediate parent of that value exists (and is of the correct + * type).

+ * + *

Finally, if the last reference token of the JSON Pointer is {@code -} and + * the immediate parent is an array, the given value is added at the end of the + * array. For instance, applying:

+ * + *
+ *     { "op": "add", "path": "/-", "value": 3 }
+ * 
+ * + *

to:

+ * + *
+ *     [ 1, 2 ]
+ * 
+ * + *

will give:

+ * + *
+ *     [ 1, 2, 3 ]
+ * 
+ */ +public final class AddOperation extends PathValueOperation { + + public static final String LAST_ARRAY_ELEMENT_SYMBOL = "-"; + + @JsonCreator + public AddOperation(@JsonProperty("path") final String path, + @JsonProperty("value") final JsonNode value) { + super("add", path, value); + } + + @Override + public JsonNode applyInternal(final JsonNode node) throws JsonPatchException { + if (path.isEmpty()) { + return value; + } + PathDetails pathDetails = PathParser.getParentPathAndNewNodeName(path); + final String pathToParent = pathDetails.getPathToParent(); + final String newNodeName = pathDetails.getNewNodeName(); + + final DocumentContext nodeContext = JsonPath.parse(node.deepCopy()); + final JsonNode evaluatedJsonParents = nodeContext.read(pathToParent); + if (evaluatedJsonParents == null) { + throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.noSuchParent")); + } + if (!evaluatedJsonParents.isContainerNode()) { + throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.parentNotContainer")); + } + + if (pathDetails.doesContainFiltersOrMultiIndexesNotation()) { // json filter result is always a list + for (int i = 0; i < evaluatedJsonParents.size(); i++) { + JsonNode parentNode = evaluatedJsonParents.get(i); + if (!parentNode.isContainerNode()) { + throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.parentNotContainer")); + } + DocumentContext containerContext = JsonPath.parse(parentNode); + if (parentNode.isArray()) { + addToArray(containerContext, "$", newNodeName); + } else { + addToObject(containerContext, "$", newNodeName); + } + } + return nodeContext.read("$"); + } else { + return evaluatedJsonParents.isArray() + ? addToArray(nodeContext, pathToParent, newNodeName) + : addToObject(nodeContext, pathToParent, newNodeName); + } + } + + private JsonNode addToArray(final DocumentContext node, String jsonPath, String newNodeName) throws JsonPatchException { + if (newNodeName.equals(LAST_ARRAY_ELEMENT_SYMBOL)) { + return node.add(jsonPath, value).read("$", JsonNode.class); + } + + final int size = node.read(jsonPath, JsonNode.class).size(); + final int index = verifyAndGetArrayIndex(newNodeName, size); + + ArrayNode updatedArray = node.read(jsonPath, ArrayNode.class).insert(index, value); + return "$".equals(jsonPath) ? updatedArray : node.set(jsonPath, updatedArray).read("$", JsonNode.class); + } + + private JsonNode addToObject(final DocumentContext node, String jsonPath, String newNodeName) { + return node + .put(jsonPath, newNodeName, value) + .read("$", JsonNode.class); + } + + private int verifyAndGetArrayIndex(String stringIndex, int size) throws JsonPatchException { + int index; + try { + index = Integer.parseInt(stringIndex); + } catch (NumberFormatException ignored) { + throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.notAnIndex")); + } + if (index < 0 || index > size) { + throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.noSuchIndex")); + } + return index; + } +} diff --git a/src/main/java/com/github/fge/jsonpatch/CopyOperation.java b/src/main/java/com/gravity9/jsonpatch/CopyOperation.java similarity index 69% rename from src/main/java/com/github/fge/jsonpatch/CopyOperation.java rename to src/main/java/com/gravity9/jsonpatch/CopyOperation.java index c35f5dac..9d4f669e 100644 --- a/src/main/java/com/github/fge/jsonpatch/CopyOperation.java +++ b/src/main/java/com/gravity9/jsonpatch/CopyOperation.java @@ -17,7 +17,7 @@ * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt */ -package com.github.fge.jsonpatch; +package com.gravity9.jsonpatch; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; @@ -42,18 +42,18 @@ */ public final class CopyOperation extends DualPathOperation { - @JsonCreator - public CopyOperation(@JsonProperty("from") final String from, @JsonProperty("path") final String path) { - super("copy", from, path); - } + @JsonCreator + public CopyOperation(@JsonProperty("from") final String from, @JsonProperty("path") final String path) { + super("copy", from, path); + } - @Override - public JsonNode applyInternal(final JsonNode node) throws JsonPatchException { - final String jsonPath = JsonPathParser.tmfStringToJsonPath(from); - final JsonNode dupData = JsonPath.parse(node.deepCopy()).read(jsonPath); - if (dupData == null) { - throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.noSuchPath")); - } - return new AddOperation(path, dupData).apply(node); - } + @Override + public JsonNode applyInternal(final JsonNode node) throws JsonPatchException { + final String jsonPath = JsonPathParser.tmfStringToJsonPath(from); + final JsonNode dupData = JsonPath.parse(node.deepCopy()).read(jsonPath); + if (dupData == null) { + throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.noSuchPath")); + } + return new AddOperation(path, dupData).apply(node); + } } diff --git a/src/main/java/com/gravity9/jsonpatch/DualPathOperation.java b/src/main/java/com/gravity9/jsonpatch/DualPathOperation.java new file mode 100644 index 00000000..15946096 --- /dev/null +++ b/src/main/java/com/gravity9/jsonpatch/DualPathOperation.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) + * + * This 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 this file and of both licenses is available at the root of this + * project or, if you have the jar distribution, in directory META-INF/, 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 + */ + +package com.gravity9.jsonpatch; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.jsontype.TypeSerializer; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import java.io.IOException; + +/** + * Base class for JSON Patch operations taking two JSON Pointers as arguments + */ +public abstract class DualPathOperation extends JsonPatchOperation { + + @JsonSerialize(using = ToStringSerializer.class) + protected final String from; + + /** + * Protected constructor + * + * @param op operation name + * @param from source path + * @param path destination path + */ + protected DualPathOperation(final String op, final String from, final String path) { + super(op, path); + this.from = from; + } + + @Override + public final void serialize(final JsonGenerator jgen, final SerializerProvider provider) throws IOException, JsonProcessingException { + jgen.writeStartObject(); + jgen.writeStringField("op", op); + jgen.writeStringField("path", path); + jgen.writeStringField("from", from); + jgen.writeEndObject(); + } + + @Override + public final void serializeWithType(final JsonGenerator jgen, final SerializerProvider provider, + final TypeSerializer typeSer) throws IOException, JsonProcessingException { + serialize(jgen, provider); + } + + public final String getFrom() { + return from; + } + + @Override + public final String toString() { + return "op: " + op + "; from: \"" + from + "\"; path: \"" + path + '"'; + } +} diff --git a/src/main/java/com/gravity9/jsonpatch/Iterables.java b/src/main/java/com/gravity9/jsonpatch/Iterables.java new file mode 100644 index 00000000..3e6ce222 --- /dev/null +++ b/src/main/java/com/gravity9/jsonpatch/Iterables.java @@ -0,0 +1,33 @@ +package com.gravity9.jsonpatch; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Iterables utility class + * + * @author {@literal @}soberich on 30-Nov-18 + */ +public final class Iterables { + + private Iterables() { + } + + /** + * Returns the last element of {@code iterable}. + * + * @param underlying type being iterated + * @param iterable type of iterable + * @return the last element of {@code iterable} + * @throws NoSuchElementException if the iterable is empty + */ + public static T getLast(Iterable iterable) { + Iterator iterator = iterable.iterator(); + while (true) { + T current = iterator.next(); + if (!iterator.hasNext()) { + return current; + } + } + } +} diff --git a/src/main/java/com/github/fge/jsonpatch/JsonPatch.java b/src/main/java/com/gravity9/jsonpatch/JsonPatch.java similarity index 55% rename from src/main/java/com/github/fge/jsonpatch/JsonPatch.java rename to src/main/java/com/gravity9/jsonpatch/JsonPatch.java index b41ed6bb..e656806b 100644 --- a/src/main/java/com/github/fge/jsonpatch/JsonPatch.java +++ b/src/main/java/com/gravity9/jsonpatch/JsonPatch.java @@ -17,7 +17,7 @@ * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt */ -package com.github.fge.jsonpatch; +package com.gravity9.jsonpatch; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.core.JsonGenerator; @@ -28,7 +28,6 @@ import com.github.fge.jackson.JacksonUtils; import com.github.fge.msgsimple.bundle.MessageBundle; import com.github.fge.msgsimple.load.MessageBundles; - import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -90,93 +89,86 @@ *

IMPORTANT NOTE: the JSON Patch is supposed to be VALID when the * constructor for this class ({@link JsonPatch#fromJson(JsonNode)} is used.

*/ -public final class JsonPatch - implements JsonSerializable, Patch -{ - private static final MessageBundle BUNDLE - = MessageBundles.getBundle(JsonPatchMessages.class); +public final class JsonPatch implements JsonSerializable, Patch { + + private static final MessageBundle BUNDLE + = MessageBundles.getBundle(JsonPatchMessages.class); - /** - * List of operations - */ - private final List operations; + /** + * List of operations + */ + private final List operations; - /** - * Constructor - * - *

Normally, you should never have to use it.

- * - * @param operations the list of operations for this patch - * @see JsonPatchOperation - */ - @JsonCreator - public JsonPatch(final List operations) - { - this.operations = Collections.unmodifiableList(new ArrayList(operations)); - } + /** + * Constructor + * + *

Normally, you should never have to use it.

+ * + * @param operations the list of operations for this patch + * @see JsonPatchOperation + */ + @JsonCreator + public JsonPatch(final List operations) { + this.operations = Collections.unmodifiableList(new ArrayList(operations)); + } - /** - * Static factory method to build a JSON Patch out of a JSON representation - * - * @param node the JSON representation of the generated JSON Patch - * @return a JSON Patch - * @throws IOException input is not a valid JSON patch - * @throws NullPointerException input is null - */ - public static JsonPatch fromJson(final JsonNode node) - throws IOException - { - BUNDLE.checkNotNull(node, "jsonPatch.nullInput"); - return JacksonUtils.getReader().forType(JsonPatch.class) - .readValue(node); - } + /** + * Static factory method to build a JSON Patch out of a JSON representation + * + * @param node the JSON representation of the generated JSON Patch + * @return a JSON Patch + * @throws IOException input is not a valid JSON patch + * @throws NullPointerException input is null + */ + public static JsonPatch fromJson(final JsonNode node) + throws IOException { + BUNDLE.checkNotNull(node, "jsonPatch.nullInput"); + return JacksonUtils.getReader().forType(JsonPatch.class) + .readValue(node); + } - /** - * Apply this patch to a JSON value - * - * @param node the value to apply the patch to - * @return the patched JSON value - * @throws JsonPatchException failed to apply patch - * @throws NullPointerException input is null - */ - @Override - public JsonNode apply(final JsonNode node) - throws JsonPatchException - { - BUNDLE.checkNotNull(node, "jsonPatch.nullInput"); - JsonNode ret = node; - for (final JsonPatchOperation operation: operations) - ret = operation.apply(ret); + /** + * Apply this patch to a JSON value + * + * @param node the value to apply the patch to + * @return the patched JSON value + * @throws JsonPatchException failed to apply patch + * @throws NullPointerException input is null + */ + @Override + public JsonNode apply(final JsonNode node) + throws JsonPatchException { + BUNDLE.checkNotNull(node, "jsonPatch.nullInput"); + JsonNode ret = node; + for (final JsonPatchOperation operation : operations) + ret = operation.apply(ret); - return ret; - } + return ret; + } - public final List getOperations() { - return operations; - } + public final List getOperations() { + return operations; + } - @Override - public String toString() - { - return operations.toString(); - } + @Override + public String toString() { + return operations.toString(); + } - @Override - public void serialize(final JsonGenerator jgen, - final SerializerProvider provider) - throws IOException - { - jgen.writeStartArray(); - for (final JsonPatchOperation op: operations) - op.serialize(jgen, provider); - jgen.writeEndArray(); - } + @Override + public void serialize(final JsonGenerator jgen, + final SerializerProvider provider) + throws IOException { + jgen.writeStartArray(); + for (final JsonPatchOperation op : operations) + op.serialize(jgen, provider); + jgen.writeEndArray(); + } - @Override - public void serializeWithType(final JsonGenerator jgen, - final SerializerProvider provider, final TypeSerializer typeSer) - throws IOException - { - serialize(jgen, provider); - } + @Override + public void serializeWithType(final JsonGenerator jgen, + final SerializerProvider provider, final TypeSerializer typeSer) + throws IOException { + serialize(jgen, provider); + } } diff --git a/src/main/java/com/github/fge/jsonpatch/JsonPatchException.java b/src/main/java/com/gravity9/jsonpatch/JsonPatchException.java similarity index 68% rename from src/main/java/com/github/fge/jsonpatch/JsonPatchException.java rename to src/main/java/com/gravity9/jsonpatch/JsonPatchException.java index 088f1b87..15976291 100644 --- a/src/main/java/com/github/fge/jsonpatch/JsonPatchException.java +++ b/src/main/java/com/gravity9/jsonpatch/JsonPatchException.java @@ -17,18 +17,15 @@ * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt */ -package com.github.fge.jsonpatch; +package com.gravity9.jsonpatch; -public final class JsonPatchException - extends Exception -{ - public JsonPatchException(final String message) - { - super(message); - } +public final class JsonPatchException extends Exception { - public JsonPatchException(final String message, final Throwable cause) - { - super(message, cause); - } + public JsonPatchException(final String message) { + super(message); + } + + public JsonPatchException(final String message, final Throwable cause) { + super(message, cause); + } } diff --git a/src/main/java/com/github/fge/jsonpatch/JsonPatchMessages.java b/src/main/java/com/gravity9/jsonpatch/JsonPatchMessages.java similarity index 76% rename from src/main/java/com/github/fge/jsonpatch/JsonPatchMessages.java rename to src/main/java/com/gravity9/jsonpatch/JsonPatchMessages.java index a67583bf..1dec9c48 100644 --- a/src/main/java/com/github/fge/jsonpatch/JsonPatchMessages.java +++ b/src/main/java/com/gravity9/jsonpatch/JsonPatchMessages.java @@ -17,18 +17,16 @@ * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt */ -package com.github.fge.jsonpatch; +package com.gravity9.jsonpatch; import com.github.fge.msgsimple.bundle.MessageBundle; import com.github.fge.msgsimple.bundle.PropertiesBundle; import com.github.fge.msgsimple.load.MessageBundleLoader; -public final class JsonPatchMessages - implements MessageBundleLoader -{ - @Override - public MessageBundle getBundle() - { - return PropertiesBundle.forPath("/com/github/fge/jsonpatch/messages"); - } +public final class JsonPatchMessages implements MessageBundleLoader { + + @Override + public MessageBundle getBundle() { + return PropertiesBundle.forPath("/com/github/fge/jsonpatch/messages"); + } } diff --git a/src/main/java/com/github/fge/jsonpatch/JsonPatchOperation.java b/src/main/java/com/gravity9/jsonpatch/JsonPatchOperation.java similarity index 50% rename from src/main/java/com/github/fge/jsonpatch/JsonPatchOperation.java rename to src/main/java/com/gravity9/jsonpatch/JsonPatchOperation.java index a94808d1..72f3f73d 100644 --- a/src/main/java/com/github/fge/jsonpatch/JsonPatchOperation.java +++ b/src/main/java/com/gravity9/jsonpatch/JsonPatchOperation.java @@ -17,7 +17,7 @@ * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt */ -package com.github.fge.jsonpatch; +package com.gravity9.jsonpatch; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; @@ -43,12 +43,12 @@ @JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "op") @JsonSubTypes({ - @Type(name = "add", value = AddOperation.class), - @Type(name = "copy", value = CopyOperation.class), - @Type(name = "move", value = MoveOperation.class), - @Type(name = "remove", value = RemoveOperation.class), - @Type(name = "replace", value = ReplaceOperation.class), - @Type(name = "test", value = TestOperation.class) + @Type(name = "add", value = AddOperation.class), + @Type(name = "copy", value = CopyOperation.class), + @Type(name = "move", value = MoveOperation.class), + @Type(name = "remove", value = RemoveOperation.class), + @Type(name = "replace", value = ReplaceOperation.class), + @Type(name = "test", value = TestOperation.class) }) /* @@ -67,73 +67,74 @@ */ @JsonIgnoreProperties(ignoreUnknown = true) public abstract class JsonPatchOperation implements JsonSerializable { - protected static final MessageBundle BUNDLE = MessageBundles.getBundle(JsonPatchMessages.class); - - static { - Configuration.setDefaults(new Configuration.Defaults() { - @Override - public JsonProvider jsonProvider() { - return new JacksonJsonNodeJsonProvider(); - } - - @Override - public Set