All Downloads are FREE. Search and download functionalities are using the official Maven repository.

zygf.jackshaft.impl.JacksonWrapperImpl Maven / Gradle / Ivy

Go to download

Incremental (non-blocking) parsing for Scala JSON ASTs, based on jackson-core.

There is a newer version: 0.2.2
Show newest version
package zygf.jackshaft.impl;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import zygf.jackshaft.exceptions.*;

import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Arrays;

/**
 * A non-blocking JSON parser middleware based on jackson, meant for building Scala ASTs.
 *
 * @param  JSON AST node type
 * @param  Collection type for storing array members
 * @param  Collection type for storing object members
 */
public class JacksonWrapperImpl extends JacksonWrapper
{
    public JacksonWrapperImpl(ParsingMiddlewareImpl middleware, Class jClass) {
        this(middleware, jClass, 4, 64);
    }
    
    public JacksonWrapperImpl(ParsingMiddlewareImpl middleware, Class jClass, int initialDepth, int maxDepth) {
        this.maxDepth = maxDepth;
        this.middleware = middleware;
        
        maps = (M[]) new Object[initialDepth]; // this is private, can be "erased"
        arrays = (A[]) new Object[initialDepth]; // this is private, can be "erased"
        states = new byte[initialDepth];
        keys = new String[initialDepth * 4];
        vals = (J[]) Array.newInstance(jClass, initialDepth * 4); // this isn't private, must be non-erased
        depth = -1;
    }
    
    private final ParsingMiddlewareImpl middleware;
    private final int maxDepth;
    
    // PARSING STATE
    
    /**
     * State at the current nesting level:
     * - 0 - nothing parsed
     * - 1 - 1 key/value pair parsed
     * - 2 - 2 key/value pairs parsed
     * - 3 - 3 key/value pairs parsed
     * - 4 - 4 key/value pairs parsed
     * - 5 - more key/value pairs parsed
     * - 6 - parsing an array
     * - 7 - streaming an array
     * - 8 - streaming whitespace-separated values
     */
    private byte[] states;
    
    /**
     * Parsed object keys, each nesting level gets 4 slots
     */
    private String[] keys;
    
    /**
     * Parsed object values, each nesting level gets 4 slots
     */
    private J[] vals;
    
    /**
     * Parsed JSON array at each nesting level
     */
    private A[] arrays;
    
    /**
     * Parsed JSON map of 5 or more pairs at each nesting level
     */
    private M[] maps;
    
    /**
     * Current nesting level, starts at -1 and goes up with each nested object or array
     */
    private int depth;
    
    /**
     * Reset this instance's state for parsing a fresh JSON stream
     */
    public void reset() {
        Arrays.fill(arrays, null);
        Arrays.fill(maps, null);
        Arrays.fill(keys, null);
        Arrays.fill(vals, null);
        Arrays.fill(states, (byte) 0);
        depth = -1;
    }
    
    private void grow() {
        final int maxDepth = this.maxDepth;
        int       limit    = arrays.length;
        if (limit == maxDepth)
            throw new IllegalStateException("Recursion limit reached.");
        
        limit *= 2;
        if (limit > maxDepth)
            limit = maxDepth;
        
        arrays = (A[]) Arrays.copyOf((Object[]) arrays, limit);
        maps = (M[]) Arrays.copyOf((Object[]) maps, limit);
        keys = Arrays.copyOf(keys, limit * 4);
        vals = Arrays.copyOf(vals, limit * 4);
        states = Arrays.copyOf(states, limit);
    }
    
    public J parseValue(final JsonParser jax) throws IOException {
        A[]      arrays = this.arrays;
        M[]      maps   = this.maps;
        byte[]   states = this.states;
        String[] keys   = this.keys;
        J[]      vals   = this.vals;
        int      depth  = this.depth;
        int      limit  = this.arrays.length;
        
        final ParsingMiddlewareImpl middleware = this.middleware;
        
        while (true) {
            final JsonToken token = jax.nextToken();
            
            if (token == null) {
                if (depth >= 0 && states[0] < 8)
                    throw UnexpectedEndOfInputException$.MODULE$;
                
                this.depth = -2;
                return null; // return point: final end of input
            }
            
            byte newState = 0;
            J    result   = null;
            
            switch (token) {
                case NOT_AVAILABLE:
                    return null; // return point: temporary end of input
                
                case START_ARRAY:
                    newState = 6;
                    //fallthrough
                
                case START_OBJECT:
                    depth++;
                    this.depth = depth;
                    if (depth >= limit) {
                        grow();
                        arrays = this.arrays;
                        maps = this.maps;
                        states = this.states;
                        keys = this.keys;
                        vals = this.vals;
                        limit = this.arrays.length;
                    }
                    states[depth] = newState;
                    continue;
                
                case END_ARRAY:
                    if (arrays[depth] == null)
                        result = middleware.buildArray();
                    else {
                        result = middleware.buildArray(arrays[depth]);
                        arrays[depth] = null;
                    }
                    depth--;
                    this.depth = depth;
                    break;
                
                case FIELD_NAME:
                    if (states[depth] < 4) {
                        keys[depth * 4 + states[depth]] = jax.getCurrentName();
                    }
                    continue;
                
                case END_OBJECT: {
                    int at = depth * 4;
                    switch (states[depth]) {
                        case 0:
                            result = middleware.buildObject();
                            break;
                        case 1:
                        case 2:
                        case 3:
                        case 4:
                            result = middleware.buildObject(keys, vals, at, states[depth]);
                            break;
                        default:
                            result = middleware.buildObject(maps[depth]);
                    }
                    maps[depth] = null;
                    depth--;
                    this.depth = depth;
                    break;
                }
                
                case VALUE_NULL:
                    result = middleware.buildNull();
                    break;
                
                case VALUE_TRUE:
                    result = middleware.buildTrue();
                    break;
                
                case VALUE_FALSE:
                    result = middleware.buildFalse();
                    break;
                
                case VALUE_NUMBER_INT:
                case VALUE_NUMBER_FLOAT:
                    result = middleware.buildNumber(jax.getNumberValue());
                    break;
                
                case VALUE_STRING:
                    if (jax.getTextLength() == 0)
                        result = middleware.buildString("");
                    else
                        result = middleware.buildString(jax.getText());
            }
            
            if (depth < 0) {
                if (states[0] >= 7) {
                    return null; // return point: finished streaming an array
                }
                
                reset();
                return result; // return point: successfully parsed a top level value (not used when streaming as depth>=0)
            }
            
            switch (states[depth]) {
                case 0:
                case 1:
                case 2:
                case 3:
                    vals[depth * 4 + states[depth]] = result;
                    states[depth]++;
                    break;
                case 4: {
                    int at = depth * 4;
                    maps[depth] = middleware.growMap(middleware.growMap(middleware.growMap(middleware.growMap(middleware.emptyMap(),
                                                                                                              keys[at], vals[at++]),
                                                                                           keys[at], vals[at++]),
                                                                        keys[at], vals[at++]),
                                                     keys[at], vals[at]);
                    states[depth] = 5;
                    //fallthrough
                }
                case 5:
                    maps[depth] = middleware.growMap(maps[depth], jax.getCurrentName(), result);
                    break;
                case 6:
                    if (arrays[depth] == null)
                        arrays[depth] = middleware.emptyArray();
                    arrays[depth] = middleware.growArray(arrays[depth], result);
                    break;
                default: // 7, 8 (only in parseArray, parseStream)
                    return result; // return point: parsed 1 streaming value
            }
        }
    }
    
    public boolean parseAsync(final JsonParser jax, final ParsingMode mode, final java.util.function.Consumer consumer) throws IOException {
        switch (mode) {
            case VALUE:
                if (depth != -2) {
                    J result = parseValue(jax);
                    if (result != null) {
                        consumer.accept(result);
                        depth = -2;
                        return true;
                    }
                    return false;
                }
                return true;
                
            case ARRAY:
                return parseArray(jax, consumer);
                
            case STREAM:
                parseStream(jax, consumer);
                return depth == -2;
                
            default:
                return true;
        }
    }
    
    public boolean parseArray(final JsonParser jax, final java.util.function.Consumer consumer) throws IOException {
        if (depth < 0) {
            if (states[0] == 7) {
                return true; 
            }
            else {
                switch (jax.nextToken()) {
                    case NOT_AVAILABLE:
                        return false;
                    case START_ARRAY:
                        break;
                    default:
                        throw new ArrayExpectedException("JSON array expected but first token was: " + jax.currentToken().toString());
                }
                depth++;
                states[depth] = 7;
            }
        }
        
        J result;
        while ((result = parseValue(jax)) != null) {
            consumer.accept(result);
        }
        
        return depth < 0;
    }
    
    public void parseStream(final JsonParser jax, final java.util.function.Consumer consumer) throws IOException {
        if (depth == -2)
            return;
        else if (depth == -1) {
            depth = 0;
            states[depth] = 8;
        }
        
        J result;
        while ((result = parseValue(jax)) != null) {
            consumer.accept(result);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy