com.networknt.jsonoverlay.PropertiesOverlay Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of json-overlay Show documentation
Show all versions of json-overlay Show documentation
A fork of RepreZen JsonOverlay with all dependencies and code generation removed
/*********************************************************************
* Copyright (c) 2017 ModelSolv, Inc. and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* ModelSolv, Inc.
* - initial API and implementation and/or initial documentation
**********************************************************************/
package com.networknt.jsonoverlay;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.networknt.jsonoverlay.SerializationOptions.Option;
public abstract class PropertiesOverlay extends JsonOverlay {
// retrieve property values from this map by property name
private Map> children = new HashMap<>();
// this queue sets ordering for serialization, so it matches parsed JSON
private List childOrder = new CopyOnWriteArrayList<>();
private boolean elaborated = false;
private boolean deferElaboration = false;
private V elaborationValue = null;
protected PropertiesOverlay(JsonNode json, JsonOverlay> parent, OverlayFactory factory,
ReferenceManager refMgr) {
super(json, parent, factory, refMgr);
this.deferElaboration = json.isMissingNode();
}
protected PropertiesOverlay(V value, JsonOverlay> parent, OverlayFactory factory, ReferenceManager refMgr) {
super(value, parent, factory, refMgr);
this.elaborationValue = value;
}
protected T _get(String name, Class cls) {
return _get(name, true, cls);
}
/* package */ List _getPropertyNames() {
return childOrder.stream().map(locator -> locator.getName()).collect(Collectors.toList());
}
/* package */ JsonOverlay> _getOverlay(String name) {
return children.get(name);
}
protected boolean _isPresent(String name) {
JsonOverlay> overlay = children.get(name);
return overlay != null && !overlay.json.isMissingNode();
}
protected T _get(String name, boolean elaborate, Class cls) {
if (elaborate) {
_ensureElaborated();
}
@SuppressWarnings("unchecked")
JsonOverlay overlay = (JsonOverlay) children.get(name);
return overlay._get();
}
protected JsonOverlay _getOverlay(String name, Class cls) {
@SuppressWarnings("unchecked")
JsonOverlay overlay = (JsonOverlay) children.get(name);
return overlay;
}
protected void _setScalar(String name, T val, Class cls) {
@SuppressWarnings("unchecked")
JsonOverlay overlay = (JsonOverlay) children.get(name);
overlay._set(val);
}
protected List _getList(String name, Class cls) {
@SuppressWarnings("unchecked")
List list = _get(name, List.class);
return list;
}
protected List _getList(String name, boolean elaborate, Class cls) {
@SuppressWarnings("unchecked")
List list = _get(name, elaborate, List.class);
return list;
}
protected T _get(String name, int index, Class cls) {
return _get(name, index, true, cls);
}
protected T _get(String name, int index, boolean elaborate, Class cls) {
@SuppressWarnings("unchecked")
ListOverlay overlay = (ListOverlay) children.get(name);
return overlay.get(index);
}
protected void _setList(String name, List listVal, Class cls) {
@SuppressWarnings("unchecked")
ListOverlay overlay = (ListOverlay) children.get(name);
overlay._set(listVal);
}
protected void _set(String name, int index, T val, Class cls) {
@SuppressWarnings("unchecked")
ListOverlay overlay = (ListOverlay) children.get(name);
overlay.set(index, val);
}
protected void _insert(String name, int index, T val, Class cls) {
@SuppressWarnings("unchecked")
ListOverlay overlay = (ListOverlay) children.get(name);
overlay.insert(index, val);
}
protected void _add(String name, T val, Class cls) {
@SuppressWarnings("unchecked")
ListOverlay overlay = (ListOverlay) children.get(name);
overlay.add(val);
}
protected void _remove(String name, int index, Class cls) {
@SuppressWarnings("unchecked")
ListOverlay overlay = (ListOverlay) children.get(name);
overlay.remove(index);
}
protected Map _getMap(String name, Class cls) {
@SuppressWarnings("unchecked")
Map map = _get(name, Map.class);
return map;
}
protected Map _getMap(String name, boolean elaborate, Class cls) {
@SuppressWarnings("unchecked")
Map map = _get(name, elaborate, Map.class);
return map;
}
protected T _get(String name, String key, Class cls) {
return _get(name, key, true, cls);
}
protected T _get(String name, String key, boolean elaborate, Class cls) {
@SuppressWarnings("unchecked")
MapOverlay overlay = (MapOverlay) children.get(name);
return overlay.get(key);
}
protected void _setMap(String name, Map mapVal, Class cls) {
@SuppressWarnings("unchecked")
MapOverlay overlay = (MapOverlay) children.get(name);
overlay._set(mapVal);
}
protected void _set(String name, String key, T val, Class cls) {
@SuppressWarnings("unchecked")
MapOverlay overlay = (MapOverlay) children.get(name);
overlay.set(key, val);
}
protected void _remove(String name, String key, Class cls) {
@SuppressWarnings("unchecked")
MapOverlay overlay = (MapOverlay) children.get(name);
overlay.remove(key);
}
@Override
protected void _elaborate(boolean atCreation) {
if (atCreation && deferElaboration) {
return;
}
if (elaborationValue != null) {
_elaborateValue();
} else {
_elaborateJson();
}
Collections.sort(childOrder);
elaborated = true;
}
protected void _elaborateJson() {
// need an implemenatation here because subclasses invoke method in
// super, to
// support type extensions
}
private void _elaborateValue() {
@SuppressWarnings("unchecked")
PropertiesOverlay overlay = (PropertiesOverlay) elaborationValue;
children.clear();
for (Entry> entry : overlay.children.entrySet()) {
children.put(entry.getKey(), entry.getValue()._copy());
}
childOrder.clear();
childOrder.addAll(overlay.childOrder);
this.elaborationValue = null;
}
@Override
public boolean _isElaborated() {
return elaborated;
}
protected JsonOverlay _createScalar(String name, String path, OverlayFactory factory) {
return _addChild(name, path, factory);
}
protected ListOverlay _createList(String name, String path, OverlayFactory itemFactory) {
return (ListOverlay) _addChild(name, path, ListOverlay.getFactory(itemFactory));
}
protected MapOverlay _createMap(String name, String path, OverlayFactory valueFactory,
String keyPattern) {
return (MapOverlay) _addChild(name, path, MapOverlay.getFactory(valueFactory, keyPattern));
}
private JsonOverlay _addChild(String name, String path, OverlayFactory factory) {
JsonPointer pointer = JsonPointer.compile(path.isEmpty() ? "" : "/" + path);
JsonNode childJson = json.at(pointer);
JsonOverlay child = factory.create(childJson, this, refMgr);
child._setPathInParent(path);
PropertyLocator locator = new PropertyLocator(name, path, json);
childOrder.add(locator);
children.put(name, child);
return child;
}
@Override
protected JsonOverlay> _findInternal(JsonPointer path) {
for (JsonOverlay> child : children.values()) {
if (matchesPath(child, path)) {
JsonOverlay> found = child._find(tailPath(child, path));
if (found != null) {
return found;
}
}
}
return null;
}
private boolean matchesPath(JsonOverlay> child, JsonPointer path) {
JsonPointer childPath = getPointer(child);
while (!childPath.matches()) {
if (!childPath.matchesProperty(path.getMatchingProperty())) {
return false;
} else {
path = path.tail();
childPath = childPath.tail();
}
}
return true;
}
private JsonPointer tailPath(JsonOverlay> child, JsonPointer path) {
JsonPointer childPath = getPointer(child);
while (!childPath.matches()) {
path = path.tail();
childPath = childPath.tail();
}
return path;
}
private JsonPointer getPointer(JsonOverlay> child) {
String path = child._getPathInParent();
return JsonPointer.compile(path == null || path.isEmpty() ? "" : "/" + path);
}
@Override
public V _fromJson(JsonNode json) {
// parsing of the json node is expected to be done in the constructor of
// the
// subclass, so nothing is done here. But we do establish this object as
// its own
// value.
@SuppressWarnings("unchecked")
V result = (V) this;
return result;
}
@Override
protected JsonNode _toJsonInternal(SerializationOptions options) {
JsonNode obj = _jsonMissing();
for (PropertyLocator child : childOrder) {
JsonNode childJson = children.get(child.getName())._toJson(options.minus(Option.KEEP_ONE_EMPTY));
if (!childJson.isMissingNode()) {
obj = _injectChild(obj, childJson, child.getPointer());
}
}
JsonNode result = _fixJson(obj);
return result.size() > 0 || options.isKeepThisEmpty() ? result : _jsonMissing();
}
private JsonNode _injectChild(JsonNode node, JsonNode child, JsonPointer pointer) {
if (pointer.matches()) {
// inject into current node, which means:
// * If current is missing, return child
// * If current and child are both objects, merge child into current
// * Otherwise error
if (node.isMissingNode()) {
return child;
} else if (node.isObject() && child.isObject()) {
((ObjectNode) node).setAll((ObjectNode) child);
return node;
} else {
throw new IllegalArgumentException();
}
} else if (node.isObject() || node.isMissingNode()) {
String name = pointer.getMatchingProperty();
JsonNode childNode = _injectChild(node.path(name), child, pointer.tail());
if (!childNode.isMissingNode()) {
node = node.isObject() ? node : _jsonObject();
((ObjectNode) node).set(name, childNode);
}
return node;
} else {
// can't add a property name to a non-object
throw new IllegalArgumentException();
}
}
protected JsonNode _fixJson(JsonNode json) {
return json;
}
@Override
public boolean equals(Object other) {
return equals(other, false);
}
public boolean equals(Object other, boolean sameOrder) {
if (other != null && getClass() == other.getClass()) {
PropertiesOverlay> otherPO = (PropertiesOverlay>) other;
if (elaborated != otherPO.elaborated) {
return false;
}
if (children != null ? !children.equals(otherPO.children) : otherPO.children != null) {
return false;
}
if (sameOrder) {
return childOrder != null ? childOrder.equals(otherPO.childOrder) : otherPO.childOrder == null;
} else {
return true;
}
}
return false;
}
@Override
public int hashCode() {
int hash = 7;
hash = 31 * hash + (children != null ? children.hashCode() : 0);
hash = 31 * hash + (childOrder != null ? childOrder.hashCode() : 0);
return hash;
}
protected static class PropertyLocator implements Comparable {
private final String name;
private final JsonPointer pointer;
private final List vector;
public PropertyLocator(String name, String path, JsonNode json) {
this.name = name;
this.pointer = JsonPointer.compile(path.isEmpty() ? "" : "/" + path);
this.vector = computeVector(pointer, json);
}
public String getName() {
return name;
}
public JsonPointer getPointer() {
return pointer;
}
private List computeVector(JsonPointer pointer, JsonNode json) {
// the "position vector" computed by this method is key to keeping our children
// sorted by the order in which they appeared in a parsed JSON object. The
// vector list contains the index, in each level of object nesting, of the
// property on the path to each child's value. The ordering of these vectors
// thus represents the order in which the properties appeared in the parsed
// JSON, with missing properties arbitrarily ordered at the end. Root-level maps
// are also ordered at the end. They are either partial, in which case their
// actual members may be scattered among other properties and we don't try to
// maintain that ordering, or they represent the entire root object, in which
// case the ordering is irrelevant.
JsonNode currentJson = json;
List result = new ArrayList<>();
// we only consider object nodes and continue until our pointer is
// fully
// consumed
while (currentJson instanceof ObjectNode && !pointer.matches()) {
String key = pointer.getMatchingProperty();
boolean found = false;
int i = 0;
for (Iterator iter = currentJson.fieldNames(); iter.hasNext(); i += 1) {
if (key.equals(iter.next())) {
found = true;
result.add(i);
currentJson = currentJson.get(key);
pointer = pointer.tail();
break;
}
}
if (!found) {
// no match at current level, so child is not present -
// exclude from ordering
return null;
}
}
// empty vector means the path was empty and matched the root json
// object. This
// occurs only with maps, which are excluded from ordering.
return result.isEmpty() ? null : result;
}
@Override
public int compareTo(PropertyLocator other) {
if (vector == null) {
return other.vector == null ? name.compareTo(other.name) : 1;
} else if (other.vector == null) {
return -1;
} else {
int cmp = 0;
// first component where paths differ determines relative
// ordering
for (int i = 0; cmp == 0 && i < vector.size() && i < other.vector.size(); i++) {
cmp = vector.get(i) - other.vector.get(i);
}
return cmp;
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((pointer == null) ? 0 : pointer.hashCode());
result = prime * result + ((vector == null) ? 0 : vector.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PropertyLocator other = (PropertyLocator) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (pointer == null) {
if (other.pointer != null)
return false;
} else if (!pointer.equals(other.pointer))
return false;
if (vector == null) {
if (other.vector != null)
return false;
} else if (!vector.equals(other.vector))
return false;
return true;
}
@Override
public String toString() {
return String.format("Loc[%s]=%s", name, vector);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy