Package com.dropbox.core.json

Source Code of com.dropbox.core.json.JsonExtractor

package com.dropbox.core.json;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import java.util.HashMap;

public abstract class JsonExtractor<T>
{
    public abstract T extract(JsonParser parser)
        throws IOException, JsonExtractionException;

    public final T extractField(JsonParser parser, String fieldName, T v)
        throws IOException, JsonExtractionException
    {
        if (v != null) throw new JsonExtractionException("duplicate field \"" + fieldName + "\"", parser.getTokenLocation());
        return extract(parser);
    }

    public final T extractOptional(JsonParser parser)
        throws IOException, JsonExtractionException
    {
        if (parser.getCurrentToken() == JsonToken.VALUE_NULL) {
            parser.nextToken();
            return null;
        } else {
            return extract(parser);
        }
    }

    /**
     * A wrapper around 'JsonParser.nextToken' that throws our own better {@link JsonExtractionException}
     * instead of Jackson's {@link JsonParseException}.
     * <p>
     * JsonParseException is bad for two reasons.  First, it extends IOException, which makes it easy to
     * miss.  Second, there's no way to get the original error message, which makes it hard to chain
     * logical location information (see {@link JsonExtractionException#addFieldContext} and
     * {@link JsonExtractionException#addArrayContext}).
     */
    public static JsonToken nextToken(JsonParser parser)
        throws IOException, JsonExtractionException
    {
        try {
            return parser.nextToken();
        }
        catch (JsonParseException ex) {
            throw JsonExtractionException.fromJackson(ex);
        }
    }

    // ------------------------------------------------------------------
    // Delimiter checking helpers.

    public static JsonLocation expectObjectStart(JsonParser parser)
        throws IOException, JsonExtractionException
    {
        if (parser.getCurrentToken() != JsonToken.START_OBJECT) {
            throw new JsonExtractionException("expecting the start of an object (\"{\")", parser.getTokenLocation());
        }
        JsonLocation loc = parser.getTokenLocation();
        nextToken(parser);
        return loc;
    }

    public static void expectObjectEnd(JsonParser parser)
        throws IOException, JsonExtractionException
    {
        if (parser.getCurrentToken() != JsonToken.END_OBJECT) {
            throw new JsonExtractionException("expecting the end of an object (\"}\")", parser.getTokenLocation());
        }
        nextToken(parser);
    }

    public static JsonLocation expectArrayStart(JsonParser parser)
        throws IOException, JsonExtractionException
    {
        if (parser.getCurrentToken() != JsonToken.START_ARRAY) {
            throw new JsonExtractionException("expecting the start of an array (\"[\")", parser.getTokenLocation());
        }
        JsonLocation loc = parser.getTokenLocation();
        nextToken(parser);
        return loc;
    }

    public static boolean isArrayEnd(JsonParser parser)
    {
        return (parser.getCurrentToken() == JsonToken.END_ARRAY);
    }

    public static void skipValue(JsonParser parser)
        throws IOException, JsonExtractionException
    {
        try {
            parser.skipChildren();
            parser.nextToken();
        }
        catch (JsonParseException ex) {
            throw JsonExtractionException.fromJackson(ex);
        }
    }

    // ------------------------------------------------------------------
    // Helpers for various types.

    public static long extractUnsignedLong(JsonParser parser)
        throws IOException, JsonExtractionException
    {
        try {
            long v = parser.getLongValue();
            if (v < 0) {
                throw new JsonExtractionException("expecting a non-negative number, got: " + v, parser.getTokenLocation());
            }
            parser.nextToken();
            return v;
        }
        catch (JsonParseException ex) {
            throw JsonExtractionException.fromJackson(ex);
        }
    }

    public static long extractUnsignedLongField(JsonParser parser, String fieldName, long v)
        throws IOException, JsonExtractionException
    {
        if (v >= 0) throw new JsonExtractionException("duplicate field \"" + fieldName + "\"", parser.getCurrentLocation());
        return JsonExtractor.extractUnsignedLong(parser);
    }

    public static final JsonExtractor<String> StringExtractor = new JsonExtractor<String>()
    {
        public String extract(JsonParser parser)
            throws IOException, JsonExtractionException
        {
            try {
                String v = parser.getText();
                parser.nextToken();
                return v;
            }
            catch (JsonParseException ex) {
                throw JsonExtractionException.fromJackson(ex);
            }
        }
    };

    public static final JsonExtractor<Boolean> BooleanExtractor = new JsonExtractor<Boolean>()
    {
        public Boolean extract(JsonParser parser)
            throws IOException, JsonExtractionException
        {
            return extractBoolean(parser);
        }
    };

    public static boolean extractBoolean(JsonParser parser)
        throws IOException, JsonExtractionException
    {
        try {
            boolean b = parser.getBooleanValue();
            parser.nextToken();
            return b;
        }
        catch (JsonParseException ex) {
            throw JsonExtractionException.fromJackson(ex);
        }
    }

    /**
     * If you're implementing a {@link JsonExtractor} for a JSON object, you can use this to map
     * field names to a number you can {@code switch} on to efficiently locate assign a
     * field.
     */
    public static final class FieldMapping
    {
        // This is not optimized.  Potential optimizations:
        // - Store 'int' values instead of 'Integer' values.
        // - Don't use "HashMap".  Do something gperf-like that generates a faster hash
        //   function for when you know the valid strings ahead-of-time.
        // - The get() could take (char[], offset, length) instead of String, which we can
        //   provide straight from JsonParser's internal buffer.  This makes error reporting
        //   tricky, though, because we won't have a string for addFieldContext.
        public final HashMap<String,Integer> fields;

        private FieldMapping(HashMap<String,Integer> fields)
        {
            assert fields != null;
            this.fields = fields;
        }

        public int get(String fieldName)
        {
            Integer i = fields.get(fieldName);
            if (i == null) return -1;
            return i;
        }

        public static final class Builder
        {
            private HashMap<String,Integer> fields = new HashMap<String,Integer>();

            public void add(String fieldName, int expectedIndex)
            {
                if (fields == null) throw new IllegalStateException("already called build(); can't call add() anymore");
                int i = fields.size();
                if (expectedIndex != i) {
                    throw new IllegalStateException("expectedIndex = " + expectedIndex + ", actual = " + i);
                }
                Object displaced = fields.put(fieldName, i);
                if (displaced != null) {
                    throw new IllegalStateException("duplicate field name: \"" + fieldName + "\"");
                }
            }

            public FieldMapping build()
            {
                HashMap<String,Integer> f = fields;
                this.fields = null;
                return new FieldMapping(f);
            }
        }
    }

    private static final JsonFactory jsonFactory = new JsonFactory();

    public T extractFullAndClose(InputStream utf8Body)
        throws IOException, JsonExtractionException
    {
        try {
            JsonParser parser = jsonFactory.createParser(utf8Body);
            return extractFullAndClose(parser);
        }
        catch (JsonParseException ex) {
            throw JsonExtractionException.fromJackson(ex);
        }
        finally {
            try { utf8Body.close(); } catch (IOException ex) {
                // Can ignore, because we got everything we wanted.
            }
        }
    }

    public T extractFull(String body)
        throws JsonExtractionException
    {
        try {
            JsonParser parser = jsonFactory.createParser(body);
            return extractFullAndClose(parser);
        }
        catch (JsonParseException ex) {
            throw JsonExtractionException.fromJackson(ex);
        }
        catch (IOException ex) {
            AssertionError ae = new AssertionError("Got IOException reading from String");
            ae.initCause(ae);
            throw ae;
        }
    }

    public T extractFull(byte[] utf8Body)
        throws JsonExtractionException
    {
        try {
            JsonParser parser = jsonFactory.createParser(utf8Body);
            return extractFullAndClose(parser);
        }
        catch (JsonParseException ex) {
            throw JsonExtractionException.fromJackson(ex);
        }
        catch (IOException ex) {
            AssertionError ae = new AssertionError("Got IOException reading from byte[]");
            ae.initCause(ae);
            throw ae;
        }
    }

    public T extractFromFile(String filePath)
        throws FileLoadException
    {
        return extractFromFile(new File(filePath));
    }

    public T extractFromFile(File file)
        throws FileLoadException
    {
        try {
            InputStream in = new FileInputStream(file);
            try {
                return extractFullAndClose(in);
            }
            finally {
                try { in.close(); } catch (IOException ex) {
                    // We already have our data, so ignore.
                }
            }
        }
        catch (JsonExtractionException ex) {
            throw new FileLoadException.JsonError(file, ex);
        }
        catch (IOException ex) {
            throw new FileLoadException.IOError(file, ex);
        }
    }

    public static abstract class FileLoadException extends Exception
    {
        protected FileLoadException(String message)
        {
            super(message);
        }

        public static final class IOError extends FileLoadException
        {
            public final IOException reason;

            public IOError(File file, IOException reason)
            {
                super("unable to read file \"" + file.getPath() + "\": " + reason.getMessage());
                this.reason = reason;
            }
        }

        public static final class JsonError extends FileLoadException
        {
            public final JsonExtractionException reason;

            public JsonError(File file, JsonExtractionException reason)
            {
                super(file.getPath() + ": " + reason.getMessage());
                this.reason = reason;
            }
        }
    }

    public T extractFullAndClose(JsonParser parser)
        throws IOException, JsonExtractionException
    {
        try {
            parser.nextToken();
            T value = this.extract(parser);
            if (parser.getCurrentToken() != null) {
                throw new AssertionError("The JSON library should ensure there's no tokens after the main value: "
                                         + parser.getCurrentToken() + "@" + parser.getCurrentLocation());
            }
            return value;
        }
        finally {
            if (parser != null) parser.close();
        }
    }
}
TOP

Related Classes of com.dropbox.core.json.JsonExtractor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.