| /* |
| * 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.util; |
| |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| import org.apache.jena.atlas.lib.ListUtils; |
| import org.apache.jena.atlas.lib.SetUtils; |
| import org.apache.jena.atlas.lib.StrUtils ; |
| import org.apache.jena.datatypes.RDFDatatype ; |
| 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.rdf.model.impl.Util ; |
| import org.apache.jena.sparql.ARQInternalErrorException ; |
| import org.apache.jena.sparql.expr.Expr ; |
| import org.apache.jena.sparql.expr.ExprEvalException ; |
| import org.apache.jena.sparql.expr.NodeValue ; |
| import org.apache.jena.sparql.expr.nodevalue.NodeFunctions ; |
| import org.apache.jena.util.iterator.ExtendedIterator ; |
| import org.apache.jena.util.iterator.MapFilter ; |
| import org.apache.jena.util.iterator.MapFilterIterator ; |
| import org.apache.jena.util.iterator.WrappedIterator ; |
| |
| /** Node utilities */ |
| public class NodeUtils |
| { |
| public interface EqualityTest { |
| default boolean equal(Node n1, Node n2) { |
| return Objects.equals(n1, n2) ; |
| } |
| } |
| |
| /** IRI to Node */ |
| public static Node asNode(IRI iri) { |
| return NodeFactory.createURI(iri.toString()) ; |
| } |
| |
| /** IRI string to Node */ |
| public static Node asNode(String iri) { |
| return NodeFactory.createURI(iri) ; |
| } |
| |
| /** Return true if the node is a literal and has a language tag */ |
| public static boolean hasLang(Node node) { |
| if ( !node.isLiteral() ) |
| return false ; |
| String x = node.getLiteralLanguage() ; |
| if ( x == null ) |
| return false ; |
| if ( x.equals("") ) |
| return false ; |
| return true ; |
| } |
| |
| /** Get lexical for of anything that looks like a string literal. |
| * Returns the string value of plain literal (simple literal |
| * or lang string) or XSD string. |
| */ |
| public static String stringLiteral(Node literal) { |
| if ( !literal.isLiteral() ) |
| return null ; |
| RDFDatatype dType = literal.getLiteralDatatype() ; |
| String langTag = literal.getLiteralLanguage() ; |
| |
| // Language? |
| if ( langTag != null && !langTag.equals("") ) |
| return literal.getLiteralLexicalForm() ; |
| |
| if ( dType == null || dType.equals(XSDDatatype.XSDstring) ) |
| return literal.getLiteralLexicalForm() ; |
| |
| return null ; |
| } |
| |
| /** Convert IRI Nodes to strings. Skip other kinds of Node */ |
| public static Iterator<String> nodesToURIs(Iterator<Node> iter) { |
| MapFilter<Node, String> mapper = new MapFilter<Node, String>() { |
| @Override |
| public String accept(Node x) { |
| return x.getURI() ; |
| } |
| } ; |
| |
| ExtendedIterator<Node> eIter = WrappedIterator.create(iter) ; |
| Iterator<String> conv = new MapFilterIterator<>(mapper, eIter) ; |
| return conv ; |
| } |
| |
| |
| /** @deprecated Use {@link #convertToSetNodes} */ |
| @Deprecated |
| public static Set<Node> convertToNodes(Collection<String> namedGraphs) { |
| return convertToSetNodes(namedGraphs); |
| } |
| |
| /** Convert a collection of strings to a set of {@link Node Nodes}. */ |
| public static Set<Node> convertToSetNodes(Collection<String> namedGraphs) { |
| Set<Node> nodes = SetUtils.toSet( |
| namedGraphs.stream().map(NodeFactory::createURI) |
| ); |
| return nodes; |
| } |
| |
| /** Convert a collection of strings to a set of {@link Node Nodes}. */ |
| public static Set<Node> convertToSetNodes(String... namedGraphs) { |
| return convertToSetNodes(Arrays.asList(namedGraphs)); |
| } |
| |
| /** Convert strings to a List of {@link Node Nodes}. */ |
| public static List<Node> convertToListNodes(String... namedGraphs) { |
| return convertToListNodes(Arrays.asList(namedGraphs)); |
| } |
| |
| /** Convert strings to a List of {@link Node Nodes}. */ |
| public static List<Node> convertToListNodes(List<String> namedGraphs) { |
| List<Node> nodes = ListUtils.toList( |
| namedGraphs.stream().map(NodeFactory::createURI) |
| ); |
| return nodes; |
| } |
| |
| /** Compare two Nodes, based on their RDF terms forms, not value */ |
| public static int compareRDFTerms(Node node1, Node node2) { |
| if ( node1 == null ) { |
| if ( node2 == null ) |
| return Expr.CMP_EQUAL ; |
| return Expr.CMP_LESS ; |
| } |
| |
| if ( node2 == null ) |
| return Expr.CMP_GREATER ; |
| |
| // No nulls. |
| if ( node1.isLiteral() && node2.isLiteral() ) |
| return compareLiteralsBySyntax(node1, node2) ; |
| |
| // One or both not literals |
| // Variables < Blank nodes < URIs < Literals |
| |
| if ( node1.isVariable() ) { |
| if ( node2.isVariable() ) { |
| return StrUtils.strCompare(node1.getName(), node2.getName()) ; |
| } |
| // Variables before anything else |
| return Expr.CMP_LESS ; |
| } |
| |
| if ( node2.isVariable() ) { |
| // node1 not variable |
| return Expr.CMP_GREATER ; |
| } |
| |
| if ( node1.isBlank() ) { |
| if ( node2.isBlank() ) { |
| String s1 = node1.getBlankNodeId().getLabelString() ; |
| String s2 = node2.getBlankNodeId().getLabelString() ; |
| return StrUtils.strCompare(s1, s2) ; |
| } |
| // bNodes before anything but variables |
| return Expr.CMP_LESS ; |
| } |
| |
| if ( node2.isBlank() ) |
| // node1 not blank. |
| return Expr.CMP_GREATER ; |
| |
| // Not blanks. 2 URI or one URI and one literal |
| |
| if ( node1.isURI() ) { |
| if ( node2.isURI() ) { |
| String s1 = node1.getURI() ; |
| String s2 = node2.getURI() ; |
| return StrUtils.strCompare(s1, s2) ; |
| } |
| return Expr.CMP_LESS ; |
| } |
| |
| if ( node2.isURI() ) |
| return Expr.CMP_GREATER ; |
| |
| // No URIs, no blanks nodes by this point |
| // And a pair of literals was filterd out first. |
| |
| // Should not happen. |
| throw new ARQInternalErrorException("Compare: " + node1 + " " + node2) ; |
| } |
| |
| /** Compare literals by kind - not by value. |
| * Gives a deterministic, stable, arbitrary ordering between unrelated literals. |
| * |
| * Ordering: |
| * <ol> |
| * <li>By lexical form</li> |
| * <li> For same lexical form: |
| * <ul> |
| * <li> RDF 1.0 : simple literal < literal by lang < literal with type |
| * <li> RDF 1.1 : xsd:string < rdf:langString < other dataypes.<br/> |
| * This is the closest to SPARQL 1.1: treat xsd:string as a simple literal</ul></li> |
| * <li> Lang by sorting on language tag (first case insensitive then case sensitive) |
| * <li> Datatypes by URI |
| * </ol> |
| */ |
| |
| private static int compareLiteralsBySyntax(Node node1, Node node2) { |
| if ( node1 == null || !node1.isLiteral() || node2 == null || !node2.isLiteral() ) |
| throw new ARQInternalErrorException("compareLiteralsBySyntax called with non-literal: (" + node1 + "," + node2 + ")") ; |
| |
| if ( node1.equals(node2) ) |
| return Expr.CMP_EQUAL ; |
| |
| String lex1 = node1.getLiteralLexicalForm() ; |
| String lex2 = node2.getLiteralLexicalForm() ; |
| |
| int x = StrUtils.strCompare(lex1, lex2) ; |
| if ( x != Expr.CMP_EQUAL ) |
| return x ; |
| |
| // Same lexical form. Not .equals() |
| if ( isSimpleString(node1) ) // node2 not a simple string because they |
| // would be .equals |
| return Expr.CMP_LESS ; |
| if ( isSimpleString(node2) ) |
| return Expr.CMP_GREATER ; |
| // Neither simple string / xsd:string(RDF 1.1) |
| |
| // Both language strings? |
| if ( isLangString(node1) && isLangString(node2) ) { |
| String lang1 = node1.getLiteralLanguage() ; |
| String lang2 = node2.getLiteralLanguage() ; |
| x = StrUtils.strCompareIgnoreCase(lang1, lang2) ; |
| if ( x != Expr.CMP_EQUAL ) |
| return x ; |
| x = StrUtils.strCompare(lang1, lang2) ; |
| if ( x != Expr.CMP_EQUAL ) |
| return x ; |
| throw new ARQInternalErrorException("compareLiteralsBySyntax: lexical form and languages tags identical on non.equals literals") ; |
| } |
| |
| // One a language string? |
| if ( isLangString(node1) ) |
| return Expr.CMP_LESS ; |
| if ( isLangString(node2) ) |
| return Expr.CMP_GREATER ; |
| |
| // Both have other datatypes. Neither simple nor language tagged. |
| String dt1 = node1.getLiteralDatatypeURI() ; |
| String dt2 = node2.getLiteralDatatypeURI() ; |
| // Two datatypes. |
| return StrUtils.strCompare(dt1, dt2) ; |
| } |
| |
| /** |
| * A Node is a simple string if: |
| * <li>(RDF 1.0) No datatype and no language tag |
| * <li>(RDF 1.1) xsd:string |
| */ |
| public static boolean isSimpleString(Node n) { return Util.isSimpleString(n) ; } |
| |
| /** |
| * A Node is a language string if it has a language tag. |
| * (RDF 1.0 and RDF 1.1) |
| */ |
| public static boolean isLangString(Node n) { return Util.isLangString(n) ; } |
| |
| // This is term comparison. |
| public static EqualityTest sameTerm = new EqualityTest() { |
| @Override |
| public boolean equal(Node n1, Node n2) |
| { |
| return NodeFunctions.sameTerm(n1, n2) ; |
| } |
| } ; |
| // This is value comparison |
| public static EqualityTest sameValue = new EqualityTest() { |
| @Override |
| public boolean equal(Node n1, Node n2) |
| { |
| NodeValue nv1 = NodeValue.makeNode(n1) ; |
| NodeValue nv2 = NodeValue.makeNode(n2) ; |
| try { |
| return NodeValue.sameAs(nv1, nv2) ; |
| } catch(ExprEvalException ex) |
| { |
| // Incomparible as values - must be different for our purposes. |
| return false ; |
| } |
| } |
| } ; |
| } |