Skip to content

Commit

Permalink
Fix #306
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Mar 22, 2017
1 parent 7e57c55 commit f008130
Show file tree
Hide file tree
Showing 8 changed files with 424 additions and 6 deletions.
1 change: 1 addition & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ JSON library.
#17: Add 'JsonGenerator.writeString(Reader r, int charLength)'
(constributed by Logan W)
#304: Optimize `NumberOutput.outputLong()` method
#306: Add new method in `JsonStreamContext` to construct `JsonPointer`
#312: Add `JsonProcessingException.clearLocation()` to allow clearing
possibly security-sensitive information
(contributed by Alex Y)
Expand Down
101 changes: 100 additions & 1 deletion src/main/java/com/fasterxml/jackson/core/JsonPointer.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* It may be used in future for filtering of streaming JSON content
* as well (not implemented yet for 2.3).
*<p>
* Instances are fully immutable and can be shared, cached.
* Instances are fully immutable and can be cached, shared between threads.
*
* @author Tatu Saloranta
*
Expand Down Expand Up @@ -137,6 +137,86 @@ public static JsonPointer compile(String input) throws IllegalArgumentException
*/
public static JsonPointer valueOf(String input) { return compile(input); }

/**
* Factory method that will construct a pointer instance that describes
* path to location given {@link JsonStreamContext} points to.
*
* @param context Context to build pointer expression fot
* @param includeRoot Whether to include number offset for virtual "root context"
* or not.
*
* @since 2.9
*/
public static JsonPointer forPath(JsonStreamContext context,
boolean includeRoot)
{
// First things first: last segment may be for START_ARRAY/START_OBJECT,
// in which case it does not yet point to anything, and should be skipped
if (context == null) {
return EMPTY;
}
if (!context.hasPathSegment()) {
// one special case; do not prune root if we need it
if (!(includeRoot && context.inRoot() && context.hasCurrentIndex())) {
context = context.getParent();
}
}
JsonPointer tail = null;

for (; context != null; context = context.getParent()) {
if (context.inObject()) {
String seg = context.getCurrentName();
if (seg == null) { // is this legal?
seg = "";
}
tail = new JsonPointer(_fullPath(tail, seg), seg, tail);
} else if (context.inArray() || includeRoot) {
int ix = context.getCurrentIndex();
String ixStr = String.valueOf(ix);
tail = new JsonPointer(_fullPath(tail, ixStr), ixStr, ix, tail);
}
// NOTE: this effectively drops ROOT node(s); should have 1 such node,
// as the last one, but we don't have to care (probably some paths have
// no root, for example)
}
if (tail == null) {
return EMPTY;
}
return tail;
}

private static String _fullPath(JsonPointer tail, String segment)
{
if (tail == null) {
StringBuilder sb = new StringBuilder(segment.length()+1);
sb.append('/');
_appendEscaped(sb, segment);
return sb.toString();
}
String tailDesc = tail._asString;
StringBuilder sb = new StringBuilder(segment.length() + 1 + tailDesc.length());
sb.append('/');
_appendEscaped(sb, segment);
sb.append(tailDesc);
return sb.toString();
}

private static void _appendEscaped(StringBuilder sb, String segment)
{
for (int i = 0, end = segment.length(); i < end; ++i) {
char c = segment.charAt(i);
if (c == '/') {
sb.append("~1");
continue;
}
if (c == '~') {
sb.append("~0");
continue;
}
sb.append(c);
}
}

/* Factory method that composes a pointer instance, given a set
* of 'raw' segments: raw meaning that no processing will be done,
* no escaping may is present.
Expand Down Expand Up @@ -189,13 +269,32 @@ public JsonPointer last() {
return current;
}

/**
* Mutant factory method that will return
*<ul>
* <li>`tail` if `this` instance is "empty" pointer, OR
* </li>
* <li>`this` instance if `tail` is "empty" pointer, OR
* </li>
* <li>Newly constructed {@link JsonPointer} instance that starts with all segments
* of `this`, followed by all segments of `tail`.
* </li>
*</ul>
*
* @param tail {@link JsonPointer} instance to append to this one, to create a new pointer instance
*
* @return Either `this` instance, `tail`, or a newly created combination, as per description above.
*/
public JsonPointer append(JsonPointer tail) {
if (this == EMPTY) {
return tail;
}
if (tail == EMPTY) {
return this;
}
// 21-Mar-2017, tatu: Not superbly efficient; could probably improve by not concatenating,
// re-decoding -- by stitching together segments -- but for now should be fine.

String currentJsonPointer = _asString;
if (currentJsonPointer.endsWith("/")) {
//removes final slash
Expand Down
67 changes: 66 additions & 1 deletion src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public String typeDesc() {
}
return "?";
}

/**
* @return Number of entries that are complete and started.
*/
Expand All @@ -112,13 +112,53 @@ public String typeDesc() {
*/
public final int getCurrentIndex() { return (_index < 0) ? 0 : _index; }

/**
* Method that may be called to verify whether this context has valid index:
* will return `false` before the first entry of Object context or before
* first element of Array context; otherwise returns `true`.
*
* @since 2.9
*/
public boolean hasCurrentIndex() { return _index >= 0; }

/**
* Method that may be called to check if this context is either:
*<ul>
* <li>Object, with at least one entry written (partially or completely)
* </li>
* <li>Array, with at least one entry written (partially or completely)
* </li>
*</ul>
* and if so, return `true`; otherwise return `false`. Latter case includes
* Root context (always), and Object/Array contexts before any entries/elements
* have been read or written.
*<p>
* Method is mostly used to determine whether this context should be used for
* constructing {@link JsonPointer}
*
* @since 2.9
*/
public boolean hasPathSegment() {
if (_type == TYPE_OBJECT) {
return hasCurrentName();
} else if (_type == TYPE_ARRAY) {
return hasCurrentIndex();
}
return false;
}

/**
* Method for accessing name associated with the current location.
* Non-null for <code>FIELD_NAME</code> and value events that directly
* follow field names; null for root level and array values.
*/
public abstract String getCurrentName();

/**
* @since 2.9
*/
public boolean hasCurrentName() { return getCurrentName() != null; }

/**
* Method for accessing currently active value being used by data-binding
* (as the source of streaming data to write, or destination of data being
Expand All @@ -145,4 +185,29 @@ public Object getCurrentValue() {
* @since 2.5
*/
public void setCurrentValue(Object v) { }

/**
* Factory method for constructing a {@link JsonPointer} that points to the current
* location within the stream that this context is for, excluding information about
* "root context" (only relevant for multi-root-value cases)
*
* @since 2.9
*/
public JsonPointer pathAsPointer() {
return JsonPointer.forPath(this, false);
}

/**
* Factory method for constructing a {@link JsonPointer} that points to the current
* location within the stream that this context is for, optionally including
* "root value index"
*
* @param includeRoot Whether root-value offset is included as the first segment or not;
*
*
* @since 2.9
*/
public JsonPointer pathAsPointer(boolean includeRoot) {
return JsonPointer.forPath(this, includeRoot);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ public void setCurrentValue(Object v) { }

@Override public final TokenFilterContext getParent() { return _parent; }
@Override public final String getCurrentName() { return _currentName; }
// @since 2.9
@Override public boolean hasCurrentName() { return _currentName != null; }

public TokenFilter getFilter() { return _filter; }
public boolean isStartHandled() { return _startHandled; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ protected void reset(int type, int lineNr, int colNr) {
}

/*
public void trackDups(JsonParser jp) {
_dups = DupDetector.rootDetector(jp);
public void trackDups(JsonParser p) {
_dups = DupDetector.rootDetector(p);
}
*/

Expand Down Expand Up @@ -140,6 +140,10 @@ public JsonReadContext createChildObjectContext(int lineNr, int colNr) {
*/

@Override public String getCurrentName() { return _currentName; }

// @since 2.9
@Override public boolean hasCurrentName() { return _currentName != null; }

@Override public JsonReadContext getParent() { return _parent; }

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ public JsonWriteContext createChildObjectContext() {

@Override public final JsonWriteContext getParent() { return _parent; }
@Override public final String getCurrentName() { return _currentName; }
// @since 2.9
@Override public boolean hasCurrentName() { return _currentName != null; }

/**
* Method that can be used to both clear the accumulated references
Expand Down
Loading

0 comments on commit f008130

Please sign in to comment.