org.eclipse.rdf4j.rio.rdfjson.RDFJSONParser.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.rdf4j.rio.rdfjson.RDFJSONParser.java

Source

/*******************************************************************************
 * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *******************************************************************************/
package org.eclipse.rdf4j.rio.rdfjson;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.io.input.BOMInputStream;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.RDFHandler;
import org.eclipse.rdf4j.rio.RDFHandlerException;
import org.eclipse.rdf4j.rio.RDFParseException;
import org.eclipse.rdf4j.rio.RDFParser;
import org.eclipse.rdf4j.rio.RioSetting;
import org.eclipse.rdf4j.rio.helpers.AbstractRDFParser;
import org.eclipse.rdf4j.rio.helpers.RDFJSONParserSettings;

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

/**
 * {@link RDFParser} implementation for the RDF/JSON format
 * 
 * @author Peter Ansell
 */
public class RDFJSONParser extends AbstractRDFParser implements RDFParser {

    private final RDFFormat actualFormat;

    /**
     * Creates a parser using {@link RDFFormat#RDFJSON} to identify the parser.
     */
    public RDFJSONParser() {
        this(RDFFormat.RDFJSON);
    }

    /**
     * Creates a parser using the given RDFFormat to self identify.
     * 
     * @param actualFormat
     */
    public RDFJSONParser(final RDFFormat actualFormat) {
        this.actualFormat = actualFormat;
    }

    @Override
    public RDFFormat getRDFFormat() {
        return this.actualFormat;
    }

    @Override
    public void parse(final InputStream inputStream, final String baseUri)
            throws IOException, RDFParseException, RDFHandlerException {
        JsonParser jp = null;

        clear();

        try {
            if (this.rdfHandler != null) {
                this.rdfHandler.startRDF();
            }

            jp = RDFJSONUtility.JSON_FACTORY.createParser(new BOMInputStream(inputStream, false));
            rdfJsonToHandlerInternal(this.rdfHandler, this.valueFactory, jp);
        } catch (final IOException e) {
            if (jp != null) {
                reportFatalError("Found IOException during parsing", e, jp.getCurrentLocation());
            } else {
                reportFatalError(e);
            }
        } finally {
            clear();
            if (jp != null) {
                try {
                    jp.close();
                } catch (final IOException e) {
                    reportFatalError("Found exception while closing JSON parser", e, jp.getCurrentLocation());
                }
            }
        }
        if (this.rdfHandler != null) {
            this.rdfHandler.endRDF();
        }
    }

    /**
     * Creates a literal, using the current value, language, and datatype, and additionally using the given
     * {@link JsonLocation} to provide information about the line and column numbers in the event of a
     * warning, error or exception being generated by the creation of the literal.
     * 
     * @param label
     *        the literal's lexical label
     * @param language
     *        the literal's language tag. Can be null.
     * @param datatype
     *        the literal's datatype. Can be null.
     * @param currentLocation
     *        the current JsonLocation. May not be null.
     * @return the created {@link Literal} object.
     * @throws RDFParseException
     */
    protected Literal createLiteral(String label, String language, IRI datatype, JsonLocation currentLocation)
            throws RDFParseException {
        return createLiteral(label, language, datatype, currentLocation.getLineNr(), currentLocation.getColumnNr());
    }

    protected void reportError(String msg, Throwable e, JsonLocation location, RioSetting<Boolean> setting)
            throws RDFParseException {
        reportError(msg, location.getLineNr(), location.getColumnNr(), setting);
    }

    protected void reportError(String msg, JsonLocation location, RioSetting<Boolean> setting)
            throws RDFParseException {
        reportError(msg, location.getLineNr(), location.getColumnNr(), setting);
    }

    protected void reportFatalError(String msg, Throwable e, JsonLocation location) throws RDFParseException {
        reportFatalError(msg, location.getLineNr(), location.getColumnNr());
    }

    protected void reportFatalError(String msg, JsonLocation location) throws RDFParseException {
        reportFatalError(msg, location.getLineNr(), location.getColumnNr());
    }

    @Override
    public void parse(final Reader reader, final String baseUri)
            throws IOException, RDFParseException, RDFHandlerException {
        JsonParser jp = null;

        clear();

        try {
            if (this.rdfHandler != null) {
                this.rdfHandler.startRDF();
            }

            jp = RDFJSONUtility.JSON_FACTORY.createParser(reader);
            rdfJsonToHandlerInternal(rdfHandler, valueFactory, jp);
        } catch (final IOException e) {
            if (jp != null) {
                reportFatalError("Found IOException during parsing", e, jp.getCurrentLocation());
            } else {
                reportFatalError(e);
            }
        } finally {
            clear();
            if (jp != null) {
                try {
                    jp.close();
                } catch (final IOException e) {
                    reportFatalError("Found exception while closing JSON parser", e, jp.getCurrentLocation());
                }
            }
        }
        if (rdfHandler != null) {
            rdfHandler.endRDF();
        }
    }

    private void rdfJsonToHandlerInternal(final RDFHandler handler, final ValueFactory vf, final JsonParser jp)
            throws IOException, JsonParseException, RDFParseException, RDFHandlerException {
        if (jp.nextToken() != JsonToken.START_OBJECT) {
            reportFatalError("Expected RDF/JSON document to start with an Object", jp.getCurrentLocation());
        }

        while (jp.nextToken() != JsonToken.END_OBJECT) {
            final String subjStr = jp.getCurrentName();
            Resource subject = null;

            subject = subjStr.startsWith("_:") ? createNode(subjStr.substring(2)) : vf.createIRI(subjStr);
            if (jp.nextToken() != JsonToken.START_OBJECT) {
                reportFatalError("Expected subject value to start with an Object", jp.getCurrentLocation());
            }

            boolean foundPredicate = false;
            while (jp.nextToken() != JsonToken.END_OBJECT) {
                final String predStr = jp.getCurrentName();

                final IRI predicate = vf.createIRI(predStr);
                foundPredicate = true;

                if (jp.nextToken() != JsonToken.START_ARRAY) {
                    reportFatalError("Expected predicate value to start with an array", jp.getCurrentLocation());
                }

                boolean foundObject = false;

                while (jp.nextToken() != JsonToken.END_ARRAY) {
                    if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
                        reportFatalError("Expected object value to start with an Object: subject=<" + subjStr
                                + "> predicate=<" + predStr + ">", jp.getCurrentLocation());
                    }

                    String nextValue = null;
                    String nextType = null;
                    String nextDatatype = null;
                    String nextLanguage = null;
                    final Set<String> nextContexts = new HashSet<String>(2);

                    while (jp.nextToken() != JsonToken.END_OBJECT) {
                        final String fieldName = jp.getCurrentName();
                        if (RDFJSONUtility.VALUE.equals(fieldName)) {
                            if (nextValue != null) {
                                reportError(
                                        "Multiple values found for a single object: subject=" + subjStr
                                                + " predicate=" + predStr,
                                        jp.getCurrentLocation(),
                                        RDFJSONParserSettings.FAIL_ON_MULTIPLE_OBJECT_VALUES);
                            }

                            jp.nextToken();

                            nextValue = jp.getText();
                        } else if (RDFJSONUtility.TYPE.equals(fieldName)) {
                            if (nextType != null) {
                                reportError(
                                        "Multiple types found for a single object: subject=" + subjStr
                                                + " predicate=" + predStr,
                                        jp.getCurrentLocation(),
                                        RDFJSONParserSettings.FAIL_ON_MULTIPLE_OBJECT_TYPES);
                            }

                            jp.nextToken();

                            nextType = jp.getText();
                        } else if (RDFJSONUtility.LANG.equals(fieldName)) {
                            if (nextLanguage != null) {
                                reportError(
                                        "Multiple languages found for a single object: subject=" + subjStr
                                                + " predicate=" + predStr,
                                        jp.getCurrentLocation(),
                                        RDFJSONParserSettings.FAIL_ON_MULTIPLE_OBJECT_LANGUAGES);
                            }

                            jp.nextToken();

                            nextLanguage = jp.getText();
                        } else if (RDFJSONUtility.DATATYPE.equals(fieldName)) {
                            if (nextDatatype != null) {
                                reportError(
                                        "Multiple datatypes found for a single object: subject=" + subjStr
                                                + " predicate=" + predStr,
                                        jp.getCurrentLocation(),
                                        RDFJSONParserSettings.FAIL_ON_MULTIPLE_OBJECT_DATATYPES);
                            }

                            jp.nextToken();

                            nextDatatype = jp.getText();
                        } else if (RDFJSONUtility.GRAPHS.equals(fieldName)) {
                            if (jp.nextToken() != JsonToken.START_ARRAY) {
                                reportError("Expected graphs to start with an array", jp.getCurrentLocation(),
                                        RDFJSONParserSettings.SUPPORT_GRAPHS_EXTENSION);
                            }

                            while (jp.nextToken() != JsonToken.END_ARRAY) {
                                final String nextGraph = jp.getText();
                                nextContexts.add(nextGraph);
                            }
                        } else {
                            reportError(
                                    "Unrecognised JSON field name for object: subject=" + subjStr + " predicate="
                                            + predStr + " fieldname=" + fieldName,
                                    jp.getCurrentLocation(), RDFJSONParserSettings.FAIL_ON_UNKNOWN_PROPERTY);
                        }
                    }

                    Value object = null;

                    if (nextType == null) {
                        reportFatalError("No type for object: subject=" + subjStr + " predicate=" + predStr,
                                jp.getCurrentLocation());
                    }

                    if (nextValue == null) {
                        reportFatalError("No value for object: subject=" + subjStr + " predicate=" + predStr,
                                jp.getCurrentLocation());
                    }

                    if (RDFJSONUtility.LITERAL.equals(nextType)) {
                        if (nextLanguage != null) {
                            object = this.createLiteral(nextValue, nextLanguage, null, jp.getCurrentLocation());
                        } else if (nextDatatype != null) {
                            object = this.createLiteral(nextValue, null, this.createURI(nextDatatype),
                                    jp.getCurrentLocation());
                        } else {
                            object = this.createLiteral(nextValue, null, null, jp.getCurrentLocation());
                        }
                    } else if (RDFJSONUtility.BNODE.equals(nextType)) {
                        if (nextLanguage != null) {
                            reportFatalError("Language was attached to a blank node object: subject=" + subjStr
                                    + " predicate=" + predStr, jp.getCurrentLocation());
                        }
                        if (nextDatatype != null) {
                            reportFatalError("Datatype was attached to a blank node object: subject=" + subjStr
                                    + " predicate=" + predStr, jp.getCurrentLocation());
                        }
                        object = createNode(nextValue.substring(2));
                    } else if (RDFJSONUtility.URI.equals(nextType)) {
                        if (nextLanguage != null) {
                            reportFatalError("Language was attached to a uri object: subject=" + subjStr
                                    + " predicate=" + predStr, jp.getCurrentLocation());
                        }
                        if (nextDatatype != null) {
                            reportFatalError("Datatype was attached to a uri object: subject=" + subjStr
                                    + " predicate=" + predStr, jp.getCurrentLocation());
                        }
                        object = vf.createIRI(nextValue);
                    }
                    foundObject = true;

                    if (!nextContexts.isEmpty()) {
                        for (final String nextContext : nextContexts) {
                            final Resource context;

                            if (nextContext.equals(RDFJSONUtility.NULL)) {
                                context = null;
                            } else if (nextContext.startsWith("_:")) {
                                context = createNode(nextContext.substring(2));
                            } else {
                                context = vf.createIRI(nextContext);
                            }
                            Statement st = vf.createStatement(subject, predicate, object, context);
                            if (handler != null) {
                                handler.handleStatement(st);
                            }
                        }
                    } else {
                        Statement st = vf.createStatement(subject, predicate, object);
                        if (handler != null) {
                            handler.handleStatement(st);
                        }
                    }

                }
                if (!foundObject) {
                    reportFatalError("No object for predicate: subject=" + subjStr + " predicate=" + predStr,
                            jp.getCurrentLocation());
                }
            }

            if (!foundPredicate) {
                reportFatalError("No predicate for object: subject=" + subjStr, jp.getCurrentLocation());
            }
        }
    }

    @Override
    public Collection<RioSetting<?>> getSupportedSettings() {
        Collection<RioSetting<?>> result = new HashSet<RioSetting<?>>(super.getSupportedSettings());

        result.add(RDFJSONParserSettings.FAIL_ON_MULTIPLE_OBJECT_DATATYPES);
        result.add(RDFJSONParserSettings.FAIL_ON_MULTIPLE_OBJECT_LANGUAGES);
        result.add(RDFJSONParserSettings.FAIL_ON_MULTIPLE_OBJECT_TYPES);
        result.add(RDFJSONParserSettings.FAIL_ON_MULTIPLE_OBJECT_VALUES);
        result.add(RDFJSONParserSettings.FAIL_ON_UNKNOWN_PROPERTY);
        result.add(RDFJSONParserSettings.SUPPORT_GRAPHS_EXTENSION);

        return result;
    }

}