/**
* The contents of this file are subject to the Open Software License
* Version 3.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.opensource.org/licenses/osl-3.0.txt
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*/
package org.mulgara.query.filter.value;
import java.math.BigDecimal;
import java.net.URI;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.mulgara.query.QueryException;
import org.mulgara.query.filter.RDFTerm;
import org.mulgara.query.rdf.XSD;
/**
* Basic common representation of literals.
*
* @created Mar 7, 2008
* @author Paula Gearon
* @copyright © 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
* @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
*/
public abstract class AbstractComparableLiteral extends AbstractComparable implements ValueLiteral {
/** Serialization ID */
private static final long serialVersionUID = 8196102019234790023L;
/** The wrapped value */
Object value;
/**
* Creates a value
* @param value The value to use
*/
public AbstractComparableLiteral(Object value) {
this.value = value;
}
/**
* Returns the wrapped data for this value.
* @return The wrapped data.
*/
public Object getValue() {
return value;
}
/**
* {@inheritDoc}
* Override this if a tagged string.
*/
public SimpleLiteral getLang() throws QueryException {
return SimpleLiteral.EMPTY;
}
/** {@inheritDoc} */
public boolean isBlank() { return false; }
/** {@inheritDoc} */
public boolean isIRI() { return false; }
/** {@inheritDoc} */
public boolean isURI() { return false; }
/** {@inheritDoc} */
public boolean isLiteral() { return true; }
/** {@inheritDoc} */
public boolean sameTerm(RDFTerm v) throws QueryException {
if (!v.isLiteral()) return false;
return comparableLiteralTypes((ValueLiteral)v) && getValue().equals(v.getValue());
}
/**
* {@inheritDoc}
* This method will only return <code>true</code> when the elements are identical.
* Since this object is a literal, then an incorrect comparison will throw an exception.
* See: <a href="http://www.w3.org/TR/rdf-sparql-query/#func-RDFterm-equal">http://www.w3.org/TR/rdf-sparql-query/#func-RDFterm-equal</a>
* <em>produces a type error if the arguments are both literal but are not the same RDF term</em>
*/
public boolean equals(RDFTerm v) throws QueryException {
if (!v.isLiteral()) return false;
// numbers are compared differently
if (isNumber(v) && isNumber(this)) return numericEquals(v);
// simple strings can do the comparison for us. This object cannot be simple,
// otherwise this method would not have been called.
if (((ValueLiteral)v).isSimple()) return v.equals(this);
// if the types allow for direct comparisons, then do so
if (directlyComparableTypes(v)) return getValue().equals(v.getValue());
// This is now RDFterm-equal, return true or an error
// compare for type equality, then value equality
if (comparableLiteralTypes(v) && getValue().equals(v.getValue())) return true;
throw new QueryException("Type Error: Terms are not equal");
}
/**
* Indicates that an direct compare operation is valid on these types. This is defined
* to be valid for:
* <ul>
* <li>booleans</li>
* <li>dateTimes</li>
* <li>XSD strings</li>
* </ul>
* Simple strings and numbers are handled elsewhere.
* @param term The other value literal to be compared against.
* @return <code>true</code> if direct comparison is allowed between these types.
* @throws QueryException If there is a data error accessing the types.
*/
private boolean directlyComparableTypes(RDFTerm term) throws QueryException {
ValueLiteral vl = (ValueLiteral)term;
IRI otherType = vl.getType();
IRI thisType = getType();
assert otherType != null && thisType != null;
// if differing types, then can't be compared
if (!thisType.equals(otherType)) return false;
URI tt = thisType.getValue();
return tt.equals(XSD.BOOLEAN_URI) || tt.equals(XSD.DATE_TIME_URI) || thisType.equals(SimpleLiteral.STRING_TYPE);
}
/**
* Compares the type of this object to the type of another object. This takes into account
* that Simple Literals claim to have a string type, when they have no type at all.
* @param term The object to test.
* @return <code>true</code> if the types are exactly the same. If both types are strings,
* then both objects have to be typed literals, or untyped literals.
* @throws QueryException If there is an error accessing the type data.
*/
private boolean comparableLiteralTypes(RDFTerm term) throws QueryException {
ValueLiteral vl = (ValueLiteral)term;
IRI opType = vl.getType();
IRI thisType = getType();
assert opType != null && thisType != null;
// if the types differ, then the literals are definitely not equal
return opType.equals(thisType);
}
/**
* Extended numerical comparison function. Currently unused.
* @param v The term to compare against.
* @return <code>true</code> if this compares against v with semantic equivalence, regardless of lexical equivalence
* @throws QueryException Thrown when a value cannot be resolved, or if the types are no numbers.
*/
private boolean numericEquals(RDFTerm v) throws QueryException {
Object ov = v.getValue();
if (!(value instanceof Number) || !(ov instanceof Number)) throw new QueryException("Terms are not equal");
return compare(value, ov) == 0;
}
/**
* Type-based switching to handle comparison of things that may not be directly comparable.
* @param left The first thing to compare
* @param right The second thing to compare
* @return -1 if left<right, +1 if left>right, and 0 if left=right
* @throws QueryException The data could not be compared.
*/
protected int compare(Object left, Object right) throws QueryException {
DataCompare cmpFn = typeMap.get(left.getClass());
if (cmpFn == null) throw new QueryException("Type Error: Cannot compare a " + left.getClass() + " to a " + right.getClass());
return cmpFn.compare(left, right);
}
/**
* Utility to test a literal to see if it is a numeric type. Accepts an RDFTerm for convenience.
* @param t A ValueLiteral to test to see if it is a number.
* @return <code>true</code> if the term is a number.
* @throws QueryException If there was an error accessing the value of the term.
*/
private static final boolean isNumber(RDFTerm t) throws QueryException {
return NumericLiteral.isNumeric(((ValueLiteral)t).getType().getValue());
}
/** Map of class types to the functions used to compare those types */
protected static Map<Class<? extends Comparable<?>>,DataCompare> typeMap = new HashMap<Class<? extends Comparable<?>>,DataCompare>();
static {
typeMap.put(String.class, new StringCompare());
typeMap.put(Date.class, new DateCompare());
typeMap.put(Boolean.class, new BooleanCompare());
typeMap.put(Float.class, new FloatCompare());
typeMap.put(Double.class, new DoubleCompare());
typeMap.put(Long.class, new IntegralCompare());
typeMap.put(Integer.class, new IntegralCompare());
typeMap.put(Short.class, new IntegralCompare());
typeMap.put(Byte.class, new IntegralCompare());
typeMap.put(BigDecimal.class, new BigDecimalCompare());
}
/** Defines a function for comparing objects of arbitrary type */
protected interface DataCompare {
/**
* Comparison method used for any kind of type that might be compared.
* @param left The left hand side of the comparison. This must be of the correct type for the class.
* @param right The right hand side of the comparison. This should be tested for type compatibility with the left parameter
* @return -1 if left<right, 1 if left>right, and 0 if left==right
* @throws QueryException Due to an error in resolution of the values to be compared
*/
int compare(Object left, Object right) throws QueryException;
/**
* Creates a new ValueLiteral compatible with this comparison type, from given data
* @param data The data in the correct native {@link java.lang.Class} to be converted.
* @return A new literal containing the data.
*/
ValueLiteral newLiteral(Object data);
}
/** Implements string comparisons */
private static class StringCompare implements DataCompare {
public int compare(Object left, Object right) throws QueryException {
if (!(right instanceof String)) throw new QueryException("Type Error: Cannot compare a String to a: " + right.getClass());
return ((String)left).compareTo((String)right);
}
public ValueLiteral newLiteral(Object data) { return new TypedLiteral((String)data, SimpleLiteral.STRING_TYPE.getValue()); }
}
/** Implements string comparisons */
private static class DateCompare implements DataCompare {
public int compare(Object left, Object right) throws QueryException {
if (!(right instanceof Date)) throw new QueryException("Type Error: Cannot compare a Date to a: " + right.getClass());
return ((Date)left).compareTo((Date)right);
}
public ValueLiteral newLiteral(Object data) { return new DateTime((Date)data); }
}
/** Implements boolean comparisons */
private static class BooleanCompare implements DataCompare {
public int compare(Object left, Object right) throws QueryException {
if (!(right instanceof Boolean)) throw new QueryException("Type Error: Cannot compare a boolean to a: " + right.getClass());
return ((Boolean)left).compareTo((Boolean)right);
}
public ValueLiteral newLiteral(Object data) { return new Bool((Boolean)data); }
}
/** Implements floating point comparisons, or double comparisons if the rhs parameter is a double */
private static class FloatCompare implements DataCompare {
public int compare(Object left, Object right) throws QueryException {
Float fleft = (Float)left;
if (!(right instanceof Number)) throw new QueryException("Type Error: Cannot compare a float to a: " + right.getClass());
// if right has more precision, then promote lfloat, and compare the other way around
if (right instanceof Double) return -((Double)right).compareTo(fleft.doubleValue());
return fleft.compareTo(((Number)right).floatValue());
}
public ValueLiteral newLiteral(Object data) { return new NumericLiteral((Float)data); }
}
/** Implements double precision floating point comparisons */
private static class DoubleCompare implements DataCompare {
public int compare(Object left, Object right) throws QueryException {
if (!(right instanceof Number)) throw new QueryException("Type Error: Cannot compare a double to a: " + right.getClass());
return ((Double)left).compareTo(((Number)right).doubleValue());
}
public ValueLiteral newLiteral(Object data) { return new NumericLiteral((Double)data); }
}
/** Implements integer comparisons */
private static class IntegralCompare implements DataCompare {
public int compare(Object left, Object right) throws QueryException {
if (!(right instanceof Number)) throw new QueryException("Type Error: Cannot compare a decimal number to a: " + right.getClass());
Long lleft = ((Number)left).longValue();
return lleft.compareTo(((Number)right).longValue());
}
public ValueLiteral newLiteral(Object data) { return new NumericLiteral((Number)data); }
}
/** Implements big-decimal comparisons */
private static class BigDecimalCompare implements DataCompare {
public int compare(Object left, Object right) throws QueryException {
if (!(right instanceof Number)) throw new QueryException("Type Error: Cannot compare a decimal number to a: " + right.getClass());
BigDecimal bleft = (BigDecimal)left;
if (right instanceof BigDecimal) return bleft.compareTo((BigDecimal)right);
if (right instanceof Double || right instanceof Float) {
return bleft.compareTo(BigDecimal.valueOf(((Number)right).doubleValue()));
}
return bleft.compareTo(BigDecimal.valueOf(((Number)right).longValue()));
}
public ValueLiteral newLiteral(Object data) { return new NumericLiteral((Number)data); }
}
}