/** Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Portions Copyright Aduna (http://www.aduna-software.com/) (c) 2007. * * Licensed under the Aduna BSD-style license. */ /* * Created on Aug 8, 2011 */ package com.bigdata.bop.solutions; import info.aduna.lang.ObjectUtil; import java.io.Serializable; import java.util.Comparator; import org.openrdf.model.Literal; import org.openrdf.model.URI; import org.openrdf.model.datatypes.XMLDatatypeUtil; import org.openrdf.query.algebra.Compare.CompareOp; import org.openrdf.query.algebra.evaluation.ValueExprEvaluationException; import org.openrdf.query.algebra.evaluation.util.QueryEvaluationUtil; import org.openrdf.query.algebra.evaluation.util.ValueComparator; import com.bigdata.rdf.internal.IV; import com.bigdata.rdf.internal.impl.bnode.SidIV; import com.bigdata.rdf.internal.impl.literal.LiteralExtensionIV; import com.bigdata.rdf.model.BigdataLiteral; /** * A comparator that compares {@link IV}s according the SPARQL value ordering as * specified in <a * href="http://www.w3.org/TR/rdf-sparql-query/#modOrderBy">SPARQL Query * Language for RDF</a>. This implementation is based on the openrdf * {@link ValueComparator} but has been modified to work with {@link IV}s. * <p> * Note: {@link SidIV} are blank nodes. However, we order SIDs after normal * blank nodes since they are modeling somewhat different information. * * @author james * @author Arjohn Kampman * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @author <a href="mailto:mrpersonick@users.sourceforge.net">Mike Personick</a> * @version $Id$ */ public class IVComparator implements Comparator<IV>, Serializable { /** * */ private static final long serialVersionUID = 1L; @SuppressWarnings({ "rawtypes", "unchecked" }) public int compare(final IV o1, final IV o2) { // check equality if (ObjectUtil.nullEquals(o1, o2)) { return 0; } // 1. (Lowest) no value assigned to the variable if (o1 == null) { return -1; } if (o2 == null) { return 1; } // 2. Blank nodes final boolean b1 = o1.isBNode(); final boolean b2 = o2.isBNode(); if (b1 && b2) { return compareBNodes(o1, o2); } if (b1) { return -1; } if (b2) { return 1; } // 2.5 Statement identifiers (SIDs) final boolean s1 = o1.isStatement(); final boolean s2 = o2.isStatement(); if (s1 && s2) { return compareSIDs(o1, o2); } if (s1) { return -1; } if (s2) { return 1; } // 3. IRIs final boolean u1 = o1.isURI(); final boolean u2 = o2.isURI(); if (u1 && u2) { return compareURIs((URI) o1, (URI) o2); } if (u1) { return -1; } if (u2) { return 1; } // 4. RDF literals return compareLiterals((IV<BigdataLiteral, ?>) o1, (IV<BigdataLiteral, ?>) o2); } /** * Use the natural ordering of the {@link IV}s when they are both blank * nodes. This causes the solutions for the same blank node to be "grouped". * * @see <a href="http://www.openrdf.org/issues/browse/SES-873"> Order the * same Blank Nodes together in ORDER BY</a> */ @SuppressWarnings({ "unchecked", "rawtypes" }) private int compareBNodes(final IV leftBNode, final IV rightBNode) { return leftBNode.compareTo(rightBNode); } /** * Use the natural ordering of the {@link SidIV}s when they are both SIDs * nodes. This causes the solutions for the same {@link SidIV} to be * "grouped". * * @see <a href="https://sourceforge.net/apps/trac/bigdata/ticket/505"> * Exception when using SPARQL sort & statement identifiers </a> */ @SuppressWarnings({ "unchecked", "rawtypes" }) private int compareSIDs(final IV leftSid, final IV rightSid) { return leftSid.compareTo(rightSid); } /** * Only difference here with Sesame ValueComparator is that we use * stringValue() instead of toString(). */ private int compareURIs(final URI leftURI, final URI rightURI) { return leftURI.stringValue().compareTo(rightURI.stringValue()); } private int compareLiterals( final IV<BigdataLiteral, ?> left, final IV<BigdataLiteral, ?> right) { /* * Only thing we need to special case are LiteralExtensionIVs, which * are used to model xsd:dateTime. */ if (left instanceof LiteralExtensionIV && right instanceof LiteralExtensionIV) { @SuppressWarnings("rawtypes") final IV leftDatatype = ((LiteralExtensionIV) left).getExtensionIV(); @SuppressWarnings("rawtypes") final IV rightDatatype = ((LiteralExtensionIV) right).getExtensionIV(); if (leftDatatype.equals(rightDatatype)) { return left.compareTo(right); } } return compareLiterals((Literal) left, (Literal) right); } /** * Taken directly from Sesame's ValueComparator, no modification. Handles * inlines nicely since they now implement the Literal interface. */ private int compareLiterals(final Literal leftLit, final Literal rightLit) { // Additional constraint for ORDER BY: "A plain literal is lower // than an RDF literal with type xsd:string of the same lexical // form." if (!QueryEvaluationUtil.isStringLiteral(leftLit) || !QueryEvaluationUtil.isStringLiteral(rightLit)) { try { boolean isSmaller = QueryEvaluationUtil.compareLiterals(leftLit, rightLit, CompareOp.LT); if (isSmaller) { return -1; } else { return 1; } } catch (ValueExprEvaluationException e) { // literals cannot be compared using the '<' operator, continue // below } } int result = 0; // Sort by datatype first, plain literals come before datatyped literals URI leftDatatype = leftLit.getDatatype(); URI rightDatatype = rightLit.getDatatype(); if (leftDatatype != null) { if (rightDatatype != null) { // Both literals have datatypes result = compareDatatypes(leftDatatype, rightDatatype); } else { result = 1; } } else if (rightDatatype != null) { result = -1; } if (result == 0) { // datatypes are equal or both literals are untyped; sort by language // tags, simple literals come before literals with language tags String leftLanguage = leftLit.getLanguage(); String rightLanguage = rightLit.getLanguage(); if (leftLanguage != null) { if (rightLanguage != null) { result = leftLanguage.compareTo(rightLanguage); } else { result = 1; } } else if (rightLanguage != null) { result = -1; } } if (result == 0) { // Literals are equal as fas as their datatypes and language tags are // concerned, compare their labels result = leftLit.getLabel().compareTo(rightLit.getLabel()); } return result; } /** * Taken directly from Sesame's ValueComparator, no modification. */ private int compareDatatypes(final URI leftDatatype, final URI rightDatatype) { if (XMLDatatypeUtil.isNumericDatatype(leftDatatype)) { if (XMLDatatypeUtil.isNumericDatatype(rightDatatype)) { // both are numeric datatypes return compareURIs(leftDatatype, rightDatatype); } else { return -1; } } else if (XMLDatatypeUtil.isNumericDatatype(rightDatatype)) { return 1; } else if (XMLDatatypeUtil.isCalendarDatatype(leftDatatype)) { if (XMLDatatypeUtil.isCalendarDatatype(rightDatatype)) { // both are calendar datatypes return compareURIs(leftDatatype, rightDatatype); } else { return -1; } } else if (XMLDatatypeUtil.isCalendarDatatype(rightDatatype)) { return 1; } else { // incompatible or unordered datatypes return compareURIs(leftDatatype, rightDatatype); } } }