blob: f4d2533e3ea5ebc5f639d25e0fa383e6c6054189 [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;
50
51 /*
52 /**********************************************************
53 /* State
54 /**********************************************************
55 */
56
57 /**
58 * Last token retrieved via {@link #nextToken}, if any.
59 * Null before the first call to <code>nextToken()</code>,
60 * as well as if token has been explicitly cleared
61 */
62 protected JsonToken _currToken;
63
64 /**
65 * Last cleared token, if any: that is, value that was in
66 * effect when {@link #clearCurrentToken} was called.
67 */
68 protected JsonToken _lastClearedToken;
69
70 /**
Cowtowncoder00900312015-04-14 16:02:33 -070071 * During traversal this is the actual "open" parse tree, which sometimes
72 * is the same as {@link #_exposedContext}, and at other times is ahead
73 * of it. Note that this context is never null.
Tatu Salorantadfd69092015-04-09 22:29:34 -070074 */
Cowtowncoder00900312015-04-14 16:02:33 -070075 protected TokenFilterContext _headContext;
Tatu Salorantadfd69092015-04-09 22:29:34 -070076
77 /**
Cowtowncoder00900312015-04-14 16:02:33 -070078 * In cases where {@link #_headContext} is "ahead" of context exposed to
79 * caller, this context points to what is currently exposed to caller.
80 * When the two are in sync, this context reference will be <code>null</code>.
81 */
82 protected TokenFilterContext _exposedContext;
Cowtowncoder9a797fb2015-04-14 16:12:12 -070083
Cowtowncoder00900312015-04-14 16:02:33 -070084 /**
Tatu Salorantadfd69092015-04-09 22:29:34 -070085 * State that applies to the item within container, used where applicable.
86 * Specifically used to pass inclusion state between property name and
87 * property, and also used for array elements.
88 */
89 protected TokenFilter _itemFilter;
90
91 /**
92 * Number of tokens for which {@link TokenFilter#INCLUDE_ALL}
Tatu Saloranta5a5d1192015-04-13 23:05:07 -070093 * has been returned.
Tatu Salorantadfd69092015-04-09 22:29:34 -070094 */
95 protected int _matchCount;
96
97 /*
98 /**********************************************************
99 /* Construction, initialization
100 /**********************************************************
101 */
102
103 public FilteringParserDelegate(JsonParser p, TokenFilter f,
104 boolean includePath, boolean allowMultipleMatches)
105 {
106 super(p);
107 rootFilter = f;
108 // and this is the currently active filter for root values
109 _itemFilter = f;
Cowtowncoder00900312015-04-14 16:02:33 -0700110 _headContext = TokenFilterContext.createRootContext(f);
Tatu Salorantadfd69092015-04-09 22:29:34 -0700111 _includePath = includePath;
112 _allowMultipleMatches = allowMultipleMatches;
113 }
114
115 /*
116 /**********************************************************
117 /* Extended API
118 /**********************************************************
119 */
120
121 public TokenFilter getFilter() { return rootFilter; }
122
123 /**
124 * Accessor for finding number of matches, where specific token and sub-tree
125 * starting (if structured type) are passed.
126 */
127 public int getMatchCount() {
128 return _matchCount;
129 }
130
131 /*
132 /**********************************************************
133 /* Public API, token accessors
134 /**********************************************************
135 */
136
137 @Override public JsonToken getCurrentToken() { return _currToken; }
138
139 @Override public final int getCurrentTokenId() {
140 final JsonToken t = _currToken;
141 return (t == null) ? JsonTokenId.ID_NO_TOKEN : t.id();
142 }
143
144 @Override public boolean hasCurrentToken() { return _currToken != null; }
145 @Override public boolean hasTokenId(int id) {
146 final JsonToken t = _currToken;
147 if (t == null) {
148 return (JsonTokenId.ID_NO_TOKEN == id);
149 }
150 return t.id() == id;
151 }
152
153 @Override public final boolean hasToken(JsonToken t) {
154 return (_currToken == t);
155 }
156
157 @Override public boolean isExpectedStartArrayToken() { return _currToken == JsonToken.START_ARRAY; }
158 @Override public boolean isExpectedStartObjectToken() { return _currToken == JsonToken.START_OBJECT; }
159
160 @Override public JsonLocation getCurrentLocation() { return delegate.getCurrentLocation(); }
Tatu Salorantadfd69092015-04-09 22:29:34 -0700161
Cowtowncoder00900312015-04-14 16:02:33 -0700162 @Override
163 public JsonStreamContext getParsingContext() {
164 return _filterContext();
165 }
Tatu Salorantadfd69092015-04-09 22:29:34 -0700166
Cowtowncoder00900312015-04-14 16:02:33 -0700167 // !!! TODO: Verify it works as expected: copied from standard JSON parser impl
168 @Override
169 public String getCurrentName() throws IOException {
170 JsonStreamContext ctxt = _filterContext();
171 if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
172 JsonStreamContext parent = ctxt.getParent();
173 return (parent == null) ? null : parent.getCurrentName();
174 }
175 return ctxt.getCurrentName();
176 }
177
Tatu Salorantadfd69092015-04-09 22:29:34 -0700178 /*
179 /**********************************************************
180 /* Public API, token state overrides
181 /**********************************************************
182 */
183
184 @Override
185 public void clearCurrentToken() {
186 if (_currToken != null) {
187 _lastClearedToken = _currToken;
188 _currToken = null;
189 }
190 }
191
192 @Override
193 public JsonToken getLastClearedToken() { return _lastClearedToken; }
194
Tatu Salorantadfd69092015-04-09 22:29:34 -0700195 @Override
Cowtowncoder00900312015-04-14 16:02:33 -0700196 public void overrideCurrentName(String name) {
197 /* 14-Apr-2015, tatu: Not sure whether this can be supported, and if so,
198 * what to do with it... Delegation won't work for sure, so let's for
199 * now throw an exception
200 */
201 throw new UnsupportedOperationException("Can not currently override name during filtering read");
202 }
Tatu Salorantadfd69092015-04-09 22:29:34 -0700203
204 /*
205 /**********************************************************
206 /* Public API, traversal
207 /**********************************************************
208 */
209
Cowtowncoder00900312015-04-14 16:02:33 -0700210 @Override
211 public JsonToken nextToken() throws IOException
212 {
Cowtowncoder9a797fb2015-04-14 16:12:12 -0700213 // Anything buffered?
Cowtowncoder37d0d6e2015-04-14 19:48:10 -0700214 TokenFilterContext ctxt = _exposedContext;
215 if (ctxt != null) {
216 while (true) {
217 JsonToken t = _exposedContext.nextTokenToRead(_currToken);
218 if (t != null) {
219 _currToken = t;
220 return t;
221 }
222 // all done with buffered stuff?
223 if (ctxt == _headContext) {
224 _exposedContext = null;
225 break;
226 }
227 // If not, traverse down the context chain
228 ctxt = _exposedContext.findChildOf(_exposedContext);
229 _exposedContext = ctxt;
230 if (ctxt == null) { // should never occur
231 throw _constructError("Unexpected problem: chain of filtered context broken");
232 }
233 }
234 }
235
Tatu Salorantab46e0372015-04-14 21:43:03 -0700236 // If not, need to read more. If we got any:
Cowtowncoder37d0d6e2015-04-14 19:48:10 -0700237 JsonToken t = delegate.nextToken();
238 if (t == null) {
Tatu Salorantab46e0372015-04-14 21:43:03 -0700239 // no strict need to close, since we have no state here
240 return (_currToken = t);
Cowtowncoder9a797fb2015-04-14 16:12:12 -0700241 }
Tatu Salorantace077d42015-04-14 20:48:23 -0700242
Tatu Salorantab46e0372015-04-14 21:43:03 -0700243 // otherwise... to include or not?
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700244 TokenFilter f;
245
246 switch (t.id()) {
Tatu Salorantab46e0372015-04-14 21:43:03 -0700247 case ID_START_ARRAY:
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700248 f = _itemFilter;
249 if (f == TokenFilter.INCLUDE_ALL) {
250 _headContext = _headContext.createChildArrayContext(f, true);
251 return (_currToken = t);
252 }
253 if (f == null) { // does this occur?
Tatu Salorantab46e0372015-04-14 21:43:03 -0700254 delegate.skipChildren();
255 break;
256 }
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700257 // Otherwise still iffy, need to check
258 f = _headContext.checkValue(f);
259 if (f == null) {
260 delegate.skipChildren();
261 break;
262 }
263 if (f != TokenFilter.INCLUDE_ALL) {
264 f = f.filterStartArray();
265 }
266 _itemFilter = f;
267 _headContext = _headContext.createChildArrayContext(f, true);
268 if (f == TokenFilter.INCLUDE_ALL) {
Tatu Salorantab46e0372015-04-14 21:43:03 -0700269 return (_currToken = t);
270 }
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700271 // but if we didn't figure it out yet, need to buffer possible events
272 return _nextTokenWithBuffering(_headContext);
Tatu Salorantab46e0372015-04-14 21:43:03 -0700273
274 case ID_START_OBJECT:
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700275 f = _itemFilter;
276 if (f == TokenFilter.INCLUDE_ALL) {
277 _headContext = _headContext.createChildObjectContext(f, true);
278 return (_currToken = t);
279 }
280 if (f == null) { // does this occur?
Tatu Salorantab46e0372015-04-14 21:43:03 -0700281 delegate.skipChildren();
282 break;
283 }
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700284 // Otherwise still iffy, need to check
285 f = _headContext.checkValue(f);
286 if (f == null) {
287 delegate.skipChildren();
288 break;
289 }
290 if (f != TokenFilter.INCLUDE_ALL) {
291 f = f.filterStartObject();
292 }
293 _itemFilter = f;
294 _headContext = _headContext.createChildObjectContext(f, true);
295 if (f == TokenFilter.INCLUDE_ALL) {
Tatu Salorantab46e0372015-04-14 21:43:03 -0700296 return (_currToken = t);
297 }
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700298 // but if we didn't figure it out yet, need to buffer possible events
299 return _nextTokenWithBuffering(_headContext);
Tatu Salorantab46e0372015-04-14 21:43:03 -0700300
301 case ID_END_ARRAY:
302 case ID_END_OBJECT:
303 {
304 boolean returnEnd = _headContext.isStartHandled();
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700305 f = _headContext.getFilter();
Tatu Salorantab46e0372015-04-14 21:43:03 -0700306 if ((f != null) && (f != TokenFilter.INCLUDE_ALL)) {
307 f.filterFinishArray();
308 }
309 _headContext = _headContext.getParent();
310 _itemFilter = _headContext.getFilter();
311 if (returnEnd) {
312 return (_currToken = t);
313 }
314 }
315 break;
316
317 case ID_FIELD_NAME:
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700318 {
319 final String name = delegate.getCurrentName();
320 f = _headContext.setFieldName(name);
321 if (f == TokenFilter.INCLUDE_ALL) {
322 _itemFilter = f;
323 return (_currToken = t);
324 }
325 if (f == null) { // filter out the value
326 delegate.nextToken();
327 delegate.skipChildren();
328 break;
329 }
330 f = f.includeProperty(name);
331 if (f == null) { // filter out the value
332 delegate.nextToken();
333 delegate.skipChildren();
334 break;
335 }
336 if (f == TokenFilter.INCLUDE_ALL) {
337 _itemFilter = f;
338 return (_currToken = t);
339 }
340 // !!! TODO: still not decided if to include, so...
341
342 _itemFilter = f;
343 }
344 break;
Tatu Salorantab46e0372015-04-14 21:43:03 -0700345
346 default: // scalar value
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700347 if (_itemFilter == TokenFilter.INCLUDE_ALL) {
348 return (_currToken = t);
349 }
350 // Otherwise not included (leaves must be explicitly included)
351 break;
Tatu Salorantab46e0372015-04-14 21:43:03 -0700352 }
353
354 // We get here if token was not yet found; offlined handling
355 return _nextToken2();
356 }
357
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700358 /**
359 * Offlined handling for cases where there was no buffered token to
360 * return, and the token read next could not be returned as-is,
361 * at least not yet.
362 */
Tatu Salorantab46e0372015-04-14 21:43:03 -0700363 protected final JsonToken _nextToken2() throws IOException
364 {
Tatu Saloranta404aebc2015-04-14 22:47:07 -0700365 while (true) {
366 JsonToken t = delegate.nextToken();
367 if (t == null) { // is this really legal? For the moment, assume it is
368 return (_currToken = t);
369 }
370 switch (_currToken.id()) {
371 case ID_START_ARRAY:
372 if (_itemFilter == TokenFilter.INCLUDE_ALL) {
373 _headContext = _headContext.createChildArrayContext(_itemFilter, true);
374 return (_currToken = t);
375 }
376 if (_itemFilter == null) { // does this occur?
377 delegate.skipChildren();
378 break;
379 }
380 _exposedContext = _headContext = _headContext.createChildArrayContext(_itemFilter, false);
381 break;
382
383 case ID_START_OBJECT:
384 if (_itemFilter == TokenFilter.INCLUDE_ALL) {
385 _headContext = _headContext.createChildObjectContext(_itemFilter, true);
386 return (_currToken = t);
387 }
388 if (_itemFilter == null) { // does this occur?
389 delegate.skipChildren();
390 break;
391 }
392 _exposedContext = _headContext = _headContext.createChildObjectContext(_itemFilter, false);
393 break;
394
395 case ID_END_ARRAY:
396 case ID_END_OBJECT:
397 {
398 boolean returnEnd = _headContext.isStartHandled();
399 TokenFilter f = _headContext.getFilter();
400 if ((f != null) && (f != TokenFilter.INCLUDE_ALL)) {
401 f.filterFinishArray();
402 }
403 _headContext = _headContext.getParent();
404 _itemFilter = _headContext.getFilter();
405 if (returnEnd) {
406 return (_currToken = t);
407 }
408 }
409 break;
410
411 case ID_FIELD_NAME:
412 {
413 final String name = delegate.getCurrentName();
414 TokenFilter f = _headContext.setFieldName(name);
415 if (f == null) { // filter out the value
416 delegate.nextToken();
417 delegate.skipChildren();
418 break;
419 }
420 if (_itemFilter == TokenFilter.INCLUDE_ALL) {
421 _itemFilter = f;
422 return (_currToken = t);
423 }
424 f = f.includeProperty(name);
425 _itemFilter = f;
426 }
427 break;
428
429 default: // scalar value
430 if (_itemFilter == TokenFilter.INCLUDE_ALL) {
431 return (_currToken = t);
432 }
433 // Otherwise not included (leaves must be explicitly included)
434 break;
435 }
436 }
437 }
438
439 /**
440 * Method called when a new potentially included context is found.
441 */
442 protected final JsonToken _nextTokenWithBuffering(TokenFilterContext buffRoot) throws IOException
443 {
444 _exposedContext = _headContext;
Tatu Salorantace077d42015-04-14 20:48:23 -0700445 // !!! TODO
446 return null;
Cowtowncoder00900312015-04-14 16:02:33 -0700447 }
Tatu Salorantab46e0372015-04-14 21:43:03 -0700448
Tatu Salorantadfd69092015-04-09 22:29:34 -0700449 @Override
450 public JsonToken nextValue() throws IOException {
451 // Re-implemented same as ParserMinimalBase:
452 JsonToken t = nextToken();
453 if (t == JsonToken.FIELD_NAME) {
454 t = nextToken();
455 }
456 return t;
457 }
458
459 /**
460 * Need to override, re-implement similar to how method defined in
461 * {@link com.fasterxml.jackson.core.base.ParserMinimalBase}, to keep
462 * state correct here.
463 */
464 @Override
465 public JsonParser skipChildren() throws IOException
466 {
Cowtowncoder00900312015-04-14 16:02:33 -0700467 if ((_currToken != JsonToken.START_OBJECT)
468 && (_currToken != JsonToken.START_ARRAY)) {
Tatu Salorantadfd69092015-04-09 22:29:34 -0700469 return this;
470 }
471 int open = 1;
472
473 // Since proper matching of start/end markers is handled
474 // by nextToken(), we'll just count nesting levels here
475 while (true) {
476 JsonToken t = nextToken();
477 if (t == null) { // not ideal but for now, just return
478 return this;
479 }
480 if (t.isStructStart()) {
481 ++open;
482 } else if (t.isStructEnd()) {
483 if (--open == 0) {
484 return this;
485 }
486 }
487 }
488 }
489
490 /*
491 /**********************************************************
492 /* Public API, access to token information, text
493 /**********************************************************
494 */
495
496 @Override public String getText() throws IOException { return delegate.getText(); }
497 @Override public boolean hasTextCharacters() { return delegate.hasTextCharacters(); }
498 @Override public char[] getTextCharacters() throws IOException { return delegate.getTextCharacters(); }
499 @Override public int getTextLength() throws IOException { return delegate.getTextLength(); }
500 @Override public int getTextOffset() throws IOException { return delegate.getTextOffset(); }
501
502 /*
503 /**********************************************************
504 /* Public API, access to token information, numeric
505 /**********************************************************
506 */
507
508 @Override
509 public BigInteger getBigIntegerValue() throws IOException { return delegate.getBigIntegerValue(); }
510
511 @Override
512 public boolean getBooleanValue() throws IOException { return delegate.getBooleanValue(); }
513
514 @Override
515 public byte getByteValue() throws IOException { return delegate.getByteValue(); }
516
517 @Override
518 public short getShortValue() throws IOException { return delegate.getShortValue(); }
519
520 @Override
521 public BigDecimal getDecimalValue() throws IOException { return delegate.getDecimalValue(); }
522
523 @Override
524 public double getDoubleValue() throws IOException { return delegate.getDoubleValue(); }
525
526 @Override
527 public float getFloatValue() throws IOException { return delegate.getFloatValue(); }
528
529 @Override
530 public int getIntValue() throws IOException { return delegate.getIntValue(); }
531
532 @Override
533 public long getLongValue() throws IOException { return delegate.getLongValue(); }
534
535 @Override
536 public NumberType getNumberType() throws IOException { return delegate.getNumberType(); }
537
538 @Override
539 public Number getNumberValue() throws IOException { return delegate.getNumberValue(); }
540
541 /*
542 /**********************************************************
543 /* Public API, access to token information, coercion/conversion
544 /**********************************************************
545 */
546
547 @Override public int getValueAsInt() throws IOException { return delegate.getValueAsInt(); }
548 @Override public int getValueAsInt(int defaultValue) throws IOException { return delegate.getValueAsInt(defaultValue); }
549 @Override public long getValueAsLong() throws IOException { return delegate.getValueAsLong(); }
550 @Override public long getValueAsLong(long defaultValue) throws IOException { return delegate.getValueAsLong(defaultValue); }
551 @Override public double getValueAsDouble() throws IOException { return delegate.getValueAsDouble(); }
552 @Override public double getValueAsDouble(double defaultValue) throws IOException { return delegate.getValueAsDouble(defaultValue); }
553 @Override public boolean getValueAsBoolean() throws IOException { return delegate.getValueAsBoolean(); }
554 @Override public boolean getValueAsBoolean(boolean defaultValue) throws IOException { return delegate.getValueAsBoolean(defaultValue); }
555 @Override public String getValueAsString() throws IOException { return delegate.getValueAsString(); }
556 @Override public String getValueAsString(String defaultValue) throws IOException { return delegate.getValueAsString(defaultValue); }
557
558 /*
559 /**********************************************************
560 /* Public API, access to token values, other
561 /**********************************************************
562 */
563
564 @Override public Object getEmbeddedObject() throws IOException { return delegate.getEmbeddedObject(); }
565 @Override public byte[] getBinaryValue(Base64Variant b64variant) throws IOException { return delegate.getBinaryValue(b64variant); }
566 @Override public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IOException { return delegate.readBinaryValue(b64variant, out); }
567 @Override public JsonLocation getTokenLocation() { return delegate.getTokenLocation(); }
Cowtowncoder00900312015-04-14 16:02:33 -0700568
569 /*
570 /**********************************************************
571 /* Internal helper methods
572 /**********************************************************
573 */
574
575 protected JsonStreamContext _filterContext() {
576 if (_exposedContext != null) {
577 return _exposedContext;
578 }
579 return _headContext;
580 }
581
Tatu Salorantadfd69092015-04-09 22:29:34 -0700582}