Skip to content

Commit cdd66ea

Browse files
committed
Implement most of #1980, array part incomplete
1 parent b486824 commit cdd66ea

File tree

7 files changed

+229
-57
lines changed

7 files changed

+229
-57
lines changed

src/main/java/com/fasterxml/jackson/databind/node/ArrayNode.java

+46-9
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
11
package com.fasterxml.jackson.databind.node;
22

3+
import java.io.IOException;
4+
import java.math.BigDecimal;
5+
import java.math.BigInteger;
6+
import java.util.*;
7+
38
import com.fasterxml.jackson.core.*;
49
import com.fasterxml.jackson.core.type.WritableTypeId;
10+
511
import com.fasterxml.jackson.databind.JsonNode;
612
import com.fasterxml.jackson.databind.SerializerProvider;
713
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
814
import com.fasterxml.jackson.databind.util.RawValue;
915

10-
import java.io.IOException;
11-
import java.math.BigDecimal;
12-
import java.math.BigInteger;
13-
import java.util.ArrayList;
14-
import java.util.Collection;
15-
import java.util.Comparator;
16-
import java.util.Iterator;
17-
import java.util.List;
18-
1916
/**
2017
* Node class that represents Arrays mapped from JSON content.
2118
*<p>
@@ -68,6 +65,45 @@ public ArrayNode deepCopy()
6865
return ret;
6966
}
7067

68+
@Override
69+
protected ObjectNode _withObject(JsonPointer origPtr,
70+
JsonPointer currentPtr,
71+
OverwriteMode overwriteMode, boolean preferIndex)
72+
{
73+
if (currentPtr.matches()) {
74+
// Cannot return, not an ObjectNode so:
75+
return null;
76+
}
77+
JsonNode n = _at(currentPtr);
78+
// If there's a path, follow it
79+
if ((n != null) && (n instanceof BaseJsonNode)) {
80+
ObjectNode found = ((BaseJsonNode) n)._withObject(origPtr, currentPtr.tail(),
81+
overwriteMode, preferIndex);
82+
if (found != null) {
83+
return found;
84+
}
85+
// Ok no; must replace if allowed to
86+
if (!_withObjectMayReplace(n, overwriteMode)) {
87+
return _reportWrongNodeType(
88+
"Cannot replace `JsonNode` of type `%s` for property \"%s\" in JSON Pointer \"%s\" (mode %s)",
89+
n.getClass().getName(), currentPtr.getMatchingProperty(),
90+
origPtr, overwriteMode);
91+
}
92+
}
93+
// Either way; must replace or add a new property
94+
return _withObjectCreateTail(currentPtr, preferIndex);
95+
}
96+
97+
protected ObjectNode _withObjectCreateTail(JsonPointer tail, boolean preferIndex)
98+
{
99+
// !!! TBI
100+
if (true) {
101+
throw new Error("Need to create tail for: "+tail);
102+
}
103+
return null;
104+
}
105+
106+
/*
71107
@Override
72108
protected ObjectNode _withObjectCreatePath(JsonPointer origPtr,
73109
JsonPointer currentPtr,
@@ -101,6 +137,7 @@ protected ObjectNode _withObjectCreatePath(JsonPointer origPtr,
101137
}
102138
return (ObjectNode) currentNode;
103139
}
140+
*/
104141

105142
/*
106143
/**********************************************************

src/main/java/com/fasterxml/jackson/databind/node/BaseJsonNode.java

+41-33
Original file line numberDiff line numberDiff line change
@@ -111,51 +111,55 @@ public JsonParser.NumberType numberType() {
111111

112112
@Override
113113
public ObjectNode withObject(JsonPointer ptr,
114-
OverwriteMode overwriteMode, boolean preferIndex) {
115-
if (!isObject()) {
116-
// To avoid abstract method, base implementation just fails
117-
_reportWrongNodeType("Can only call `withObject()` on `ObjectNode`, not `%s`",
118-
getClass().getName());
119-
}
120-
return _withObject(ptr, ptr, overwriteMode, preferIndex);
121-
}
122-
123-
protected ObjectNode _withObject(JsonPointer origPtr,
124-
JsonPointer currentPtr,
125114
OverwriteMode overwriteMode, boolean preferIndex)
126115
{
127-
if (currentPtr.matches()) {
116+
// Degenerate case of using with "empty" path; ok if ObjectNode
117+
if (ptr.matches()) {
128118
if (this instanceof ObjectNode) {
129119
return (ObjectNode) this;
130120
}
131-
return _reportWrongNodeType(
132-
"`JsonNode` matching `JsonPointer` \"%s\" must be `ObjectNode`, not `%s`",
133-
origPtr.toString(),
134-
getClass().getName());
121+
_reportWrongNodeType("Can only call `withObject()` with empty JSON Pointer on `ObjectNode`, not `%s`",
122+
getClass().getName());
135123
}
136-
JsonNode n = _at(currentPtr);
137-
// If there's a path, follow it
138-
if ((n != null) && (n instanceof BaseJsonNode)) {
139-
return ((BaseJsonNode) n)._withObject(origPtr, currentPtr.tail(),
140-
overwriteMode, preferIndex);
124+
// Otherwise check recursively
125+
ObjectNode n = _withObject(ptr, ptr, overwriteMode, preferIndex);
126+
if (n == null) {
127+
_reportWrongNodeType("Cannot replace context node (of type `%s`) using `withObject()` with JSON Pointer '%s'",
128+
getClass().getName(), ptr);
141129
}
142-
return _withObjectCreatePath(origPtr, currentPtr, overwriteMode, preferIndex);
130+
return n;
143131
}
144132

145-
/**
146-
* Helper method for constructing specified path under this node, if possible;
147-
* or throwing an exception if not. If construction successful, needs to return
148-
* the innermost {@code ObjectNode} constructed.
149-
*/
150-
protected ObjectNode _withObjectCreatePath(JsonPointer origPtr,
133+
protected ObjectNode _withObject(JsonPointer origPtr,
151134
JsonPointer currentPtr,
152135
OverwriteMode overwriteMode, boolean preferIndex)
153136
{
154-
// Cannot traverse non-container nodes:
155-
return _reportWrongNodeType(
156-
"`JsonPointer` path \"%s\" cannot traverse non-container node of type `%s`",
157-
origPtr.toString(),
158-
getClass().getName());
137+
// Three-part logic:
138+
//
139+
// 1) If we are at the end of JSON Pointer; if so, return
140+
// `this` if Object node, `null` if not (for caller to handle)
141+
// 2) If not at the end, if we can follow next segment, call recursively
142+
// handle non-null (existing Object node, return)
143+
// vs `null` (must replace; may not be allowed to)
144+
// 3) Can not follow the segment? Try constructing, adding path
145+
//
146+
// But the default implementation assumes non-container behavior so
147+
// it'll simply return `null`
148+
return null;
149+
}
150+
151+
protected boolean _withObjectMayReplace(JsonNode node, OverwriteMode overwriteMode) {
152+
switch (overwriteMode) {
153+
case NONE:
154+
return false;
155+
case NULLS:
156+
return node.isNull();
157+
case SCALARS:
158+
return !node.isContainerNode();
159+
default:
160+
case ALL:
161+
return true;
162+
}
159163
}
160164

161165
/*
@@ -209,4 +213,8 @@ public String toPrettyString() {
209213
protected <T> T _reportWrongNodeType(String msgTemplate, Object...args) {
210214
throw new UnsupportedOperationException(String.format(msgTemplate, args));
211215
}
216+
217+
protected <T> T _reportWrongNodeOperation(String msgTemplate, Object...args) {
218+
throw new UnsupportedOperationException(String.format(msgTemplate, args));
219+
}
212220
}

src/main/java/com/fasterxml/jackson/databind/node/ContainerNode.java

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.math.BigInteger;
55

66
import com.fasterxml.jackson.core.*;
7+
78
import com.fasterxml.jackson.databind.JsonNode;
89
import com.fasterxml.jackson.databind.util.RawValue;
910

@@ -54,6 +55,11 @@ protected ContainerNode(JsonNodeFactory nc) {
5455
@Override
5556
public abstract JsonNode get(String fieldName);
5657

58+
@Override
59+
protected abstract ObjectNode _withObject(JsonPointer origPtr,
60+
JsonPointer currentPtr,
61+
OverwriteMode overwriteMode, boolean preferIndex);
62+
5763
/*
5864
/**********************************************************
5965
/* JsonNodeCreator implementation, Enumerated/singleton types

src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java

+98-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import com.fasterxml.jackson.core.*;
99
import com.fasterxml.jackson.core.type.WritableTypeId;
10+
1011
import com.fasterxml.jackson.databind.*;
1112
import com.fasterxml.jackson.databind.cfg.JsonNodeFeature;
1213
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
@@ -23,6 +24,12 @@ public class ObjectNode
2324
{
2425
private static final long serialVersionUID = 1L; // since 2.10
2526

27+
/**
28+
* Constant that defines maximum {@code JsonPointer} element index we
29+
* use for inserts.
30+
*/
31+
protected final static int MAX_ELEMENT_INDEX_FOR_INSERT = 9999;
32+
2633
// Note: LinkedHashMap for backwards compatibility
2734
protected final Map<String, JsonNode> _children;
2835

@@ -61,19 +68,103 @@ public ObjectNode deepCopy()
6168
}
6269

6370
@Override
64-
protected ObjectNode _withObjectCreatePath(JsonPointer origPtr,
71+
protected ObjectNode _withObject(JsonPointer origPtr,
6572
JsonPointer currentPtr,
6673
OverwriteMode overwriteMode, boolean preferIndex)
6774
{
68-
ObjectNode currentNode = this;
69-
while (!currentPtr.matches()) {
70-
// Should we try to build Arrays? For now, nope.
71-
currentNode = currentNode.putObject(currentPtr.getMatchingProperty());
72-
currentPtr = currentPtr.tail();
75+
if (currentPtr.matches()) {
76+
return this;
77+
}
78+
79+
JsonNode n = _at(currentPtr);
80+
81+
// If there's a path, follow it
82+
if ((n != null) && (n instanceof BaseJsonNode)) {
83+
ObjectNode found = ((BaseJsonNode) n)._withObject(origPtr, currentPtr.tail(),
84+
overwriteMode, preferIndex);
85+
if (found != null) {
86+
return found;
87+
}
88+
// Ok no; must replace if allowed to
89+
if (!_withObjectMayReplace(n, overwriteMode)) {
90+
return _reportWrongNodeType(
91+
"Cannot replace `JsonNode` of type `%s` for property \"%s\" in JSON Pointer \"%s\" (mode `OverwriteMode.%s`)",
92+
n.getClass().getName(), currentPtr.getMatchingProperty(),
93+
origPtr, overwriteMode);
94+
}
95+
}
96+
// Either way; must replace or add a new property
97+
return _withObjectAddTailProperty(currentPtr, preferIndex, this);
98+
}
99+
100+
protected ObjectNode _withObjectAddTailProperty(JsonPointer tail, boolean preferIndex,
101+
ObjectNode currObject)
102+
{
103+
final String propName = tail.getMatchingProperty();
104+
tail = tail.tail();
105+
106+
// First: did we complete traversal? If so, easy, we got our result
107+
if (tail.matches()) {
108+
return currObject.putObject(propName);
109+
}
110+
111+
// Otherwise, do we want Array or Object
112+
if (_withObjectChooseArray(tail, preferIndex)) { // array!
113+
return _withObjectAddTailElement(tail, preferIndex,
114+
currObject.putArray(propName));
73115
}
74-
return currentNode;
116+
return _withObjectAddTailProperty(tail, preferIndex,
117+
currObject.putObject(propName));
75118
}
76119

120+
protected ObjectNode _withObjectAddTailElement(JsonPointer tail, boolean preferIndex,
121+
ArrayNode currArray)
122+
{
123+
final int index = tail.getMatchingIndex();
124+
tail = tail.tail();
125+
126+
// First: did we complete traversal? If so, easy, we got our result
127+
if (tail.matches()) {
128+
ObjectNode result = currArray.objectNode();
129+
_withObjectSetArrayElement(currArray, index, result);
130+
return result;
131+
}
132+
133+
// Otherwise, do we want Array or Object
134+
if (_withObjectChooseArray(tail, preferIndex)) { // array!
135+
ArrayNode next = currArray.arrayNode();
136+
_withObjectSetArrayElement(currArray, index, next);
137+
return _withObjectAddTailElement(tail, preferIndex, next);
138+
}
139+
ObjectNode next = currArray.objectNode();
140+
_withObjectSetArrayElement(currArray, index, next);
141+
return _withObjectAddTailProperty(tail, preferIndex, next);
142+
}
143+
144+
protected boolean _withObjectChooseArray(JsonPointer ptr, boolean preferIndex)
145+
{
146+
if (preferIndex) {
147+
final int ix = ptr.getMatchingIndex();
148+
if (ix >= 0) {
149+
// 27-Jul-2022, tatu: Let's make it less likely anyone OOMs by
150+
// humongous index...
151+
if (ix <= MAX_ELEMENT_INDEX_FOR_INSERT) {
152+
return true;
153+
}
154+
_reportWrongNodeOperation("Too big Array index (%d; max %d) to use for insert with `JsonPointer`",
155+
ix, MAX_ELEMENT_INDEX_FOR_INSERT, ptr);
156+
}
157+
}
158+
return false;
159+
}
160+
161+
protected void _withObjectSetArrayElement(ArrayNode array, int index, JsonNode value) {
162+
while (index >= array.size()) {
163+
array.addNull();
164+
}
165+
array.set(index, value);
166+
}
167+
77168
/*
78169
/**********************************************************
79170
/* Overrides for JsonSerializable.Base

src/main/java/com/fasterxml/jackson/databind/util/internal/LinkedDeque.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
* concurrent access by multiple threads. Null elements are prohibited.
3030
* <p>
3131
* Most <tt>LinkedDeque</tt> operations run in constant time by assuming that
32-
* the {@link Linked} parameter is associated with the deque instance. Any usage
32+
* the {@code Linked} parameter is associated with the deque instance. Any usage
3333
* that violates this assumption will result in non-deterministic behavior.
3434
* <p>
3535
* The iterators returned by this class are <em>not</em> <i>fail-fast</i>: If

src/main/java/com/fasterxml/jackson/databind/util/internal/PrivateMaxEntriesMap.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
* An entry is evicted from the map when the entry size exceeds
5858
* its <tt>maximum capacity</tt> threshold.
5959
* <p>
60-
* An {@link EvictionListener} may be supplied for notification when an entry
60+
* An {@code EvictionListener} may be supplied for notification when an entry
6161
* is evicted from the map. This listener is invoked on a caller's thread and
6262
* will not block other threads from operating on the map. An implementation
6363
* should be aware that the caller's thread will not expect long execution

0 commit comments

Comments
 (0)