/*
* Copyright Aduna (http://www.aduna-software.com/) (c) 2007.
*
* Licensed under the Aduna BSD-style license.
*/
package org.openrdf.query.algebra.evaluation.util;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.XMLGregorianCalendar;
import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.datatypes.XMLDatatypeUtil;
import org.openrdf.model.vocabulary.XMLSchema;
import org.openrdf.query.algebra.Compare.CompareOp;
import org.openrdf.query.algebra.evaluation.ValueExprEvaluationException;
/**
* @author Arjohn Kampman
*/
public class QueryEvaluationUtil {
/**
* Determines the effective boolean value (EBV) of the supplied value as
* defined in the <a href="http://www.w3.org/TR/rdf-sparql-query/#ebv">SPARQL
* specification</a>:
* <ul>
* <li>The EBV of any literal whose type is xsd:boolean or numeric is false
* if the lexical form is not valid for that datatype (e.g.
* "abc"^^xsd:integer).
* <li>If the argument is a typed literal with a datatype of xsd:boolean, the
* EBV is the value of that argument.
* <li>If the argument is a plain literal or a typed literal with a datatype
* of xsd:string, the EBV is false if the operand value has zero length;
* otherwise the EBV is true.
* <li>If the argument is a numeric type or a typed literal with a datatype
* derived from a numeric type, the EBV is false if the operand value is NaN
* or is numerically equal to zero; otherwise the EBV is true.
* <li>All other arguments, including unbound arguments, produce a type
* error.
* </ul>
*
* @param value
* Some value.
* @return The EBV of <tt>value</tt>.
* @throws ValueExprEvaluationException
* In case the application of the EBV algorithm results in a type
* error.
*/
public static boolean getEffectiveBooleanValue(Value value)
throws ValueExprEvaluationException
{
if (value instanceof Literal) {
Literal literal = (Literal)value;
String label = literal.getLabel();
URI datatype = literal.getDatatype();
if (datatype == null || datatype.equals(XMLSchema.STRING)) {
return label.length() > 0;
}
else if (datatype.equals(XMLSchema.BOOLEAN)) {
if ("true".equals(label) || "1".equals(label)) {
return true;
}
else {
// also false for illegal values
return false;
}
}
else if (datatype.equals(XMLSchema.DECIMAL)) {
try {
String normDec = XMLDatatypeUtil.normalizeDecimal(label);
return !normDec.equals("0.0");
}
catch (IllegalArgumentException e) {
return false;
}
}
else if (XMLDatatypeUtil.isIntegerDatatype(datatype)) {
try {
String normInt = XMLDatatypeUtil.normalize(label, datatype);
return !normInt.equals("0");
}
catch (IllegalArgumentException e) {
return false;
}
}
else if (XMLDatatypeUtil.isFloatingPointDatatype(datatype)) {
try {
String normFP = XMLDatatypeUtil.normalize(label, datatype);
return !normFP.equals("0.0E0") && !normFP.equals("NaN");
}
catch (IllegalArgumentException e) {
return false;
}
}
}
throw new ValueExprEvaluationException();
}
public static boolean compare(Value leftVal, Value rightVal, CompareOp operator)
throws ValueExprEvaluationException
{
if (leftVal instanceof Literal && rightVal instanceof Literal) {
// Both left and right argument is a Literal
return compareLiterals((Literal)leftVal, (Literal)rightVal, operator);
}
else {
// All other value combinations
switch (operator) {
case EQ:
return valuesEqual(leftVal, rightVal);
case NE:
return !valuesEqual(leftVal, rightVal);
default:
throw new ValueExprEvaluationException(
"Only literals with compatible, ordered datatypes can be compared using <, <=, > and >= operators");
}
}
}
private static boolean valuesEqual(Value leftVal, Value rightVal) {
return leftVal != null && rightVal != null && leftVal.equals(rightVal);
}
public static boolean compareLiterals(Literal leftLit, Literal rightLit, CompareOp operator)
throws ValueExprEvaluationException
{
// type precendence:
// - simple literal
// - numeric
// - xsd:boolean
// - xsd:dateTime
// - xsd:string
// - RDF term (equal and unequal only)
URI leftDatatype = leftLit.getDatatype();
URI rightDatatype = rightLit.getDatatype();
Integer compareResult = null;
if (QueryEvaluationUtil.isStringLiteral(leftLit) && QueryEvaluationUtil.isStringLiteral(rightLit)) {
compareResult = leftLit.getLabel().compareTo(rightLit.getLabel());
}
else if (leftDatatype != null && rightDatatype != null) {
URI commonDatatype = null;
if (leftDatatype.equals(rightDatatype)) {
commonDatatype = leftDatatype;
}
else if (XMLDatatypeUtil.isNumericDatatype(leftDatatype)
&& XMLDatatypeUtil.isNumericDatatype(rightDatatype))
{
// left and right arguments have different datatypes, try to find a
// more general, shared datatype
if (leftDatatype.equals(XMLSchema.DOUBLE) || rightDatatype.equals(XMLSchema.DOUBLE)) {
commonDatatype = XMLSchema.DOUBLE;
}
else if (leftDatatype.equals(XMLSchema.FLOAT) || rightDatatype.equals(XMLSchema.FLOAT)) {
commonDatatype = XMLSchema.FLOAT;
}
else if (leftDatatype.equals(XMLSchema.DECIMAL) || rightDatatype.equals(XMLSchema.DECIMAL)) {
commonDatatype = XMLSchema.DECIMAL;
}
else {
commonDatatype = XMLSchema.INTEGER;
}
}
if (commonDatatype != null) {
try {
if (commonDatatype.equals(XMLSchema.DOUBLE)) {
compareResult = Double.compare(leftLit.doubleValue(), rightLit.doubleValue());
}
else if (commonDatatype.equals(XMLSchema.FLOAT)) {
compareResult = Float.compare(leftLit.floatValue(), rightLit.floatValue());
}
else if (commonDatatype.equals(XMLSchema.DECIMAL)) {
compareResult = leftLit.decimalValue().compareTo(rightLit.decimalValue());
}
else if (XMLDatatypeUtil.isIntegerDatatype(commonDatatype)) {
compareResult = leftLit.integerValue().compareTo(rightLit.integerValue());
}
else if (commonDatatype.equals(XMLSchema.BOOLEAN)) {
Boolean leftBool = Boolean.valueOf(leftLit.booleanValue());
Boolean rightBool = Boolean.valueOf(rightLit.booleanValue());
compareResult = leftBool.compareTo(rightBool);
}
else if (XMLDatatypeUtil.isCalendarDatatype(commonDatatype)) {
XMLGregorianCalendar left = leftLit.calendarValue();
XMLGregorianCalendar right = rightLit.calendarValue();
compareResult = left.compare(right);
// Note: XMLGregorianCalendar.compare() returns compatible
// values
// (-1, 0, 1) but INDETERMINATE needs special treatment
if (compareResult == DatatypeConstants.INDETERMINATE) {
throw new ValueExprEvaluationException("Indeterminate result for date/time comparison");
}
}
else if (commonDatatype.equals(XMLSchema.STRING)) {
compareResult = leftLit.getLabel().compareTo(rightLit.getLabel());
}
}
catch (IllegalArgumentException e) {
// One of the basic-type method calls failed, try syntactic match
// before throwing an error
if (leftLit.equals(rightLit)) {
switch (operator) {
case EQ:
return true;
case NE:
return false;
}
}
throw new ValueExprEvaluationException(e);
}
}
}
if (compareResult != null) {
// Literals have compatible ordered datatypes
switch (operator) {
case LT:
return compareResult.intValue() < 0;
case LE:
return compareResult.intValue() <= 0;
case EQ:
return compareResult.intValue() == 0;
case NE:
return compareResult.intValue() != 0;
case GE:
return compareResult.intValue() >= 0;
case GT:
return compareResult.intValue() > 0;
default:
throw new IllegalArgumentException("Unknown operator: " + operator);
}
}
else {
// All other cases, e.g. literals with languages, unequal or
// unordered datatypes, etc. These arguments can only be compared
// using the operators 'EQ' and 'NE'. See SPARQL's RDFterm-equal
// operator
boolean literalsEqual = leftLit.equals(rightLit);
if (!literalsEqual) {
if (leftDatatype != null && rightDatatype != null
&& XMLDatatypeUtil.isCalendarDatatype(leftDatatype)
&& XMLDatatypeUtil.isCalendarDatatype(rightDatatype))
{
// left and right arguments have different date/time datatypes,
// these are always unequal
}
else if (leftDatatype != null && rightLit.getLanguage() == null || rightDatatype != null
&& leftLit.getLanguage() == null)
{
// For literals with unsupported datatypes we don't know if their
// values are equal
throw new ValueExprEvaluationException("Unable to compare literals with unsupported types");
}
}
switch (operator) {
case EQ:
return literalsEqual;
case NE:
return !literalsEqual;
case LT:
case LE:
case GE:
case GT:
throw new ValueExprEvaluationException(
"Only literals with compatible, ordered datatypes can be compared using <, <=, > and >= operators");
default:
throw new IllegalArgumentException("Unknown operator: " + operator);
}
}
}
/**
* Checks whether the supplied value is a "simple literal" as defined in the
* SPARQL spec. A "simple literal" is a literal without a language tag or a
* datatype.
*/
public static boolean isSimpleLiteral(Value v) {
if (v instanceof Literal) {
return isSimpleLiteral((Literal)v);
}
return false;
}
/**
* Checks whether the supplied literal is a "simple literal" as defined in
* the SPARQL spec. A "simple literal" is a literal without a language tag or
* a datatype.
*/
public static boolean isSimpleLiteral(Literal l) {
return l.getLanguage() == null && l.getDatatype() == null;
}
/**
* Checks whether the supplied literal is a "string literal". A "string
* literal" is either a {@link #isSimpleLiteral(Literal) simple literal} or a
* literal with datatype {@link XMLSchema#STRING xsd:string}.
*/
public static boolean isStringLiteral(Literal l) {
URI datatype = l.getDatatype();
if (datatype == null) {
return l.getLanguage() == null;
}
else {
return datatype.equals(XMLSchema.STRING);
}
}
}