/*******************************************************************************
* Copyright (c) 2004, 2007 IBM Corporation and Cambridge Semantics Incorporated.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* File: $Source: /cvsroot/slrp/glitter/com.ibm.adtech.glitter/src/com/ibm/adtech/glitter/expression/FunctionBase.java,v $
* Created by: Lee Feigenbaum ( <a href="mailto:feigenbl@us.ibm.com">feigenbl@us.ibm.com </a>)
* Created on: 10/23/2006
* Revision: $Id: FunctionBase.java 164 2007-07-31 14:11:09Z mroy $
*
* Contributors:
* IBM Corporation - initial API and implementation
* Cambridge Semantics Incorporated - Fork to Anzo
*******************************************************************************/
package org.openanzo.glitter.expression;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.XMLGregorianCalendar;
import org.openanzo.glitter.exception.ExpressionEvaluationException;
import org.openanzo.glitter.exception.IncompatibleTypeException;
import org.openanzo.glitter.exception.MalformedLiteralException;
import org.openanzo.glitter.util.PolymorphicNumber;
import org.openanzo.glitter.util.TypeConversions;
import org.openanzo.rdf.Literal;
import org.openanzo.rdf.PlainLiteral;
import org.openanzo.rdf.TypedLiteral;
import org.openanzo.rdf.URI;
import org.openanzo.rdf.Value;
import org.openanzo.rdf.datatype.TypedValueMapper;
import org.openanzo.rdf.vocabulary.XMLSchema;
/**
* Base class for SPARQL functions. Provides a standard configuration for {@link #operatesOnValues()} (<tt>true</tt>) and for {@link #operatesOnTypeErrors()} (
* <tt>false</tt>). Also provides a set of utility methods to perform -1/0/1 comparison functions on {@link Value}s for booleans, numerics, datetimes, and
* strings.
*
* @author lee <lee@cambridgesemantics.com>
*
*/
public abstract class ScalarFunctionBase implements ScalarFunction {
/** Comparison results */
static public enum ComparisonResult {
/** Equal */
EQUAL,
/** Greater */
GREATER,
/** Lesser */
LESSER,
/** Indeterminate */
INDETERMINATE;
/**
* Converts DatatypeContants.GREATER into GREATER, DatatypeContants.EQUAL in EQUAL, DatatypeContants.LESSER into LESSER, and
* DatatypeContants.INDETERMINATE into INDETERMINATE
*
* @param c
* @return
*/
protected static ComparisonResult fromDatatypeConstant(int c) {
switch (c) {
case DatatypeConstants.EQUAL:
return EQUAL;
case DatatypeConstants.GREATER:
return GREATER;
case DatatypeConstants.LESSER:
return LESSER;
case DatatypeConstants.INDETERMINATE:
return INDETERMINATE;
default:
return null;
}
}
/**
* Convert standard compare results to enum
*
* @param i
* results from compare
* @return enum version of comparison
*/
public static ComparisonResult fromStandardCompareValue(int i) {
if (i < 0)
return LESSER;
if (i == 0)
return EQUAL;
return GREATER;
}
}
public boolean operatesOnValues() {
return true;
}
public boolean operatesOnTypeErrors() {
return false;
}
/**
* Compares two numeric RDF terms
*
* @param arg1
* First term
* @param arg2
* Second term
* @return -1 if <tt>arg1 < arg2</tt>; 0 if <tt>arg1 == arg2</tt>; 1 if <tt>arg1 > arg2</tt>
* @throws ExpressionEvaluationException
* If either argument is not numeric
*/
static private ComparisonResult compareNumerics(Value arg1, Value arg2) {
if (!TypeConversions.isNumeric(arg1))
return ComparisonResult.INDETERMINATE;
if (!TypeConversions.isNumeric(arg2))
return ComparisonResult.INDETERMINATE;
try {
return ComparisonResult.fromStandardCompareValue(new PolymorphicNumber(arg1).compareTo(new PolymorphicNumber(arg2)));
} catch (IllegalArgumentException iae) {
throw new MalformedLiteralException(arg1, "numeric values");
}
}
/**
* Compares two boolean RDF terms
*
* @param arg1
* First term
* @param arg2
* Second term
* @return -1 if <tt>arg1</tt> is <tt>false</tt> and <tt>arg2</tt> is <tt>true</tt>; 1 if the other way around; 0 if both terms are the same boolean value
* @throws ExpressionEvaluationException
* if either arguments is not a boolean
*/
static private ComparisonResult compareBooleans(Value arg1, Value arg2) {
try {
return compareBooleans(arg1, arg2, false);
} catch (IncompatibleTypeException ite) {
return ComparisonResult.INDETERMINATE;
}
}
/**
* Compares two boolean RDF terms
*
* @param arg1
* First term
* @param arg2
* Second term
* @param convert
* Should the effective boolean value rules be applied to derive boolean values from non-boolean arguments?
* @return -1 if <tt>arg1</tt> is <tt>false</tt> and <tt>arg2</tt> is <tt>true</tt>; 1 if the other way around; 0 if both terms are the same boolean value
* @throws ExpressionEvaluationException
* if either arguments is not a boolean and either <tt>convert</tt> is <tt>false</tt> or there are no rules for finding the EBV of an argument
*/
static private ComparisonResult compareBooleans(Value arg1, Value arg2, boolean convert) {
boolean b1, b2;
if (convert) {
b1 = TypeConversions.effectiveBooleanValue(arg1);
b2 = TypeConversions.effectiveBooleanValue(arg2);
} else {
b1 = TypeConversions.booleanValue(arg1);
b2 = TypeConversions.booleanValue(arg2);
}
return ComparisonResult.fromStandardCompareValue((!b1 && b2) ? -1 : ((b1 && !b2) ? 1 : 0));
}
/**
* Compares two datetime RDF terms as defined in section 3.2.7.4 of http://www.w3.org/TR/xpath-functions/#xmlschema-2. Uses the
* {@link XMLGregorianCalendar#compare(XMLGregorianCalendar)} method to perform the comparison. Note that the result is not always greater than, less than,
* or equal. It can also be indeterminate.
*
* @param arg1
* First term
* @param arg2
* Second term
* @return The relationship between arg1 and arg2 as DatatypeConstants.LESSER, DatatypeConstants.EQUAL, DatatypeConstants.GREATER or
* DatatypeConstants.INDETERMINATE.
*
* @throws ExpressionEvaluationException
* if either arguments is not a <tt>xsd:dateTime</tt> or one of the arguments can't be parsed into a date/time.
*/
static private ComparisonResult compareDateTimes(Value arg1, Value arg2) {
if (!(arg1 instanceof TypedLiteral))
return ComparisonResult.INDETERMINATE;
if (!(arg2 instanceof TypedLiteral))
return ComparisonResult.INDETERMINATE;
URI dt1 = ((TypedLiteral) arg1).getDatatypeURI();
URI dt2 = ((TypedLiteral) arg2).getDatatypeURI();
Class<?> class1 = TypedValueMapper.getNativeClass(dt1);
if (!XMLGregorianCalendar.class.equals(class1))
return ComparisonResult.INDETERMINATE;
Class<?> class2 = TypedValueMapper.getNativeClass(dt2);
if (!XMLGregorianCalendar.class.equals(class2))
return ComparisonResult.INDETERMINATE;
XMLGregorianCalendar cal1;
try {
cal1 = (XMLGregorianCalendar) ((TypedLiteral) arg1).getNativeValue();
} catch (IllegalArgumentException iae) {
throw new MalformedLiteralException(arg1, XMLSchema.DATETIME.toString());
}
XMLGregorianCalendar cal2;
try {
cal2 = (XMLGregorianCalendar) ((TypedLiteral) arg2).getNativeValue();
} catch (IllegalArgumentException iae) {
throw new MalformedLiteralException(arg2, XMLSchema.DATETIME.toString());
}
if (cal1 == null)
throw new MalformedLiteralException(arg1, XMLSchema.DATETIME.toString());
if (cal2 == null)
throw new MalformedLiteralException(arg2, XMLSchema.DATETIME.toString());
return ComparisonResult.fromDatatypeConstant(cal1.compare(cal2));
}
/**
* Compares two string RDF terms
*
* @param arg1
* First term
* @param arg2
* Second term
* @return -1 if <tt>arg1 < arg2</tt>; 0 if <tt>arg1 == arg2</tt>; 1 if <tt>arg1 > arg2</tt>
* @throws ExpressionEvaluationException
* if either argument is not an <tt>xsd:string</tt> or a plain literal with no language tag
*/
static private ComparisonResult compareStringLiterals(Value arg1, Value arg2) {
if (!(arg1 instanceof Literal))
return ComparisonResult.INDETERMINATE;
if (!(arg2 instanceof Literal))
return ComparisonResult.INDETERMINATE;
Literal lit1 = (Literal) arg1, lit2 = (Literal) arg2;
if ((lit1 instanceof PlainLiteral && ((PlainLiteral) lit1).hasLanguage()) && lit2 instanceof PlainLiteral && ((PlainLiteral) lit2).hasLanguage())
return compareStringLiteralsWithLanguage((PlainLiteral) lit1, (PlainLiteral) lit2);
if ((lit1 instanceof PlainLiteral && ((PlainLiteral) lit1).hasLanguage()) || (lit1 instanceof TypedLiteral && !((TypedLiteral) lit1).getDatatypeURI().equals(XMLSchema.STRING)))
return ComparisonResult.INDETERMINATE;
if ((lit2 instanceof PlainLiteral && ((PlainLiteral) lit2).hasLanguage()) || (lit2 instanceof TypedLiteral && !((TypedLiteral) lit2).getDatatypeURI().equals(XMLSchema.STRING)))
return ComparisonResult.INDETERMINATE;
String s1 = lit1.getLabel(), s2 = lit2.getLabel();
// TODO - I'm not positive that this correctly implements the codepoint collation
// required by SPARQL (by proxy of XPath). But I think it does. :)
return ComparisonResult.fromStandardCompareValue(s1.compareTo(s2));
}
static private ComparisonResult compareStringLiteralsWithLanguage(PlainLiteral lit1, PlainLiteral lit2) {
if (lit1.getLanguage().equalsIgnoreCase(lit2.getLabel()))
return ComparisonResult.INDETERMINATE;
return ComparisonResult.fromStandardCompareValue(lit1.getLabel().compareTo(lit2.getLabel()));
}
/**
* Determine if 2 terms are equal
*
* @param v1
* value 1
* @param v2
* value 2
* @return comparison of 2 values
*/
static public boolean rdfTermEquals(Value v1, Value v2) {
return v1.equals(v2);
}
/**
* Compare literal values
*
* @param v1
* value 1
* @param v2
* value 2
* @return comparison of 2 values
* @throws ExpressionEvaluationException
*/
static public ComparisonResult compareLiteralValues(Value v1, Value v2) {
boolean bothTyped = (v1 instanceof TypedLiteral && v2 instanceof TypedLiteral);
// @@ how do we know what error is most appropriate if these all fail?
ComparisonResult result = ComparisonResult.INDETERMINATE;
if (bothTyped) {
result = compareNumerics(v1, v2);
if (result != ComparisonResult.INDETERMINATE) {
return result;
}
}
result = compareStringLiterals(v1, v2);
if (result != ComparisonResult.INDETERMINATE) {
return result;
}
if (bothTyped) {
result = compareBooleans(v1, v2);
if (result != ComparisonResult.INDETERMINATE) {
return result;
}
result = compareDateTimes(v1, v2);
if (result != ComparisonResult.INDETERMINATE) {
return result;
}
}
return ComparisonResult.INDETERMINATE;
}
@Override
public String toString() {
return getIdentifier().getLocalName();
}
}