io.swagger.transform.util.MutableJsonTree Maven / Gradle / Ivy
package io.swagger.transform.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.fge.jackson.jsonpointer.JsonPointer;
import com.github.fge.jsonpatch.JsonPatch;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import io.swagger.transform.migrate.SwaggerMigrator;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.Objects;
import static io.swagger.transform.util.SwaggerMigrators.fromPatch;
/**
* A mutable JSON tree with traversal capabilities
*
* Navigation through the tree is done using {@link JsonPointer}s.
*
* Note that {@link JsonNode} is mutable. This means that at init
* time we make a copy of the node given as argument (using {@link
* JsonNode#deepCopy()}, but after that all nodes returned by {@link
* #getCurrentNode()} are mutable. Changes you make to the returned node
* therefore will be reflected in the result.
*
* While you can do that, it is advised that you use {@link SwaggerMigrator}s
* instead and make use of the {@link #applyMigrator(SwaggerMigrator)} and
* {@link #applyMigratorToElements(SwaggerMigrator)} methods.
*/
@ParametersAreNonnullByDefault
public final class MutableJsonTree {
private JsonNode baseNode;
private JsonNode currentNode;
private JsonPointer currentPointer = JsonPointer.empty();
/**
* Constructor
*
* @param node the node to transform (copied)
*/
public MutableJsonTree(final JsonNode node) {
Objects.requireNonNull(node, "node must not be null");
baseNode = node.deepCopy();
currentNode = baseNode;
}
/**
* Absolute pointer change
*
* The new current node will be the node at this pointer starting from
* the base node.
*
* @param pointer the pointer
* @throws IllegalArgumentException no JSON value at given pointer
*/
public void setPointer(final JsonPointer pointer) {
Objects.requireNonNull(pointer, "pointer must not be null");
doSetPointer(pointer);
}
/**
* Relative pointer change
*
* The pointer in argument is appended to the current pointer (using
* {@link JsonPointer#append(JsonPointer)}) and the current node is set
* accordingly.
*
* @param pointer the pointer to append
* @throws IllegalArgumentException no JSON value at given pointer
*/
public void appendPointer(final JsonPointer pointer) {
Objects.requireNonNull(pointer, "pointer must not be null");
doSetPointer(currentPointer.append(pointer));
}
private void doSetPointer(final JsonPointer pointer) {
currentPointer = pointer;
currentNode = currentPointer.path(baseNode);
Preconditions.checkArgument(!currentNode.isMissingNode(),
"base node has no element at pointer " + currentPointer);
}
/**
* Return the base node
*
* The "base node" here means the (potentially) altered copy of the node
* supplied as the constructor argument.
*
* @return the base node
*/
public JsonNode getBaseNode() {
return baseNode;
}
/**
* Get the value at the current pointer (always guaranteed to exist)
*
* @return the (mutable) node
*/
public JsonNode getCurrentNode() {
return currentNode;
}
/**
* Apply a JSON Patch to the current node
*
* This will turn the patch into a {@link SwaggerMigrator} using {@link
* SwaggerMigrators#fromPatch(JsonPatch)} and call {@link
* #applyMigrator(SwaggerMigrator)}.
*
* @param patch the JSON Patch to apply
* @throws SwaggerMigrationException same as {@link
* #applyMigrator(SwaggerMigrator)}
*/
public void applyPatch(final JsonPatch patch)
throws SwaggerMigrationException {
applyMigrator(fromPatch(patch));
}
/**
* Apply a migrator to the node at the current pointer
*
* It is assumed here that the current node is a JSON Object.
*
* @param migrator the migrator to apply
* @throws SwaggerMigrationException current node is not an object, or the
* migrator failed to apply
*/
public void applyMigrator(final SwaggerMigrator migrator)
throws SwaggerMigrationException {
final JsonPointer parent = currentPointer.parent();
if (!parent.get(baseNode).isObject()) {
throw new SwaggerMigrationException();
}
final ObjectNode parentNode = (ObjectNode) parent.get(baseNode);
final JsonNode patched = migrator.migrate(currentNode);
if (currentPointer.isEmpty()) {
baseNode = currentNode = patched;
return;
}
final String memberName = Iterables.getLast(currentPointer)
.getToken().getRaw();
parentNode.put(memberName, patched);
}
/**
* Apply a migrator to all elements of the array at the current pointer
*
* Note that if the migrator fails to apply to at least one element, the
* original array is left untouched; its elements are replaced if and only
* if the migrator applies successfully to all elements.
*
*
* @param migrator the migrator to apply
* @throws SwaggerMigrationException current node is not a JSON Array, or
* migrator failed to apply to at least one array element
*/
public void applyMigratorToElements(final SwaggerMigrator migrator)
throws SwaggerMigrationException {
if (!currentNode.isArray()) {
throw new SwaggerMigrationException();
}
final ArrayNode array = (ArrayNode) currentNode;
final ArrayNode transformed = array.arrayNode();
for (final JsonNode element : array) {
transformed.add(migrator.migrate(element));
}
array.removeAll().addAll(transformed);
}
/**
* Apply a JSON Patch to all elements of a JSON Array
*
* This will wrap the patch into a {@link SwaggerMigrator} using {@link
* SwaggerMigrators#fromPatch(JsonPatch)} and call {@link
* #applyMigratorToElements(SwaggerMigrator)}.
*
* @param patch the JSON Patch to apply
* @throws SwaggerMigrationException same as {@link
* #applyMigratorToElements(SwaggerMigrator)}
*/
public void applyPatchToElements(final JsonPatch patch)
throws SwaggerMigrationException {
applyMigratorToElements(fromPatch(patch));
}
@Override
public String toString() {
return "current pointer: " + currentPointer;
}
}