/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jena.sparql.expr.nodevalue ; import java.util.Iterator ; import java.util.UUID ; import org.apache.jena.atlas.logging.Log ; import org.apache.jena.datatypes.xsd.XSDDatatype ; import org.apache.jena.graph.Node ; import org.apache.jena.graph.NodeFactory ; import org.apache.jena.iri.IRI ; import org.apache.jena.iri.IRIFactory ; import org.apache.jena.iri.Violation ; import org.apache.jena.sparql.expr.ExprEvalException ; import org.apache.jena.sparql.expr.ExprTypeException ; import org.apache.jena.sparql.expr.NodeValue ; import org.apache.jena.sparql.graph.NodeConst ; import org.apache.jena.sparql.util.FmtUtils ; import org.apache.jena.sparql.util.NodeUtils ; import org.apache.jena.vocabulary.XSD ; /** * Implementation of node-centric functions. */ public class NodeFunctions { private static final NodeValue xsdString = NodeValue.makeNode(XSD.xstring.asNode()) ; // Helper functions /** * check and get a string (may be a simple literal, literal with language * tag or an XSD string). */ public static Node checkAndGetStringLiteral(String label, NodeValue nv) { Node n = nv.asNode() ; if ( !n.isLiteral() ) throw new ExprEvalException(label + ": Not a literal: " + nv) ; String lang = n.getLiteralLanguage() ; if ( NodeUtils.isLangString(n) ) // Language tag. Legal. return n ; // No language tag : either no datatype or a datatype of xsd:string // Includes the case of rdf:langString and no language ==> Illegal as a compatible string. if ( nv.isString() ) return n ; throw new ExprEvalException(label + ": Not a string literal: " + nv) ; } /** * Check for string operations with primary first arg and second second arg * (e.g. CONTAINS). The arguments are not used in the same way and the check * operation is not symmetric. * <li> "abc"@en is compatible with "abc" * <li> "abc" is NOT compatible with "abc"@en */ public static void checkTwoArgumentStringLiterals(String label, NodeValue arg1, NodeValue arg2) { /* Quote the spec: * Compatibility of two arguments is defined as: * The arguments are simple literals or literals typed as xsd:string * The arguments are plain literals with identical language tags * The first argument is a plain literal with language tag and the second argument is a simple literal or literal typed as xsd:string */ Node n1 = checkAndGetStringLiteral(label, arg1) ; Node n2 = checkAndGetStringLiteral(label, arg2) ; String lang1 = n1.getLiteralLanguage() ; String lang2 = n2.getLiteralLanguage() ; if ( lang1 == null ) lang1 = "" ; if ( lang2 == null ) lang2 = "" ; // Case 1 if ( lang1.equals("") ) { if ( lang2.equals("") ) return ; throw new ExprEvalException(label + ": Incompatible: " + arg1 + " and " + arg2) ; } // Case 2 if ( lang1.equalsIgnoreCase(lang2) ) return ; // Case 3 if ( lang2.equals("") ) return ; throw new ExprEvalException(label + ": Incompatible: " + arg1 + " and " + arg2) ; // ---------- // if ( lang1.equals("") && !lang2.equals("") ) // throw new ExprEvalException(label + ": Incompatible: " + arg1 + " and " + arg2) ; // // // // if ( n1.getLiteralDatatype() != null ) { // // n1 is an xsd string by checkAndGetString // if ( XSDDatatype.XSDstring.equals(n2.getLiteralDatatypeURI()) ) // return ; // if ( n2.getLiteralLanguage().equals("") ) // return ; // throw new ExprEvalException(label + ": Incompatible: " + arg1 + " and " + arg2) ; // } // // // Incompatible? // // arg1 simple or xsd:string, arg2 has a lang. // // arg1 with lang, arg2 has a different lang. // if ( !lang1.equals("") && (!lang2.equals("") && !lang1.equals(lang2)) ) // throw new ExprEvalException(label + ": Incompatible: " + arg1 + " and " + arg2) ; } // -------- sameTerm public static NodeValue sameTerm(NodeValue nv1, NodeValue nv2) { return NodeValue.booleanReturn(sameTerm(nv1.asNode(), nv2.asNode())) ; } public static boolean sameTerm(Node n1, Node n2) { if ( n1.equals(n2) ) return true ; if ( n1.isLiteral() && n2.isLiteral() ) { // But language tags are case insensitive. String lang1 = n1.getLiteralLanguage() ; String lang2 = n2.getLiteralLanguage() ; if ( !lang1.equals("") && lang1.equalsIgnoreCase(lang2) ) { // Two language tags, equal by case insensitivity. boolean b = n1.getLiteralLexicalForm().equals(n2.getLiteralLexicalForm()) ; if ( b ) return true ; } } return false ; } // -------- RDFterm-equals public static NodeValue rdfTermEquals(NodeValue nv1, NodeValue nv2) { return NodeValue.booleanReturn(rdfTermEquals(nv1.asNode(), nv2.asNode())) ; } // Exact as defined by SPARQL spec. public static boolean rdfTermEquals(Node n1, Node n2) { if ( n1.equals(n2) ) return true ; if ( n1.isLiteral() && n2.isLiteral() ) { // Two literals, may be sameTerm by language tag case insensitivity. String lang1 = n1.getLiteralLanguage() ; String lang2 = n2.getLiteralLanguage() ; if ( !lang1.equals("") && lang1.equalsIgnoreCase(lang2) ) { // Two language tags, equal by case insensitivity. boolean b = n1.getLiteralLexicalForm().equals(n2.getLiteralLexicalForm()) ; if ( b ) return true ; } // Two literals, different terms, different language tags. NodeValue.raise(new ExprEvalException("Mismatch in RDFterm-equals: " + n1 + ", " + n2)) ; } // One or both not a literal. return false ; } // -------- str public static NodeValue str(NodeValue nv) { return NodeValue.makeString(str(nv.asNode())) ; } public static String str(Node node) { if ( node.isLiteral() ) return node.getLiteral().getLexicalForm() ; if ( node.isURI() ) return node.getURI() ; // if ( node.isBlank() ) return node.getBlankNodeId().getLabelString() ; // if ( node.isBlank() ) return "" ; if ( node.isBlank() ) NodeValue.raise(new ExprTypeException("Blank node: " + node)) ; NodeValue.raise(new ExprEvalException("Not a string: " + node)) ; return "[undef]" ; } // -------- datatype public static NodeValue datatype(NodeValue nv) { return NodeValue.makeNode(datatype(nv.asNode())) ; } public static Node datatype(Node node) { if ( !node.isLiteral() ) { NodeValue.raise(new ExprTypeException("datatype: Not a literal: " + node)) ; return null ; } String s = node.getLiteralDatatypeURI() ; boolean plainLiteral = (s == null || s.equals("")) ; if ( plainLiteral ) { boolean simpleLiteral = (node.getLiteralLanguage() == null || node.getLiteralLanguage().equals("")) ; if ( !simpleLiteral ) return NodeConst.rdfLangString ; return XSD.xstring.asNode() ; } return NodeFactory.createURI(s) ; } // -------- lang public static NodeValue lang(NodeValue nv) { if ( nv.isLangString() ) return NodeValue.makeString(nv.getLang()) ; if ( nv.isLiteral() ) return NodeValue.nvEmptyString ; NodeValue.raise(new ExprTypeException("lang: Not a literal: " + nv.asQuotedString())) ; return null ; } public static String lang(Node node) { if ( !node.isLiteral() ) NodeValue.raise(new ExprTypeException("lang: Not a literal: " + FmtUtils.stringForNode(node))) ; String s = node.getLiteralLanguage() ; if ( s == null ) s = "" ; return s ; } // -------- langMatches public static NodeValue langMatches(NodeValue nv, NodeValue nvPattern) { return langMatches(nv, nvPattern.getString()) ; } public static NodeValue langMatches(NodeValue nv, String langPattern) { Node node = nv.asNode() ; if ( !node.isLiteral() ) { NodeValue.raise(new ExprTypeException("langMatches: not a literal: " + node)) ; return null ; } String nodeLang = node.getLiteralLexicalForm() ; if ( langPattern.equals("*") ) { if ( nodeLang == null || nodeLang.equals("") ) return NodeValue.FALSE ; return NodeValue.TRUE ; } // See RFC 3066 (it's "tag (-tag)*)" String[] langElts = nodeLang.split("-") ; String[] langRangeElts = langPattern.split("-") ; /* * Here is the logic to compare language code. There is a match if the * language matches the parts of the pattern - the language may be * longer than the pattern. */ /* * RFC 4647 basic filtering. * * Notes for extended: * 1. Remove any -*- (but not *-) * 2. Compare primary tags. * 3. Is the remaining range a subsequence of the remaining language tag? */ if ( langRangeElts.length > langElts.length ) // Lang tag longer than pattern tag => can't match return NodeValue.FALSE ; for ( int i = 0 ; i < langRangeElts.length ; i++ ) { String range = langRangeElts[i] ; if ( range == null ) break ; // Language longer than range if ( i >= langElts.length ) break ; String lang = langElts[i] ; if ( range.equals("*") ) continue ; if ( !range.equalsIgnoreCase(lang) ) return NodeValue.FALSE ; } return NodeValue.TRUE ; } // -------- isURI/isIRI public static NodeValue isIRI(NodeValue nv) { return NodeValue.booleanReturn(isIRI(nv.asNode())) ; } public static boolean isIRI(Node node) { if ( node.isURI() ) return true ; return false ; } public static NodeValue isURI(NodeValue nv) { return NodeValue.booleanReturn(isIRI(nv.asNode())) ; } public static boolean isURI(Node node) { return isIRI(node) ; } // -------- isBlank public static NodeValue isBlank(NodeValue nv) { return NodeValue.booleanReturn(isBlank(nv.asNode())) ; } public static boolean isBlank(Node node) { return node.isBlank() ; } // -------- isLiteral public static NodeValue isLiteral(NodeValue nv) { return NodeValue.booleanReturn(isLiteral(nv.asNode())) ; } public static boolean isLiteral(Node node) { return node.isLiteral() ; } private static final IRIFactory iriFactory = IRIFactory.iriImplementation() ; public static boolean warningsForIRIs = false ; // -------- IRI public static NodeValue iri(NodeValue nv, String baseIRI) { if ( isIRI(nv.asNode()) ) return nv ; Node n2 = iri(nv.asNode(), baseIRI) ; return NodeValue.makeNode(n2) ; } public static Node iri(Node nv, String baseIRI) { if ( nv.isURI() ) return nv ; if ( nv.isBlank() ) { // Skolemization of blank nodes to IRIs : Don't ask, just don't ask. String x = nv.getBlankNodeLabel() ; return NodeFactory.createURI("_:" + x) ; } // Simple literal or xsd:string String str = simpleLiteralOrXSDString(nv) ; if ( str == null ) throw new ExprEvalException("Can't make an IRI from " + nv) ; IRI iri = null ; String iriStr = nv.getLiteralLexicalForm() ; // Level of checking? if ( baseIRI != null ) { IRI base = iriFactory.create(baseIRI) ; iri = base.create(iriStr) ; } else iri = iriFactory.create(iriStr) ; if ( !iri.isAbsolute() ) throw new ExprEvalException("Relative IRI string: " + iriStr) ; if ( warningsForIRIs && iri.hasViolation(false) ) { String msg = "unknown violation from IRI library" ; Iterator<Violation> iter = iri.violations(false) ; if ( iter.hasNext() ) { Violation viol = iter.next() ; msg = viol.getShortMessage() ; } Log.warn(NodeFunctions.class, "Bad IRI: " + msg + ": " + iri) ; } return NodeFactory.createURI(iri.toString()) ; } // The Jena version can vbe slow to inityailise (but is pure java) // private static UUIDFactory factory = new UUID_V4_Gen() ; // private static UUIDFactory factory = new UUID_V1_Gen() ; // public static NodeValue uuid() // { // JenaUUID uuid = factory.generate() ; // Node n = Node.createURI(uuid.asURN()) ; // return NodeValue.makeNode(n) ; // } // // public static NodeValue struuid() // { // JenaUUID uuid = factory.generate() ; // return NodeValue.makeString(uuid.asString()) ; // } public static NodeValue struuid() { return NodeValue.makeString(uuidString()) ; } public static NodeValue uuid() { String str = "urn:uuid:" + uuidString() ; Node n = NodeFactory.createURI(str) ; return NodeValue.makeNode(n) ; } private static String uuidString() { return UUID.randomUUID().toString() ; } private static String simpleLiteralOrXSDString(Node n) { if ( !n.isLiteral() ) return null ; if ( n.getLiteralDatatype() == null ) { if ( n.getLiteralLanguage().equals("") ) return n.getLiteralLexicalForm() ; } else if ( n.getLiteralDatatype().equals(XSDDatatype.XSDstring) ) return n.getLiteralLexicalForm() ; return null ; } public static NodeValue strDatatype(NodeValue v1, NodeValue v2) { if ( !v1.isString() ) throw new ExprEvalException("Not a string (arg 1): " + v1) ; if ( !v2.isIRI() ) throw new ExprEvalException("Not an IRI (arg 2): " + v2) ; String lex = v1.asString() ; Node dt = v2.asNode() ; // Check? Node n = NodeFactory.createLiteral(lex, NodeFactory.getType(dt.getURI())) ; return NodeValue.makeNode(n) ; } public static NodeValue strLang(NodeValue v1, NodeValue v2) { if ( !v1.isString() ) throw new ExprEvalException("Not a string (arg 1): " + v1) ; if ( !v2.isString() ) throw new ExprEvalException("Not a string (arg 2): " + v2) ; String lex = v1.asString() ; String lang = v2.asString() ; if ( lang.isEmpty() ) throw new ExprEvalException("Empty lang tag") ; return NodeValue.makeLangString(lex, lang) ; } }