/** * Copyright 2005-2014 Restlet * * The contents of this file are subject to the terms of one of the following * open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can * select the license that you prefer but you may not use this file except in * compliance with one of these Licenses. * * You can obtain a copy of the Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the EPL 1.0 license at * http://www.opensource.org/licenses/eclipse-1.0 * * See the Licenses for the specific language governing permissions and * limitations under the Licenses. * * Alternatively, you can obtain a royalty free commercial license with less * limitations, transferable or non-transferable, directly at * http://restlet.com/products/restlet-framework * * Restlet is a registered trademark of Restlet S.A.S. */ package org.restlet.ext.rdf.internal.n3; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.restlet.data.Reference; import org.restlet.ext.rdf.Graph; import org.restlet.ext.rdf.GraphHandler; import org.restlet.ext.rdf.Literal; import org.restlet.ext.rdf.internal.RdfConstants; import org.restlet.ext.rdf.internal.turtle.BlankNodeToken; import org.restlet.ext.rdf.internal.turtle.Context; import org.restlet.ext.rdf.internal.turtle.LexicalUnit; import org.restlet.ext.rdf.internal.turtle.ListToken; import org.restlet.ext.rdf.internal.turtle.RdfTurtleReader; import org.restlet.ext.rdf.internal.turtle.StringToken; import org.restlet.ext.rdf.internal.turtle.Token; import org.restlet.ext.rdf.internal.turtle.UriToken; import org.restlet.representation.Representation; /** * Handler of RDF content according to the N3 notation. * * @author Thierry Boileau */ public class RdfN3Reader extends RdfTurtleReader { /** * Constructor. * * @param rdfRepresentation * The representation to read. * @param graphHandler * The graph handler invoked during the parsing. * @throws IOException */ public RdfN3Reader(Representation rdfN3Representation, GraphHandler graphHandler) throws IOException { super(rdfN3Representation, graphHandler); } @Override protected void generateLinks(List<LexicalUnit> lexicalUnits) { Object currentSubject = null; Reference currentPredicate = null; Object currentObject = null; int nbTokens = 0; boolean swapSubjectObject = false; // Stores the list of parsed subjects. List<Object> subjects = new ArrayList<Object>(); for (int i = 0; i < lexicalUnits.size(); i++) { LexicalUnit lexicalUnit = lexicalUnits.get(i); nbTokens++; switch (nbTokens) { case 1: if (",".equals(lexicalUnit.getValue())) { nbTokens++; } else if (";".equals(lexicalUnit.getValue())) { if (!subjects.isEmpty()) { currentSubject = subjects.get(subjects.size() - 1); } } else { if ("!".equalsIgnoreCase(lexicalUnit.getValue())) { currentObject = new BlankNodeToken(newBlankNodeId()) .resolve(); currentPredicate = getPredicate(lexicalUnits.get(++i)); this.link(currentSubject, currentPredicate, currentObject); currentSubject = currentObject; nbTokens = 1; } else if ("^".equalsIgnoreCase(lexicalUnit.getValue())) { currentObject = currentSubject; currentPredicate = getPredicate(lexicalUnits.get(++i)); currentSubject = new BlankNodeToken(newBlankNodeId()) .resolve(); this.link(currentSubject, currentPredicate, currentObject); nbTokens = 1; } else { currentSubject = lexicalUnit.resolve(); subjects.add(currentSubject); } } break; case 2: if ("is".equalsIgnoreCase(lexicalUnit.getValue())) { nbTokens--; swapSubjectObject = true; } else if ("has".equalsIgnoreCase(lexicalUnit.getValue())) { nbTokens--; } else if ("=".equalsIgnoreCase(lexicalUnit.getValue())) { currentPredicate = RdfConstants.PREDICATE_SAME; } else if ("=>".equalsIgnoreCase(lexicalUnit.getValue())) { currentPredicate = RdfConstants.PREDICATE_IMPLIES; } else if ("<=".equalsIgnoreCase(lexicalUnit.getValue())) { swapSubjectObject = true; currentPredicate = RdfConstants.PREDICATE_IMPLIES; } else if ("a".equalsIgnoreCase(lexicalUnit.getValue())) { currentPredicate = RdfConstants.PREDICATE_TYPE; } else if ("!".equalsIgnoreCase(lexicalUnit.getValue())) { currentObject = new BlankNodeToken(newBlankNodeId()) .resolve(); currentPredicate = getPredicate(lexicalUnits.get(++i)); this.link(currentSubject, currentPredicate, currentObject); currentSubject = currentObject; nbTokens = 1; } else if ("^".equalsIgnoreCase(lexicalUnit.getValue())) { currentObject = currentSubject; currentPredicate = getPredicate(lexicalUnits.get(++i)); currentSubject = new BlankNodeToken(newBlankNodeId()) .resolve(); this.link(currentSubject, currentPredicate, currentObject); nbTokens = 1; } else { currentPredicate = getPredicate(lexicalUnit); } break; case 3: if ("of".equalsIgnoreCase(lexicalUnit.getValue())) { nbTokens--; } else { // take care of the "path" shorthands. int j = i + 1; if (j < lexicalUnits.size() && isPath(lexicalUnits.get(j))) { if ("!".equalsIgnoreCase(lexicalUnits.get(j).getValue())) { // Create a new BlankNode which is the object of the // current link. currentObject = new BlankNodeToken(newBlankNodeId()) .resolve(); this.link(currentSubject, currentPredicate, currentObject); // Interpret the "!" path currentPredicate = getPredicate(lexicalUnits .get(j + 1)); this.link(lexicalUnit.resolve(), currentPredicate, currentObject); currentSubject = currentObject; nbTokens = 0; i += 2; } else if ("^".equalsIgnoreCase(lexicalUnits.get(j) .getValue())) { // Create a new BlankNode which is the object of the // current link. currentObject = new BlankNodeToken(newBlankNodeId()) .resolve(); this.link(currentSubject, currentPredicate, currentObject); // Interpret the "^" path currentSubject = currentObject; currentPredicate = getPredicate(lexicalUnits .get(j + 1)); currentObject = lexicalUnit.resolve(); this.link(currentSubject, currentPredicate, currentObject); nbTokens = 0; i += 2; } } else { if (swapSubjectObject) { currentObject = currentSubject; currentSubject = lexicalUnit.resolve(); this.link(currentSubject, currentPredicate, currentObject); currentSubject = currentObject; } else { currentObject = lexicalUnit.resolve(); this.link(currentSubject, currentPredicate, currentObject); } nbTokens = 0; swapSubjectObject = false; } } break; default: break; } } } /** * Returns the given lexical unit as a predicate. * * @param lexicalUnit * The lexical unit to get as a predicate. * @return A RDF URI reference of the predicate. */ private Reference getPredicate(LexicalUnit lexicalUnit) { Reference result = null; Object p = lexicalUnit.resolve(); if (p instanceof Reference) { result = (Reference) p; } else if (p instanceof String) { result = new Reference((String) p); } return result; } @Override protected boolean isDelimiter(int c) { return isWhiteSpace(c) || c == '^' || c == '!' || c == '=' || c == '<' || c == '"' || c == '{' || c == '}' || c == '[' || c == ']' || c == '(' || c == ')' || c == '.' || c == ';' || c == ',' || c == '@'; } /** * Returns true if the given lexical unit is a "path" shorthand. * * @param lexicalUnit * The lexical unit to analyse. * @return True if the given lexical unit is a "path" shorthand. */ protected boolean isPath(LexicalUnit lexicalUnit) { return "!".equals(lexicalUnit.getValue()) || "^".equals(lexicalUnit.getValue()); } /** * Callback method used when a link is parsed or written. * * @param source * The source or subject of the link. * @param typeRef * The type reference of the link. * @param target * The target or object of the link. */ @Override protected void link(Object source, Reference typeRef, Object target) { if (source instanceof Reference) { if (target instanceof Reference) { getGraphHandler().link((Reference) source, typeRef, (Reference) target); } else if (target instanceof Literal) { getGraphHandler().link((Reference) source, typeRef, (Literal) target); } else { org.restlet.Context .getCurrentLogger() .warning( "The N3 document contains an object which is neither a Reference nor a literal: " + target); org.restlet.Context.getCurrentLogger().warning( getParsingMessage()); } } else if (source instanceof Graph) { if (target instanceof Reference) { getGraphHandler().link((Graph) source, typeRef, (Reference) target); } else if (target instanceof Literal) { getGraphHandler().link((Graph) source, typeRef, (Literal) target); } else { org.restlet.Context .getCurrentLogger() .warning( "The N3 document contains an object which is neither a Reference nor a literal: " + target); org.restlet.Context.getCurrentLogger().warning( getParsingMessage()); } } } @Override protected void parseBlankNode(BlankNodeToken blankNode) throws IOException { step(); do { consumeWhiteSpaces(); switch (getChar()) { case '(': blankNode.getLexicalUnits().add( new ListToken(this, getContext())); break; case '<': if (step() == '=') { blankNode.getLexicalUnits().add(new Token("<=")); step(); discard(); } else { stepBack(); blankNode.getLexicalUnits().add( new UriToken(this, getContext())); } break; case '_': blankNode.getLexicalUnits().add( new BlankNodeToken(parseToken())); break; case '"': blankNode.getLexicalUnits().add( new StringToken(this, getContext())); break; case '[': blankNode.getLexicalUnits().add( new BlankNodeToken(this, getContext())); break; case '!': blankNode.getLexicalUnits().add(new Token("!")); step(); discard(); break; case '^': blankNode.getLexicalUnits().add(new Token("^")); step(); discard(); break; case '=': if (step() == '>') { blankNode.getLexicalUnits().add(new Token("=>")); step(); discard(); } else { blankNode.getLexicalUnits().add(new Token("=")); discard(); } break; case '@': // Remove the leading '@' character. step(); discard(); blankNode.getLexicalUnits().add(new Token(this, getContext())); discard(); break; case ';': step(); discard(); blankNode.getLexicalUnits().add(new Token(";")); break; case ',': step(); discard(); blankNode.getLexicalUnits().add(new Token(",")); break; case '#': parseComment(); break; case '{': blankNode.getLexicalUnits().add( new FormulaToken(this, getContext())); break; case ']': break; default: if (!isEndOfFile(getChar())) { blankNode.getLexicalUnits().add( new Token(this, getContext())); } break; } } while (!isEndOfFile(getChar()) && getChar() != ']'); if (getChar() == ']') { // Set the cursor at the right of the list token. step(); } } /** * Parses the given formula token. * * @param formulaToken * The formula token to parse. * @throws IOException */ protected void parseFormula(FormulaToken formulaToken) throws IOException { step(); do { parseStatement(new Context()); } while (!isEndOfFile(getChar()) && getChar() != '}'); if (getChar() == '}') { // Set the cursor at the right of the formula token. step(); } } @Override protected void parseList(ListToken listToken) throws IOException { step(); do { consumeWhiteSpaces(); switch (getChar()) { case '(': listToken.getLexicalUnits().add( new ListToken(this, getContext())); break; case '<': if (step() == '=') { listToken.getLexicalUnits().add(new Token("<=")); step(); discard(); } else { stepBack(); listToken.getLexicalUnits().add( new UriToken(this, getContext())); } break; case '_': listToken.getLexicalUnits().add( new BlankNodeToken(this.parseToken())); break; case '"': listToken.getLexicalUnits().add( new StringToken(this, getContext())); break; case '[': listToken.getLexicalUnits().add( new BlankNodeToken(this, getContext())); break; case '{': listToken.getLexicalUnits().add( new FormulaToken(this, getContext())); break; case ')': break; default: if (!isEndOfFile(getChar())) { listToken.getLexicalUnits().add( new Token(this, getContext())); } break; } } while (!isEndOfFile(getChar()) && getChar() != ')'); if (getChar() == ')') { // Set the cursor at the right of the list token. step(); } } @Override protected void parseStatement(Context context) throws IOException { List<LexicalUnit> lexicalUnits = new ArrayList<LexicalUnit>(); do { consumeWhiteSpaces(); switch (getChar()) { case '(': lexicalUnits.add(new ListToken(this, context)); break; case '<': if (step() == '=') { lexicalUnits.add(new Token("<=")); step(); discard(); } else { stepBack(); lexicalUnits.add(new UriToken(this, context)); } break; case '_': lexicalUnits.add(new BlankNodeToken(parseToken())); break; case '"': lexicalUnits.add(new StringToken(this, context)); break; case '[': lexicalUnits.add(new BlankNodeToken(this, context)); break; case '!': lexicalUnits.add(new Token("!")); step(); discard(); break; case '^': lexicalUnits.add(new Token("^")); step(); discard(); break; case '=': if (step() == '>') { lexicalUnits.add(new Token("=>")); step(); discard(); } else { lexicalUnits.add(new Token("=")); discard(); } break; case '@': // Remove the leading '@' character. step(); discard(); lexicalUnits.add(new Token(this, context)); discard(); break; case ';': step(); discard(); lexicalUnits.add(new Token(";")); break; case ',': step(); discard(); lexicalUnits.add(new Token(",")); break; case '{': lexicalUnits.add(new FormulaToken(this, context)); break; case '#': parseComment(); break; case '.': break; default: if (!isEndOfFile(getChar())) { lexicalUnits.add(new Token(this, context)); } break; } } while (!isEndOfFile(getChar()) && getChar() != '.' && getChar() != '}'); // Generate the links generateLinks(lexicalUnits); } }