blob: 7c7978336422bf71d84ff8f68d3902e2f495cac2 [file] [log] [blame]
Tatu Salorantadfd69092015-04-09 22:29:34 -07001package com.fasterxml.jackson.core.filter;
2
3import java.io.IOException;
4import java.io.OutputStream;
5import java.math.BigDecimal;
6import java.math.BigInteger;
7
8import com.fasterxml.jackson.core.*;
9import com.fasterxml.jackson.core.util.JsonParserDelegate;
10
11/**
12 * Specialized {@link JsonParserDelegate} that allows use of
13 * {@link TokenFilter} for outputting a subset of content that
14 * is visible to caller
15 *
16 * @since 2.6
17 */
18public class FilteringParserDelegate extends JsonParserDelegate
19{
20 /*
21 /**********************************************************
22 /* Configuration
23 /**********************************************************
24 */
25
26 /**
27 * Object consulted to determine whether to write parts of content generator
28 * is asked to write or not.
29 */
30 protected TokenFilter rootFilter;
31
32 /**
33 * Flag that determines whether filtering will continue after the first
34 * match is indicated or not: if `false`, output is based on just the first
35 * full match (returning {@link TokenFilter#INCLUDE_ALL}) and no more
36 * checks are made; if `true` then filtering will be applied as necessary
37 * until end of content.
38 */
39 protected boolean _allowMultipleMatches;
40
41 /**
42 * Flag that determines whether path leading up to included content should
43 * also be automatically included or not. If `false`, no path inclusion is
44 * done and only explicitly included entries are output; if `true` then
45 * path from main level down to match is also included as necessary.
46 */
47 protected boolean _includePath;
48
49 /*
50 /**********************************************************
51 /* State
52 /**********************************************************
53 */
54
55 /**
56 * Last token retrieved via {@link #nextToken}, if any.
57 * Null before the first call to <code>nextToken()</code>,
58 * as well as if token has been explicitly cleared
59 */
60 protected JsonToken _currToken;
61
62 /**
63 * Last cleared token, if any: that is, value that was in
64 * effect when {@link #clearCurrentToken} was called.
65 */
66 protected JsonToken _lastClearedToken;
67
68 /**
Cowtowncoder00900312015-04-14 16:02:33 -070069 * During traversal this is the actual "open" parse tree, which sometimes
70 * is the same as {@link #_exposedContext}, and at other times is ahead
71 * of it. Note that this context is never null.
Tatu Salorantadfd69092015-04-09 22:29:34 -070072 */
Cowtowncoder00900312015-04-14 16:02:33 -070073 protected TokenFilterContext _headContext;
Tatu Salorantadfd69092015-04-09 22:29:34 -070074
75 /**
Cowtowncoder00900312015-04-14 16:02:33 -070076 * In cases where {@link #_headContext} is "ahead" of context exposed to
77 * caller, this context points to what is currently exposed to caller.
78 * When the two are in sync, this context reference will be <code>null</code>.
79 */
80 protected TokenFilterContext _exposedContext;
Cowtowncoder9a797fb2015-04-14 16:12:12 -070081
Cowtowncoder00900312015-04-14 16:02:33 -070082 /**
Tatu Salorantadfd69092015-04-09 22:29:34 -070083 * State that applies to the item within container, used where applicable.
84 * Specifically used to pass inclusion state between property name and
85 * property, and also used for array elements.
86 */
87 protected TokenFilter _itemFilter;
88
89 /**
90 * Number of tokens for which {@link TokenFilter#INCLUDE_ALL}
Tatu Saloranta5a5d1192015-04-13 23:05:07 -070091 * has been returned.
Tatu Salorantadfd69092015-04-09 22:29:34 -070092 */
93 protected int _matchCount;
94
95 /*
96 /**********************************************************
97 /* Construction, initialization
98 /**********************************************************
99 */
100
101 public FilteringParserDelegate(JsonParser p, TokenFilter f,
102 boolean includePath, boolean allowMultipleMatches)
103 {
104 super(p);
105 rootFilter = f;
106 // and this is the currently active filter for root values
107 _itemFilter = f;
Cowtowncoder00900312015-04-14 16:02:33 -0700108 _headContext = TokenFilterContext.createRootContext(f);
Tatu Salorantadfd69092015-04-09 22:29:34 -0700109 _includePath = includePath;
110 _allowMultipleMatches = allowMultipleMatches;
111 }
112
113 /*
114 /**********************************************************
115 /* Extended API
116 /**********************************************************
117 */
118
119 public TokenFilter getFilter() { return rootFilter; }
120
121 /**
122 * Accessor for finding number of matches, where specific token and sub-tree
123 * starting (if structured type) are passed.
124 */
125 public int getMatchCount() {
126 return _matchCount;
127 }
128
129 /*
130 /**********************************************************
131 /* Public API, token accessors
132 /**********************************************************
133 */
134
135 @Override public JsonToken getCurrentToken() { return _currToken; }
136
137 @Override public final int getCurrentTokenId() {
138 final JsonToken t = _currToken;
139 return (t == null) ? JsonTokenId.ID_NO_TOKEN : t.id();
140 }
141
142 @Override public boolean hasCurrentToken() { return _currToken != null; }
143 @Override public boolean hasTokenId(int id) {
144 final JsonToken t = _currToken;
145 if (t == null) {
146 return (JsonTokenId.ID_NO_TOKEN == id);
147 }
148 return t.id() == id;
149 }
150
151 @Override public final boolean hasToken(JsonToken t) {
152 return (_currToken == t);
153 }
154
155 @Override public boolean isExpectedStartArrayToken() { return _currToken == JsonToken.START_ARRAY; }
156 @Override public boolean isExpectedStartObjectToken() { return _currToken == JsonToken.START_OBJECT; }
157
158 @Override public JsonLocation getCurrentLocation() { return delegate.getCurrentLocation(); }
Tatu Salorantadfd69092015-04-09 22:29:34 -0700159
Cowtowncoder00900312015-04-14 16:02:33 -0700160 @Override
161 public JsonStreamContext getParsingContext() {
162 return _filterContext();
163 }
Tatu Salorantadfd69092015-04-09 22:29:34 -0700164
Cowtowncoder00900312015-04-14 16:02:33 -0700165 // !!! TODO: Verify it works as expected: copied from standard JSON parser impl
166 @Override
167 public String getCurrentName() throws IOException {
168 JsonStreamContext ctxt = _filterContext();
169 if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
170 JsonStreamContext parent = ctxt.getParent();
171 return (parent == null) ? null : parent.getCurrentName();
172 }
173 return ctxt.getCurrentName();
174 }
175
Tatu Salorantadfd69092015-04-09 22:29:34 -0700176 /*
177 /**********************************************************
178 /* Public API, token state overrides
179 /**********************************************************
180 */
181
182 @Override
183 public void clearCurrentToken() {
184 if (_currToken != null) {
185 _lastClearedToken = _currToken;
186 _currToken = null;
187 }
188 }
189
190 @Override
191 public JsonToken getLastClearedToken() { return _lastClearedToken; }
192
Tatu Salorantadfd69092015-04-09 22:29:34 -0700193 @Override
Cowtowncoder00900312015-04-14 16:02:33 -0700194 public void overrideCurrentName(String name) {
195 /* 14-Apr-2015, tatu: Not sure whether this can be supported, and if so,
196 * what to do with it... Delegation won't work for sure, so let's for
197 * now throw an exception
198 */
199 throw new UnsupportedOperationException("Can not currently override name during filtering read");
200 }
Tatu Salorantadfd69092015-04-09 22:29:34 -0700201
202 /*
203 /**********************************************************
204 /* Public API, traversal
205 /**********************************************************
206 */
207
Cowtowncoder00900312015-04-14 16:02:33 -0700208 @Override
209 public JsonToken nextToken() throws IOException
210 {
Cowtowncoder9a797fb2015-04-14 16:12:12 -0700211 // Anything buffered?
Cowtowncoder37d0d6e2015-04-14 19:48:10 -0700212 TokenFilterContext ctxt = _exposedContext;
213 if (ctxt != null) {
214 while (true) {
215 JsonToken t = _exposedContext.nextTokenToRead(_currToken);
216 if (t != null) {
217 _currToken = t;
218 return t;
219 }
220 // all done with buffered stuff?
221 if (ctxt == _headContext) {
222 _exposedContext = null;
223 break;
224 }
225 // If not, traverse down the context chain
226 ctxt = _exposedContext.findChildOf(_exposedContext);
227 _exposedContext = ctxt;
228 if (ctxt == null) { // should never occur
229 throw _constructError("Unexpected problem: chain of filtered context broken");
230 }
231 }
232 }
233
234 // If not, need to
235 JsonToken t = delegate.nextToken();
236 if (t == null) {
Cowtowncoder9a797fb2015-04-14 16:12:12 -0700237
238 }
Cowtowncoder00900312015-04-14 16:02:33 -0700239 }
Tatu Salorantadfd69092015-04-09 22:29:34 -0700240
241 @Override
242 public JsonToken nextValue() throws IOException {
243 // Re-implemented same as ParserMinimalBase:
244 JsonToken t = nextToken();
245 if (t == JsonToken.FIELD_NAME) {
246 t = nextToken();
247 }
248 return t;
249 }
250
251 /**
252 * Need to override, re-implement similar to how method defined in
253 * {@link com.fasterxml.jackson.core.base.ParserMinimalBase}, to keep
254 * state correct here.
255 */
256 @Override
257 public JsonParser skipChildren() throws IOException
258 {
Cowtowncoder00900312015-04-14 16:02:33 -0700259 if ((_currToken != JsonToken.START_OBJECT)
260 && (_currToken != JsonToken.START_ARRAY)) {
Tatu Salorantadfd69092015-04-09 22:29:34 -0700261 return this;
262 }
263 int open = 1;
264
265 // Since proper matching of start/end markers is handled
266 // by nextToken(), we'll just count nesting levels here
267 while (true) {
268 JsonToken t = nextToken();
269 if (t == null) { // not ideal but for now, just return
270 return this;
271 }
272 if (t.isStructStart()) {
273 ++open;
274 } else if (t.isStructEnd()) {
275 if (--open == 0) {
276 return this;
277 }
278 }
279 }
280 }
281
282 /*
283 /**********************************************************
284 /* Public API, access to token information, text
285 /**********************************************************
286 */
287
288 @Override public String getText() throws IOException { return delegate.getText(); }
289 @Override public boolean hasTextCharacters() { return delegate.hasTextCharacters(); }
290 @Override public char[] getTextCharacters() throws IOException { return delegate.getTextCharacters(); }
291 @Override public int getTextLength() throws IOException { return delegate.getTextLength(); }
292 @Override public int getTextOffset() throws IOException { return delegate.getTextOffset(); }
293
294 /*
295 /**********************************************************
296 /* Public API, access to token information, numeric
297 /**********************************************************
298 */
299
300 @Override
301 public BigInteger getBigIntegerValue() throws IOException { return delegate.getBigIntegerValue(); }
302
303 @Override
304 public boolean getBooleanValue() throws IOException { return delegate.getBooleanValue(); }
305
306 @Override
307 public byte getByteValue() throws IOException { return delegate.getByteValue(); }
308
309 @Override
310 public short getShortValue() throws IOException { return delegate.getShortValue(); }
311
312 @Override
313 public BigDecimal getDecimalValue() throws IOException { return delegate.getDecimalValue(); }
314
315 @Override
316 public double getDoubleValue() throws IOException { return delegate.getDoubleValue(); }
317
318 @Override
319 public float getFloatValue() throws IOException { return delegate.getFloatValue(); }
320
321 @Override
322 public int getIntValue() throws IOException { return delegate.getIntValue(); }
323
324 @Override
325 public long getLongValue() throws IOException { return delegate.getLongValue(); }
326
327 @Override
328 public NumberType getNumberType() throws IOException { return delegate.getNumberType(); }
329
330 @Override
331 public Number getNumberValue() throws IOException { return delegate.getNumberValue(); }
332
333 /*
334 /**********************************************************
335 /* Public API, access to token information, coercion/conversion
336 /**********************************************************
337 */
338
339 @Override public int getValueAsInt() throws IOException { return delegate.getValueAsInt(); }
340 @Override public int getValueAsInt(int defaultValue) throws IOException { return delegate.getValueAsInt(defaultValue); }
341 @Override public long getValueAsLong() throws IOException { return delegate.getValueAsLong(); }
342 @Override public long getValueAsLong(long defaultValue) throws IOException { return delegate.getValueAsLong(defaultValue); }
343 @Override public double getValueAsDouble() throws IOException { return delegate.getValueAsDouble(); }
344 @Override public double getValueAsDouble(double defaultValue) throws IOException { return delegate.getValueAsDouble(defaultValue); }
345 @Override public boolean getValueAsBoolean() throws IOException { return delegate.getValueAsBoolean(); }
346 @Override public boolean getValueAsBoolean(boolean defaultValue) throws IOException { return delegate.getValueAsBoolean(defaultValue); }
347 @Override public String getValueAsString() throws IOException { return delegate.getValueAsString(); }
348 @Override public String getValueAsString(String defaultValue) throws IOException { return delegate.getValueAsString(defaultValue); }
349
350 /*
351 /**********************************************************
352 /* Public API, access to token values, other
353 /**********************************************************
354 */
355
356 @Override public Object getEmbeddedObject() throws IOException { return delegate.getEmbeddedObject(); }
357 @Override public byte[] getBinaryValue(Base64Variant b64variant) throws IOException { return delegate.getBinaryValue(b64variant); }
358 @Override public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IOException { return delegate.readBinaryValue(b64variant, out); }
359 @Override public JsonLocation getTokenLocation() { return delegate.getTokenLocation(); }
Cowtowncoder00900312015-04-14 16:02:33 -0700360
361 /*
362 /**********************************************************
363 /* Internal helper methods
364 /**********************************************************
365 */
366
367 protected JsonStreamContext _filterContext() {
368 if (_exposedContext != null) {
369 return _exposedContext;
370 }
371 return _headContext;
372 }
373
Tatu Salorantadfd69092015-04-09 22:29:34 -0700374}