blob: 4459e7faa4e83dd0f2e92f8f39db5ab668c10f8d [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
Tatu Salorantab46e0372015-04-14 21:43:03 -070011import static com.fasterxml.jackson.core.JsonTokenId.*;
12
Tatu Salorantadfd69092015-04-09 22:29:34 -070013/**
14 * Specialized {@link JsonParserDelegate} that allows use of
15 * {@link TokenFilter} for outputting a subset of content that
16 * is visible to caller
17 *
18 * @since 2.6
19 */
20public class FilteringParserDelegate extends JsonParserDelegate
21{
22 /*
23 /**********************************************************
24 /* Configuration
25 /**********************************************************
26 */
27
28 /**
29 * Object consulted to determine whether to write parts of content generator
30 * is asked to write or not.
31 */
32 protected TokenFilter rootFilter;
33
34 /**
35 * Flag that determines whether filtering will continue after the first
36 * match is indicated or not: if `false`, output is based on just the first
37 * full match (returning {@link TokenFilter#INCLUDE_ALL}) and no more
38 * checks are made; if `true` then filtering will be applied as necessary
39 * until end of content.
40 */
41 protected boolean _allowMultipleMatches;
42
43 /**
44 * Flag that determines whether path leading up to included content should
45 * also be automatically included or not. If `false`, no path inclusion is
46 * done and only explicitly included entries are output; if `true` then
47 * path from main level down to match is also included as necessary.
48 */
49 protected boolean _includePath;
Tatu Salorantacfe32d02015-04-21 23:17:22 -070050
51 /* NOTE: this feature is included in the first version (2.6), but
52 * there is no public API to enable it, yet, since there isn't an
53 * actual use case. But it seemed possible need could arise, which
54 * is feature has not yet been removed. If no use is found within
55 * first version or two, just remove.
56 *
57 * Marked as deprecated since its status is uncertain.
58 */
59 @Deprecated
Faisal Hameedb18e3882015-12-09 00:34:01 +050060 protected boolean _includeImmediateParent;
Tatu Salorantadfd69092015-04-09 22:29:34 -070061
62 /*
63 /**********************************************************
64 /* State
65 /**********************************************************
66 */
67
68 /**
69 * Last token retrieved via {@link #nextToken}, if any.
70 * Null before the first call to <code>nextToken()</code>,
71 * as well as if token has been explicitly cleared
72 */
73 protected JsonToken _currToken;
74
75 /**
76 * Last cleared token, if any: that is, value that was in
77 * effect when {@link #clearCurrentToken} was called.
78 */
79 protected JsonToken _lastClearedToken;
80
81 /**
Cowtowncoder00900312015-04-14 16:02:33 -070082 * During traversal this is the actual "open" parse tree, which sometimes
83 * is the same as {@link #_exposedContext}, and at other times is ahead
84 * of it. Note that this context is never null.
Tatu Salorantadfd69092015-04-09 22:29:34 -070085 */
Cowtowncoder00900312015-04-14 16:02:33 -070086 protected TokenFilterContext _headContext;
Tatu Salorantadfd69092015-04-09 22:29:34 -070087
88 /**
Cowtowncoder00900312015-04-14 16:02:33 -070089 * In cases where {@link #_headContext} is "ahead" of context exposed to
90 * caller, this context points to what is currently exposed to caller.
91 * When the two are in sync, this context reference will be <code>null</code>.
92 */
93 protected TokenFilterContext _exposedContext;
Cowtowncoder9a797fb2015-04-14 16:12:12 -070094
Cowtowncoder00900312015-04-14 16:02:33 -070095 /**
Tatu Salorantadfd69092015-04-09 22:29:34 -070096 * State that applies to the item within container, used where applicable.
97 * Specifically used to pass inclusion state between property name and
98 * property, and also used for array elements.
99 */
100 protected TokenFilter _itemFilter;
101
102 /**
103 * Number of tokens for which {@link TokenFilter#INCLUDE_ALL}
Tatu Saloranta5a5d1192015-04-13 23:05:07 -0700104 * has been returned.
Tatu Salorantadfd69092015-04-09 22:29:34 -0700105 */
106 protected int _matchCount;
107
108 /*
109 /**********************************************************
110 /* Construction, initialization
111 /**********************************************************
112 */
113
114 public FilteringParserDelegate(JsonParser p, TokenFilter f,
115 boolean includePath, boolean allowMultipleMatches)
116 {
117 super(p);
118 rootFilter = f;
119 // and this is the currently active filter for root values
120 _itemFilter = f;
Cowtowncoder00900312015-04-14 16:02:33 -0700121 _headContext = TokenFilterContext.createRootContext(f);
Tatu Salorantadfd69092015-04-09 22:29:34 -0700122 _includePath = includePath;
123 _allowMultipleMatches = allowMultipleMatches;
124 }
125
126 /*
127 /**********************************************************
128 /* Extended API
129 /**********************************************************
130 */
131
132 public TokenFilter getFilter() { return rootFilter; }
133
134 /**
135 * Accessor for finding number of matches, where specific token and sub-tree
136 * starting (if structured type) are passed.
137 */
138 public int getMatchCount() {
139 return _matchCount;
140 }
141
142 /*
143 /**********************************************************
144 /* Public API, token accessors
145 /**********************************************************
146 */
147
148 @Override public JsonToken getCurrentToken() { return _currToken; }
Tatu Salorantae052c312016-07-01 21:11:25 -0700149 @Override public JsonToken currentToken() { return _currToken; }
Tatu Salorantadfd69092015-04-09 22:29:34 -0700150
151 @Override public final int getCurrentTokenId() {
152 final JsonToken t = _currToken;
153 return (t == null) ? JsonTokenId.ID_NO_TOKEN : t.id();
154 }
Tatu Salorantae052c312016-07-01 21:11:25 -0700155 @Override public final int currentTokenId() {
156 final JsonToken t = _currToken;
157 return (t == null) ? JsonTokenId.ID_NO_TOKEN : t.id();
158 }
Tatu Salorantadfd69092015-04-09 22:29:34 -0700159
160 @Override public boolean hasCurrentToken() { return _currToken != null; }
161 @Override public boolean hasTokenId(int id) {
162 final JsonToken t = _currToken;
163 if (t == null) {
164 return (JsonTokenId.ID_NO_TOKEN == id);
165 }
166 return t.id() == id;
167 }
168
169 @Override public final boolean hasToken(JsonToken t) {
170 return (_currToken == t);
171 }
172
173 @Override public boolean isExpectedStartArrayToken() { return _currToken == JsonToken.START_ARRAY; }
174 @Override public boolean isExpectedStartObjectToken() { return _currToken == JsonToken.START_OBJECT; }
175
176 @Override public JsonLocation getCurrentLocation() { return delegate.getCurrentLocation(); }
Tatu Salorantadfd69092015-04-09 22:29:34 -0700177
Cowtowncoder00900312015-04-14 16:02:33 -0700178 @Override
179 public JsonStreamContext getParsingContext() {
180 return _filterContext();
181 }
Tatu Salorantadfd69092015-04-09 22:29:34 -0700182
Cowtowncoder00900312015-04-14 16:02:33 -0700183 // !!! TODO: Verify it works as expected: copied from standard JSON parser impl
184 @Override
185 public String getCurrentName() throws IOException {
186 JsonStreamContext ctxt = _filterContext();
187 if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
188 JsonStreamContext parent = ctxt.getParent();
189 return (parent == null) ? null : parent.getCurrentName();
190 }
191 return ctxt.getCurrentName();
192 }
193
Tatu Salorantadfd69092015-04-09 22:29:34 -0700194 /*
195 /**********************************************************
196 /* Public API, token state overrides
197 /**********************************************************
198 */
199
200 @Override
201 public void clearCurrentToken() {
202 if (_currToken != null) {
203 _lastClearedToken = _currToken;
204 _currToken = null;
205 }
206 }
207
208 @Override
209 public JsonToken getLastClearedToken() { return _lastClearedToken; }
210
Tatu Salorantadfd69092015-04-09 22:29:34 -0700211 @Override
Cowtowncoder00900312015-04-14 16:02:33 -0700212 public void overrideCurrentName(String name) {
213 /* 14-Apr-2015, tatu: Not sure whether this can be supported, and if so,
214 * what to do with it... Delegation won't work for sure, so let's for
215 * now throw an exception
216 */
217 throw new UnsupportedOperationException("Can not currently override name during filtering read");
218 }
Tatu Salorantadfd69092015-04-09 22:29:34 -0700219
220 /*
221 /**********************************************************
222 /* Public API, traversal
223 /**********************************************************
224 */
225
Cowtowncoder00900312015-04-14 16:02:33 -0700226 @Override
227 public JsonToken nextToken() throws IOException
228 {
Tatu Saloranta96faf2e2017-05-23 16:08:31 -0700229 // 23-May-2017, tatu: To be honest, code here is rather hairy and I don't like all
230 // conditionals; and it seems odd to return `null` but NOT considering input
231 // as closed... would love a rewrite to simplify/clear up logic here.
232
233 // Check for _allowMultipleMatches - false and at least there is one token - which is _currToken
234 // check for no buffered context _exposedContext - null
235 // If all the conditions matches then check for scalar / non-scalar property
Rafal Foltynski1415e9d2017-06-03 02:43:18 +0200236
Tatu Saloranta96faf2e2017-05-23 16:08:31 -0700237 if (!_allowMultipleMatches && (_currToken != null) && (_exposedContext == null)) {
Rafal Foltynski1415e9d2017-06-03 02:43:18 +0200238 // if scalar, and scalar not present in obj/array and !includePath and INCLUDE_ALL
Tatu Saloranta4ed6f922017-05-29 19:33:04 -0700239 // matched once, return null
240 if (_currToken.isScalarValue() && !_headContext.isStartHandled() && !_includePath
241 && (_itemFilter == TokenFilter.INCLUDE_ALL)) {
242 return (_currToken = null);
Tatu Saloranta96faf2e2017-05-23 16:08:31 -0700243 }
244 }
Cowtowncoder9a797fb2015-04-14 16:12:12 -0700245 // Anything buffered?
Cowtowncoder37d0d6e2015-04-14 19:48:10 -0700246 TokenFilterContext ctxt = _exposedContext;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700247
Cowtowncoder37d0d6e2015-04-14 19:48:10 -0700248 if (ctxt != null) {
249 while (true) {
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700250 JsonToken t = ctxt.nextTokenToRead();
Cowtowncoder37d0d6e2015-04-14 19:48:10 -0700251 if (t != null) {
252 _currToken = t;
253 return t;
254 }
255 // all done with buffered stuff?
256 if (ctxt == _headContext) {
257 _exposedContext = null;
Cowtowncoderac4b4492015-04-23 15:36:50 -0700258 if (ctxt.inArray()) {
259 t = delegate.getCurrentToken();
260// Is this guaranteed to work without further checks?
261// if (t != JsonToken.START_ARRAY) {
262 _currToken = t;
263 return t;
264 }
265
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700266 // Almost! Most likely still have the current token;
267 // with the sole exception of
Cowtowncoderdf521572015-04-16 15:58:41 -0700268 /*
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700269 t = delegate.getCurrentToken();
270 if (t != JsonToken.FIELD_NAME) {
271 _currToken = t;
272 return t;
273 }
Cowtowncoderdf521572015-04-16 15:58:41 -0700274 */
Cowtowncoder37d0d6e2015-04-14 19:48:10 -0700275 break;
276 }
277 // If not, traverse down the context chain
Cowtowncoderdf521572015-04-16 15:58:41 -0700278 ctxt = _headContext.findChildOf(ctxt);
Cowtowncoder37d0d6e2015-04-14 19:48:10 -0700279 _exposedContext = ctxt;
280 if (ctxt == null) { // should never occur
281 throw _constructError("Unexpected problem: chain of filtered context broken");
282 }
283 }
284 }
285
Tatu Salorantab46e0372015-04-14 21:43:03 -0700286 // If not, need to read more. If we got any:
Cowtowncoder37d0d6e2015-04-14 19:48:10 -0700287 JsonToken t = delegate.nextToken();
288 if (t == null) {
Tatu Salorantab46e0372015-04-14 21:43:03 -0700289 // no strict need to close, since we have no state here
Tatu Salorantae962fb92016-07-21 14:14:26 -0700290 _currToken = t;
291 return t;
Cowtowncoder9a797fb2015-04-14 16:12:12 -0700292 }
Tatu Salorantace077d42015-04-14 20:48:23 -0700293
Tatu Salorantab46e0372015-04-14 21:43:03 -0700294 // otherwise... to include or not?
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700295 TokenFilter f;
296
297 switch (t.id()) {
Tatu Salorantab46e0372015-04-14 21:43:03 -0700298 case ID_START_ARRAY:
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700299 f = _itemFilter;
300 if (f == TokenFilter.INCLUDE_ALL) {
301 _headContext = _headContext.createChildArrayContext(f, true);
302 return (_currToken = t);
303 }
304 if (f == null) { // does this occur?
Tatu Salorantab46e0372015-04-14 21:43:03 -0700305 delegate.skipChildren();
306 break;
307 }
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700308 // Otherwise still iffy, need to check
309 f = _headContext.checkValue(f);
310 if (f == null) {
311 delegate.skipChildren();
312 break;
313 }
314 if (f != TokenFilter.INCLUDE_ALL) {
315 f = f.filterStartArray();
316 }
317 _itemFilter = f;
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700318 if (f == TokenFilter.INCLUDE_ALL) {
Cowtowncoder654acf02015-04-16 15:30:43 -0700319 _headContext = _headContext.createChildArrayContext(f, true);
Tatu Salorantab46e0372015-04-14 21:43:03 -0700320 return (_currToken = t);
321 }
Cowtowncoder654acf02015-04-16 15:30:43 -0700322 _headContext = _headContext.createChildArrayContext(f, false);
323
324 // Also: only need buffering if parent path to be included
325 if (_includePath) {
Cowtowncoderb808f262015-04-23 15:47:39 -0700326 t = _nextTokenWithBuffering(_headContext);
327 if (t != null) {
328 _currToken = t;
329 return t;
330 }
Cowtowncoder654acf02015-04-16 15:30:43 -0700331 }
332 break;
Tatu Salorantab46e0372015-04-14 21:43:03 -0700333
334 case ID_START_OBJECT:
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700335 f = _itemFilter;
336 if (f == TokenFilter.INCLUDE_ALL) {
337 _headContext = _headContext.createChildObjectContext(f, true);
338 return (_currToken = t);
339 }
340 if (f == null) { // does this occur?
Tatu Salorantab46e0372015-04-14 21:43:03 -0700341 delegate.skipChildren();
342 break;
343 }
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700344 // Otherwise still iffy, need to check
345 f = _headContext.checkValue(f);
346 if (f == null) {
347 delegate.skipChildren();
348 break;
349 }
350 if (f != TokenFilter.INCLUDE_ALL) {
351 f = f.filterStartObject();
352 }
353 _itemFilter = f;
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700354 if (f == TokenFilter.INCLUDE_ALL) {
Cowtowncoder654acf02015-04-16 15:30:43 -0700355 _headContext = _headContext.createChildObjectContext(f, true);
Tatu Salorantab46e0372015-04-14 21:43:03 -0700356 return (_currToken = t);
357 }
Cowtowncoder654acf02015-04-16 15:30:43 -0700358 _headContext = _headContext.createChildObjectContext(f, false);
359 // Also: only need buffering if parent path to be included
360 if (_includePath) {
Cowtowncoderb808f262015-04-23 15:47:39 -0700361 t = _nextTokenWithBuffering(_headContext);
362 if (t != null) {
363 _currToken = t;
364 return t;
365 }
Cowtowncoder654acf02015-04-16 15:30:43 -0700366 }
367 // note: inclusion of surrounding Object handled separately via
368 // FIELD_NAME
369 break;
Tatu Salorantab46e0372015-04-14 21:43:03 -0700370
371 case ID_END_ARRAY:
372 case ID_END_OBJECT:
373 {
374 boolean returnEnd = _headContext.isStartHandled();
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700375 f = _headContext.getFilter();
Tatu Salorantab46e0372015-04-14 21:43:03 -0700376 if ((f != null) && (f != TokenFilter.INCLUDE_ALL)) {
377 f.filterFinishArray();
378 }
379 _headContext = _headContext.getParent();
380 _itemFilter = _headContext.getFilter();
381 if (returnEnd) {
382 return (_currToken = t);
383 }
384 }
385 break;
386
387 case ID_FIELD_NAME:
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700388 {
389 final String name = delegate.getCurrentName();
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700390 // note: this will also set 'needToHandleName'
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700391 f = _headContext.setFieldName(name);
392 if (f == TokenFilter.INCLUDE_ALL) {
393 _itemFilter = f;
Tatu Salorantacfe32d02015-04-21 23:17:22 -0700394 if (!_includePath) {
395 // Minor twist here: if parent NOT included, may need to induce output of
396 // surrounding START_OBJECT/END_OBJECT
397 if (_includeImmediateParent && !_headContext.isStartHandled()) {
398 t = _headContext.nextTokenToRead(); // returns START_OBJECT but also marks it handled
399 _exposedContext = _headContext;
400 }
Cowtowncoder654acf02015-04-16 15:30:43 -0700401 }
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700402 return (_currToken = t);
403 }
Cowtowncoder654acf02015-04-16 15:30:43 -0700404 if (f == null) {
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700405 delegate.nextToken();
406 delegate.skipChildren();
407 break;
408 }
409 f = f.includeProperty(name);
Cowtowncoder654acf02015-04-16 15:30:43 -0700410 if (f == null) {
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700411 delegate.nextToken();
412 delegate.skipChildren();
413 break;
414 }
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700415 _itemFilter = f;
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700416 if (f == TokenFilter.INCLUDE_ALL) {
Rafal Foltynski6feec202017-06-04 05:32:03 +0200417 if (_verifyAllowedMatches() && _includePath) {
Tatu Salorantacfe32d02015-04-21 23:17:22 -0700418 return (_currToken = t);
419 }
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700420 }
Cowtowncoder654acf02015-04-16 15:30:43 -0700421 if (_includePath) {
Cowtowncoderb808f262015-04-23 15:47:39 -0700422 t = _nextTokenWithBuffering(_headContext);
423 if (t != null) {
424 _currToken = t;
425 return t;
426 }
Cowtowncoder654acf02015-04-16 15:30:43 -0700427 }
428 break;
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700429 }
Tatu Salorantab46e0372015-04-14 21:43:03 -0700430
431 default: // scalar value
Cowtowncoder32224282015-04-16 16:39:22 -0700432 f = _itemFilter;
433 if (f == TokenFilter.INCLUDE_ALL) {
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700434 return (_currToken = t);
435 }
Cowtowncoder32224282015-04-16 16:39:22 -0700436 if (f != null) {
437 f = _headContext.checkValue(f);
438 if ((f == TokenFilter.INCLUDE_ALL)
439 || ((f != null) && f.includeValue(delegate))) {
Rafal Foltynski6feec202017-06-04 05:32:03 +0200440 if (_verifyAllowedMatches()) {
441 return (_currToken = t);
442 }
Cowtowncoder32224282015-04-16 16:39:22 -0700443 }
444 }
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700445 // Otherwise not included (leaves must be explicitly included)
446 break;
Tatu Salorantab46e0372015-04-14 21:43:03 -0700447 }
448
449 // We get here if token was not yet found; offlined handling
450 return _nextToken2();
451 }
452
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700453 /**
454 * Offlined handling for cases where there was no buffered token to
455 * return, and the token read next could not be returned as-is,
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700456 * at least not yet, but where we have not yet established that
457 * buffering is needed.
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700458 */
Tatu Salorantab46e0372015-04-14 21:43:03 -0700459 protected final JsonToken _nextToken2() throws IOException
460 {
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700461 main_loop:
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700462 while (true) {
463 JsonToken t = delegate.nextToken();
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700464 if (t == null) { // is this even legal?
Tatu Salorantae962fb92016-07-21 14:14:26 -0700465 _currToken = t;
466 return t;
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700467 }
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700468 TokenFilter f;
469
470 switch (t.id()) {
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700471 case ID_START_ARRAY:
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700472 f = _itemFilter;
473 if (f == TokenFilter.INCLUDE_ALL) {
474 _headContext = _headContext.createChildArrayContext(f, true);
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700475 return (_currToken = t);
476 }
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700477 if (f == null) { // does this occur?
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700478 delegate.skipChildren();
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700479 continue main_loop;
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700480 }
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700481 // Otherwise still iffy, need to check
482 f = _headContext.checkValue(f);
483 if (f == null) {
484 delegate.skipChildren();
485 continue main_loop;
486 }
487 if (f != TokenFilter.INCLUDE_ALL) {
488 f = f.filterStartArray();
489 }
490 _itemFilter = f;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700491 if (f == TokenFilter.INCLUDE_ALL) {
Cowtowncoder654acf02015-04-16 15:30:43 -0700492 _headContext = _headContext.createChildArrayContext(f, true);
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700493 return (_currToken = t);
494 }
Cowtowncoder654acf02015-04-16 15:30:43 -0700495 _headContext = _headContext.createChildArrayContext(f, false);
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700496 // but if we didn't figure it out yet, need to buffer possible events
Cowtowncoder654acf02015-04-16 15:30:43 -0700497 if (_includePath) {
Cowtowncoderb808f262015-04-23 15:47:39 -0700498 t = _nextTokenWithBuffering(_headContext);
499 if (t != null) {
500 _currToken = t;
501 return t;
502 }
Cowtowncoder654acf02015-04-16 15:30:43 -0700503 }
504 continue main_loop;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700505
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700506 case ID_START_OBJECT:
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700507 f = _itemFilter;
508 if (f == TokenFilter.INCLUDE_ALL) {
509 _headContext = _headContext.createChildObjectContext(f, true);
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700510 return (_currToken = t);
511 }
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700512 if (f == null) { // does this occur?
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700513 delegate.skipChildren();
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700514 continue main_loop;
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700515 }
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700516 // Otherwise still iffy, need to check
517 f = _headContext.checkValue(f);
518 if (f == null) {
519 delegate.skipChildren();
520 continue main_loop;
521 }
522 if (f != TokenFilter.INCLUDE_ALL) {
523 f = f.filterStartObject();
524 }
525 _itemFilter = f;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700526 if (f == TokenFilter.INCLUDE_ALL) {
Cowtowncoder654acf02015-04-16 15:30:43 -0700527 _headContext = _headContext.createChildObjectContext(f, true);
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700528 return (_currToken = t);
529 }
Cowtowncoder654acf02015-04-16 15:30:43 -0700530 _headContext = _headContext.createChildObjectContext(f, false);
531 if (_includePath) {
Cowtowncoderb808f262015-04-23 15:47:39 -0700532 t = _nextTokenWithBuffering(_headContext);
533 if (t != null) {
534 _currToken = t;
535 return t;
536 }
Cowtowncoder654acf02015-04-16 15:30:43 -0700537 }
538 continue main_loop;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700539
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700540 case ID_END_ARRAY:
541 case ID_END_OBJECT:
542 {
543 boolean returnEnd = _headContext.isStartHandled();
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700544 f = _headContext.getFilter();
545 if ((f != null) && (f != TokenFilter.INCLUDE_ALL)) {
546 f.filterFinishArray();
547 }
548 _headContext = _headContext.getParent();
549 _itemFilter = _headContext.getFilter();
550 if (returnEnd) {
551 return (_currToken = t);
552 }
553 }
554 continue main_loop;
555
556 case ID_FIELD_NAME:
557 {
558 final String name = delegate.getCurrentName();
559 f = _headContext.setFieldName(name);
560 if (f == TokenFilter.INCLUDE_ALL) {
561 _itemFilter = f;
562 return (_currToken = t);
563 }
564 if (f == null) { // filter out the value
565 delegate.nextToken();
566 delegate.skipChildren();
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700567 continue main_loop;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700568 }
569 f = f.includeProperty(name);
570 if (f == null) { // filter out the value
571 delegate.nextToken();
572 delegate.skipChildren();
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700573 continue main_loop;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700574 }
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700575 _itemFilter = f;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700576 if (f == TokenFilter.INCLUDE_ALL) {
Rafal Foltynski6feec202017-06-04 05:32:03 +0200577 if (_verifyAllowedMatches() && _includePath) {
Tatu Salorantacfe32d02015-04-21 23:17:22 -0700578 return (_currToken = t);
579 }
580// if (_includeImmediateParent) { ...
581 continue main_loop;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700582 }
Cowtowncoder654acf02015-04-16 15:30:43 -0700583 if (_includePath) {
Cowtowncoderb808f262015-04-23 15:47:39 -0700584 t = _nextTokenWithBuffering(_headContext);
585 if (t != null) {
586 _currToken = t;
587 return t;
588 }
Cowtowncoder654acf02015-04-16 15:30:43 -0700589 }
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700590 }
Cowtowncoder654acf02015-04-16 15:30:43 -0700591 continue main_loop;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700592
593 default: // scalar value
Cowtowncoderc17c3562015-04-16 15:47:28 -0700594 f = _itemFilter;
Cowtowncoder32224282015-04-16 16:39:22 -0700595 if (f == TokenFilter.INCLUDE_ALL) {
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700596 return (_currToken = t);
597 }
Cowtowncoder32224282015-04-16 16:39:22 -0700598 if (f != null) {
599 f = _headContext.checkValue(f);
600 if ((f == TokenFilter.INCLUDE_ALL)
601 || ((f != null) && f.includeValue(delegate))) {
Rafal Foltynski6feec202017-06-04 05:32:03 +0200602 if (_verifyAllowedMatches()) {
603 return (_currToken = t);
604 }
Cowtowncoder32224282015-04-16 16:39:22 -0700605 }
606 }
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700607 // Otherwise not included (leaves must be explicitly included)
Cowtowncoder32224282015-04-16 16:39:22 -0700608 break;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700609 }
610 }
611 }
612
613 /**
614 * Method called when a new potentially included context is found.
615 */
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700616 protected final JsonToken _nextTokenWithBuffering(final TokenFilterContext buffRoot)
617 throws IOException
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700618 {
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700619 main_loop:
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700620 while (true) {
621 JsonToken t = delegate.nextToken();
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700622 if (t == null) { // is this even legal?
Cowtowncoderb808f262015-04-23 15:47:39 -0700623 return t;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700624 }
625 TokenFilter f;
626
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700627 // One simplification here: we know for a fact that the item filter is
628 // neither null nor 'include all', for most cases; the only exception
629 // being FIELD_NAME handling
630
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700631 switch (t.id()) {
632 case ID_START_ARRAY:
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700633 f = _headContext.checkValue(_itemFilter);
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700634 if (f == null) {
635 delegate.skipChildren();
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700636 continue main_loop;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700637 }
638 if (f != TokenFilter.INCLUDE_ALL) {
639 f = f.filterStartArray();
640 }
641 _itemFilter = f;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700642 if (f == TokenFilter.INCLUDE_ALL) {
Cowtowncoderac4b4492015-04-23 15:36:50 -0700643 _headContext = _headContext.createChildArrayContext(f, true);
Cowtowncoder7f7d10a2015-04-29 16:45:02 -0700644 return _nextBuffered(buffRoot);
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700645 }
Cowtowncoderac4b4492015-04-23 15:36:50 -0700646 _headContext = _headContext.createChildArrayContext(f, false);
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700647 continue main_loop;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700648
649 case ID_START_OBJECT:
650 f = _itemFilter;
651 if (f == TokenFilter.INCLUDE_ALL) {
652 _headContext = _headContext.createChildObjectContext(f, true);
Cowtowncoderb808f262015-04-23 15:47:39 -0700653 return t;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700654 }
655 if (f == null) { // does this occur?
656 delegate.skipChildren();
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700657 continue main_loop;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700658 }
659 // Otherwise still iffy, need to check
660 f = _headContext.checkValue(f);
661 if (f == null) {
662 delegate.skipChildren();
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700663 continue main_loop;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700664 }
665 if (f != TokenFilter.INCLUDE_ALL) {
666 f = f.filterStartObject();
667 }
668 _itemFilter = f;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700669 if (f == TokenFilter.INCLUDE_ALL) {
Cowtowncoderdf521572015-04-16 15:58:41 -0700670 _headContext = _headContext.createChildObjectContext(f, true);
Cowtowncoder7f7d10a2015-04-29 16:45:02 -0700671 return _nextBuffered(buffRoot);
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700672 }
Cowtowncoderdf521572015-04-16 15:58:41 -0700673 _headContext = _headContext.createChildObjectContext(f, false);
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700674 continue main_loop;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700675
676 case ID_END_ARRAY:
677 case ID_END_OBJECT:
678 {
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700679 // Unlike with other loops, here we know that content was NOT
680 // included (won't get this far otherwise)
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700681 f = _headContext.getFilter();
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700682 if ((f != null) && (f != TokenFilter.INCLUDE_ALL)) {
683 f.filterFinishArray();
684 }
Cowtowncoder7f7d10a2015-04-29 16:45:02 -0700685 boolean gotEnd = (_headContext == buffRoot);
686 boolean returnEnd = gotEnd && _headContext.isStartHandled();
687
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700688 _headContext = _headContext.getParent();
689 _itemFilter = _headContext.getFilter();
Cowtowncoder7f7d10a2015-04-29 16:45:02 -0700690
691 if (returnEnd) {
692 return t;
693 }
694 // Hmmh. Do we need both checks, or should above suffice?
695 if (gotEnd || (_headContext == buffRoot)) {
Cowtowncoderb808f262015-04-23 15:47:39 -0700696 return null;
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700697 }
698 }
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700699 continue main_loop;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700700
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700701 case ID_FIELD_NAME:
702 {
703 final String name = delegate.getCurrentName();
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700704 f = _headContext.setFieldName(name);
705 if (f == TokenFilter.INCLUDE_ALL) {
706 _itemFilter = f;
Cowtowncoder7f7d10a2015-04-29 16:45:02 -0700707 return _nextBuffered(buffRoot);
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700708 }
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700709 if (f == null) { // filter out the value
710 delegate.nextToken();
711 delegate.skipChildren();
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700712 continue main_loop;
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700713 }
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700714 f = f.includeProperty(name);
715 if (f == null) { // filter out the value
716 delegate.nextToken();
717 delegate.skipChildren();
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700718 continue main_loop;
Tatu Salorantab1ac0572015-04-15 22:35:00 -0700719 }
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700720 _itemFilter = f;
Rafal Foltynski6feec202017-06-04 05:32:03 +0200721 if (f == TokenFilter.INCLUDE_ALL && _verifyAllowedMatches()) {
Cowtowncoder7f7d10a2015-04-29 16:45:02 -0700722 return _nextBuffered(buffRoot);
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700723 }
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700724 }
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700725 continue main_loop;
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700726
727 default: // scalar value
Cowtowncoder32224282015-04-16 16:39:22 -0700728 f = _itemFilter;
729 if (f == TokenFilter.INCLUDE_ALL) {
Cowtowncoder7f7d10a2015-04-29 16:45:02 -0700730 return _nextBuffered(buffRoot);
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700731 }
Cowtowncoder32224282015-04-16 16:39:22 -0700732 if (f != null) {
733 f = _headContext.checkValue(f);
734 if ((f == TokenFilter.INCLUDE_ALL)
735 || ((f != null) && f.includeValue(delegate))) {
Rafal Foltynski6feec202017-06-04 05:32:03 +0200736 if (_verifyAllowedMatches()) {
737 return _nextBuffered(buffRoot);
738 }
Cowtowncoder32224282015-04-16 16:39:22 -0700739 }
740 }
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700741 // Otherwise not included (leaves must be explicitly included)
Cowtowncoder32224282015-04-16 16:39:22 -0700742 continue main_loop;
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700743 }
744 }
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700745 }
746
Cowtowncoder7f7d10a2015-04-29 16:45:02 -0700747 private JsonToken _nextBuffered(TokenFilterContext buffRoot) throws IOException
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700748 {
Cowtowncoder7f7d10a2015-04-29 16:45:02 -0700749 _exposedContext = buffRoot;
750 TokenFilterContext ctxt = buffRoot;
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700751 JsonToken t = ctxt.nextTokenToRead();
752 if (t != null) {
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700753 return t;
754 }
755 while (true) {
756 // all done with buffered stuff?
757 if (ctxt == _headContext) {
758 throw _constructError("Internal error: failed to locate expected buffered tokens");
759 /*
760 _exposedContext = null;
761 break;
762 */
763 }
764 // If not, traverse down the context chain
765 ctxt = _exposedContext.findChildOf(ctxt);
766 _exposedContext = ctxt;
767 if (ctxt == null) { // should never occur
768 throw _constructError("Unexpected problem: chain of filtered context broken");
769 }
770 t = _exposedContext.nextTokenToRead();
771 if (t != null) {
Tatu Salorantaf6d583d2015-04-15 23:06:23 -0700772 return t;
773 }
774 }
775 }
Rafal Foltynski6feec202017-06-04 05:32:03 +0200776
777 private final boolean _verifyAllowedMatches() throws IOException {
778 if (_matchCount == 0 || _allowMultipleMatches) {
779 ++_matchCount;
780 return true;
781 }
782 return false;
783 }
784
Tatu Salorantadfd69092015-04-09 22:29:34 -0700785 @Override
786 public JsonToken nextValue() throws IOException {
787 // Re-implemented same as ParserMinimalBase:
788 JsonToken t = nextToken();
789 if (t == JsonToken.FIELD_NAME) {
790 t = nextToken();
791 }
792 return t;
793 }
794
795 /**
796 * Need to override, re-implement similar to how method defined in
797 * {@link com.fasterxml.jackson.core.base.ParserMinimalBase}, to keep
798 * state correct here.
799 */
800 @Override
801 public JsonParser skipChildren() throws IOException
802 {
Cowtowncoder00900312015-04-14 16:02:33 -0700803 if ((_currToken != JsonToken.START_OBJECT)
804 && (_currToken != JsonToken.START_ARRAY)) {
Tatu Salorantadfd69092015-04-09 22:29:34 -0700805 return this;
806 }
807 int open = 1;
808
809 // Since proper matching of start/end markers is handled
810 // by nextToken(), we'll just count nesting levels here
811 while (true) {
812 JsonToken t = nextToken();
813 if (t == null) { // not ideal but for now, just return
814 return this;
815 }
816 if (t.isStructStart()) {
817 ++open;
818 } else if (t.isStructEnd()) {
819 if (--open == 0) {
820 return this;
821 }
822 }
823 }
824 }
825
826 /*
827 /**********************************************************
828 /* Public API, access to token information, text
829 /**********************************************************
830 */
831
832 @Override public String getText() throws IOException { return delegate.getText(); }
833 @Override public boolean hasTextCharacters() { return delegate.hasTextCharacters(); }
834 @Override public char[] getTextCharacters() throws IOException { return delegate.getTextCharacters(); }
835 @Override public int getTextLength() throws IOException { return delegate.getTextLength(); }
836 @Override public int getTextOffset() throws IOException { return delegate.getTextOffset(); }
837
838 /*
839 /**********************************************************
840 /* Public API, access to token information, numeric
841 /**********************************************************
842 */
843
844 @Override
845 public BigInteger getBigIntegerValue() throws IOException { return delegate.getBigIntegerValue(); }
846
847 @Override
848 public boolean getBooleanValue() throws IOException { return delegate.getBooleanValue(); }
849
850 @Override
851 public byte getByteValue() throws IOException { return delegate.getByteValue(); }
852
853 @Override
854 public short getShortValue() throws IOException { return delegate.getShortValue(); }
855
856 @Override
857 public BigDecimal getDecimalValue() throws IOException { return delegate.getDecimalValue(); }
858
859 @Override
860 public double getDoubleValue() throws IOException { return delegate.getDoubleValue(); }
861
862 @Override
863 public float getFloatValue() throws IOException { return delegate.getFloatValue(); }
864
865 @Override
866 public int getIntValue() throws IOException { return delegate.getIntValue(); }
867
868 @Override
869 public long getLongValue() throws IOException { return delegate.getLongValue(); }
870
871 @Override
872 public NumberType getNumberType() throws IOException { return delegate.getNumberType(); }
873
874 @Override
875 public Number getNumberValue() throws IOException { return delegate.getNumberValue(); }
876
877 /*
878 /**********************************************************
879 /* Public API, access to token information, coercion/conversion
880 /**********************************************************
881 */
882
883 @Override public int getValueAsInt() throws IOException { return delegate.getValueAsInt(); }
884 @Override public int getValueAsInt(int defaultValue) throws IOException { return delegate.getValueAsInt(defaultValue); }
885 @Override public long getValueAsLong() throws IOException { return delegate.getValueAsLong(); }
886 @Override public long getValueAsLong(long defaultValue) throws IOException { return delegate.getValueAsLong(defaultValue); }
887 @Override public double getValueAsDouble() throws IOException { return delegate.getValueAsDouble(); }
888 @Override public double getValueAsDouble(double defaultValue) throws IOException { return delegate.getValueAsDouble(defaultValue); }
889 @Override public boolean getValueAsBoolean() throws IOException { return delegate.getValueAsBoolean(); }
890 @Override public boolean getValueAsBoolean(boolean defaultValue) throws IOException { return delegate.getValueAsBoolean(defaultValue); }
891 @Override public String getValueAsString() throws IOException { return delegate.getValueAsString(); }
892 @Override public String getValueAsString(String defaultValue) throws IOException { return delegate.getValueAsString(defaultValue); }
893
894 /*
895 /**********************************************************
896 /* Public API, access to token values, other
897 /**********************************************************
898 */
899
900 @Override public Object getEmbeddedObject() throws IOException { return delegate.getEmbeddedObject(); }
901 @Override public byte[] getBinaryValue(Base64Variant b64variant) throws IOException { return delegate.getBinaryValue(b64variant); }
902 @Override public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IOException { return delegate.readBinaryValue(b64variant, out); }
903 @Override public JsonLocation getTokenLocation() { return delegate.getTokenLocation(); }
Cowtowncoder00900312015-04-14 16:02:33 -0700904
905 /*
906 /**********************************************************
907 /* Internal helper methods
908 /**********************************************************
909 */
910
911 protected JsonStreamContext _filterContext() {
912 if (_exposedContext != null) {
913 return _exposedContext;
914 }
915 return _headContext;
916 }
LokeshNfd327722016-03-20 17:28:25 +0530917
Tatu Salorantadfd69092015-04-09 22:29:34 -0700918}