blob: c414633ee6e54a1c729cc80f3fce36b399462477 [file] [log] [blame]
package com.fasterxml.jackson.core.filter;
import java.io.IOException;
import com.fasterxml.jackson.core.*;
/**
* Alternative variant of {@link JsonStreamContext}, used when filtering
* content being read or written (based on {@link TokenFilter}).
*
* @since 2.6
*/
public class TokenFilterContext extends JsonStreamContext
{
/**
* Parent context for this context; null for root context.
*/
protected final TokenFilterContext _parent;
/*
/**********************************************************
/* Simple instance reuse slots; speed up things
/* a bit (10-15%) for docs with lots of small
/* arrays/objects
/**********************************************************
*/
protected TokenFilterContext _child;
/*
/**********************************************************
/* Location/state information
/**********************************************************
*/
/**
* Name of the field of which value is to be parsed; only
* used for OBJECT contexts
*/
protected String _currentName;
/**
* Filter to use for items in this state (for properties of Objects,
* elements of Arrays, and root-level values of root context)
*/
protected TokenFilter _filter;
/**
* Flag that indicates that start token has been read/written,
* so that matching close token needs to be read/written as well
* when context is getting closed.
*/
protected boolean _startHandled;
/**
* Flag that indicates that the current name of this context
* still needs to be read/written, if path from root down to
* included leaf is to be exposed.
*/
protected boolean _needToHandleName;
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
protected TokenFilterContext(int type, TokenFilterContext parent,
TokenFilter filter, boolean startHandled)
{
super();
_type = type;
_parent = parent;
_filter = filter;
_index = -1;
_startHandled = startHandled;
_needToHandleName = false;
}
protected TokenFilterContext reset(int type,
TokenFilter filter, boolean startWritten)
{
_type = type;
_filter = filter;
_index = -1;
_currentName = null;
_startHandled = startWritten;
_needToHandleName = false;
return this;
}
/*
/**********************************************************
/* Factory methods
/**********************************************************
*/
public static TokenFilterContext createRootContext(TokenFilter filter) {
// true -> since we have no start/end marker, consider start handled
return new TokenFilterContext(TYPE_ROOT, null, filter, true);
}
public TokenFilterContext createChildArrayContext(TokenFilter filter, boolean writeStart) {
TokenFilterContext ctxt = _child;
if (ctxt == null) {
_child = ctxt = new TokenFilterContext(TYPE_ARRAY, this, filter, writeStart);
return ctxt;
}
return ctxt.reset(TYPE_ARRAY, filter, writeStart);
}
public TokenFilterContext createChildObjectContext(TokenFilter filter, boolean writeStart) {
TokenFilterContext ctxt = _child;
if (ctxt == null) {
_child = ctxt = new TokenFilterContext(TYPE_OBJECT, this, filter, writeStart);
return ctxt;
}
return ctxt.reset(TYPE_OBJECT, filter, writeStart);
}
/*
/**********************************************************
/* State changes
/**********************************************************
*/
public TokenFilter setFieldName(String name) throws JsonProcessingException {
_currentName = name;
_needToHandleName = true;
return _filter;
}
/**
* Method called to check whether value is to be included at current output
* position, either as Object property, Array element, or root value.
*/
public TokenFilter checkValue(TokenFilter filter) {
// First, checks for Object properties have been made earlier:
if (_type == TYPE_OBJECT) {
return filter;
}
// We increase it first because at the beginning of array, value is -1
int ix = ++_index;
if (_type == TYPE_ARRAY) {
return filter.includeElement(ix);
}
return filter.includeRootValue(ix);
}
/**
* Method called to ensure that parent path from root is written up to
* and including this node.
*/
public void writePath(JsonGenerator gen) throws IOException
{
if ((_filter == null) || (_filter == TokenFilter.INCLUDE_ALL)) {
return;
}
if (_parent != null) {
_parent._writePath(gen);
}
if (_startHandled) {
// even if Object started, need to start leaf-level name
if (_needToHandleName) {
gen.writeFieldName(_currentName);
}
} else {
_startHandled = true;
if (_type == TYPE_OBJECT) {
gen.writeStartObject();
gen.writeFieldName(_currentName); // we know name must be written
} else if (_type == TYPE_ARRAY) {
gen.writeStartArray();
}
}
}
/**
* Variant of {@link #writePath(JsonGenerator)} called when all we
* need is immediately surrounding Object. Method typically called
* when including a single property but not including full path
* to root.
*/
public void writeImmediatePath(JsonGenerator gen) throws IOException
{
if ((_filter == null) || (_filter == TokenFilter.INCLUDE_ALL)) {
return;
}
if (_startHandled) {
// even if Object started, need to start leaf-level name
if (_needToHandleName) {
gen.writeFieldName(_currentName);
}
} else {
_startHandled = true;
if (_type == TYPE_OBJECT) {
gen.writeStartObject();
if (_needToHandleName) {
gen.writeFieldName(_currentName);
}
} else if (_type == TYPE_ARRAY) {
gen.writeStartArray();
}
}
}
private void _writePath(JsonGenerator gen) throws IOException
{
if ((_filter == null) || (_filter == TokenFilter.INCLUDE_ALL)) {
return;
}
if (_parent != null) {
_parent._writePath(gen);
}
if (_startHandled) {
// even if Object started, need to start leaf-level name
if (_needToHandleName) {
_needToHandleName = false; // at parent must explicitly clear
gen.writeFieldName(_currentName);
}
} else {
_startHandled = true;
if (_type == TYPE_OBJECT) {
gen.writeStartObject();
if (_needToHandleName) {
_needToHandleName = false; // at parent must explicitly clear
gen.writeFieldName(_currentName);
}
} else if (_type == TYPE_ARRAY) {
gen.writeStartArray();
}
}
}
public TokenFilterContext closeArray(JsonGenerator gen) throws IOException
{
if (_startHandled) {
gen.writeEndArray();
}
if ((_filter != null) && (_filter != TokenFilter.INCLUDE_ALL)) {
_filter.filterFinishArray();
}
return _parent;
}
public TokenFilterContext closeObject(JsonGenerator gen) throws IOException
{
if (_startHandled) {
gen.writeEndObject();
}
if ((_filter != null) && (_filter != TokenFilter.INCLUDE_ALL)) {
_filter.filterFinishObject();
}
return _parent;
}
public void skipParentChecks() {
_filter = null;
for (TokenFilterContext ctxt = _parent; ctxt != null; ctxt = ctxt._parent) {
_parent._filter = null;
}
}
/*
/**********************************************************
/* Accessors, mutators
/**********************************************************
*/
@Override
public Object getCurrentValue() { return null; }
@Override
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; }
public JsonToken nextTokenToRead() {
if (!_startHandled) {
_startHandled = true;
if (_type == TYPE_OBJECT) {
return JsonToken.START_OBJECT;
}
// Note: root should never be unhandled
return JsonToken.START_ARRAY;
}
// But otherwise at most might have FIELD_NAME
if (_needToHandleName && (_type == TYPE_OBJECT)) {
_needToHandleName = false;
return JsonToken.FIELD_NAME;
}
return null;
}
public TokenFilterContext findChildOf(TokenFilterContext parent) {
if (_parent == parent) {
return this;
}
TokenFilterContext curr = _parent;
while (curr != null) {
TokenFilterContext p = curr._parent;
if (p == parent) {
return curr;
}
curr = p;
}
// should never occur but...
return null;
}
// // // Internally used abstract methods
protected void appendDesc(StringBuilder sb) {
if (_parent != null) {
_parent.appendDesc(sb);
}
if (_type == TYPE_OBJECT) {
sb.append('{');
if (_currentName != null) {
sb.append('"');
// !!! TODO: Name chars should be escaped?
sb.append(_currentName);
sb.append('"');
} else {
sb.append('?');
}
sb.append('}');
} else if (_type == TYPE_ARRAY) {
sb.append('[');
sb.append(getCurrentIndex());
sb.append(']');
} else {
// nah, ROOT:
sb.append("/");
}
}
// // // Overridden standard methods
/**
* Overridden to provide developer writeable "JsonPath" representation
* of the context.
*/
@Override public String toString() {
StringBuilder sb = new StringBuilder(64);
appendDesc(sb);
return sb.toString();
}
}