/** * 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.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; import org.jrdf.graph.Node; import org.mulgara.query.QueryException; import org.mulgara.query.Variable; import org.mulgara.query.filter.Context; import org.mulgara.query.filter.ContextOwner; import org.mulgara.query.rdf.LiteralImpl; import org.mulgara.query.rdf.XSD; import org.mulgara.query.rdf.XSDAbbrev; /** * A literal with a URI type and a value. * * @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 class TypedLiteral extends AbstractComparableLiteral { /** Generated Serialization ID for RMI */ private static final long serialVersionUID = -6070455650703063913L; /** The logger */ private final static Logger logger = Logger.getLogger(TypedLiteral.class.getName()); /** The type URI for this literal */ private URI type; /** The lexical representation for this literal */ private String lexical; /** * Creates the value to wrap the string * @param value The data to wrap * @param type The type of the literal */ public TypedLiteral(Object value, URI type) { super(value); lexical = value.toString(); // lexical == value if value instanceof String this.type = type; } /** * A factory for typed literals. * @param value The literal data in its lexical form. This means it's a String, and can even be invalid * @param type The type of the literal. May be null. * @param lang The language code of the literal. May be null. */ public static ValueLiteral newLiteral(String value, URI type, String lang) throws QueryException { if (type != null) { // get the info registered for this type URI TypeInfo info = infoMap.get(type); if (info != null) return info.newLiteral(info.toData(value), normalizeType(type)); // no type info for the given URI, just pass through as a general typed literal return new TypedLiteral(value, type); } // no type info provided, so it's a simple string if (lang == null || lang.length() == 0) return new SimpleLiteral(value); return new SimpleLiteral(value, lang); } /** * A factory for typed literals from raw Java types. This is most likely to come * from literal numbers parsed by a SPARQL parser. * @param value The data as an object. May be a String, or some kind of {@link java.lang.Number} */ public static ValueLiteral newLiteral(Object value) throws QueryException { DataCompare dc = typeMap.get(value.getClass()); if (dc == null) { logger.info("Unrecognized data type: " + value.getClass().getSimpleName()); return new SimpleLiteral(value.toString()); } return dc.newLiteral(value); } /** * Gets the type of this literal * @return The URI for this literals type */ public IRI getType() { return new IRI(type); } /** @see org.mulgara.query.filter.value.ValueLiteral#isSimple() */ public boolean isSimple() throws QueryException { return false; } /** {@inheritDoc} */ public boolean isGrounded() throws QueryException { return true; } /** * Gets the language ID of this literal * @return The language ID for this literal */ public SimpleLiteral getLang() { return SimpleLiteral.EMPTY; } /** {@inheritDoc} */ public String getLexical() { return lexical; } /** * No context needed as this is a literal value. * @see org.mulgara.query.filter.RDFTerm#getContextOwner() */ public ContextOwner getContextOwner() { return null; } /** * A public string representation of this literal. */ public String toString() { return "'" + lexical + "'^^<" + type + ">"; } /** * A JRDF Node that represents this literal */ public Node getJRDFValue() throws QueryException { return new LiteralImpl(lexical, type); } /** * No context needed as this is a literal value. * @see org.mulgara.query.filter.RDFTerm#setContextOwner(org.mulgara.query.filter.ContextOwner) */ public void setContextOwner(ContextOwner owner) { } /** {@inheritDoc} */ public boolean test(Context context) throws QueryException { if (type == null) return ((String)value).length() != 0; TypeInfo test = infoMap.get(type); if (test == null) throw new QueryException("Type Error: no effective boolean value for: " + toString()); if (NumericLiteral.isNumeric(type)) { return (value instanceof Number) && test.ebv((Number)value); } return test.ebv(value.toString()); } /** @see org.mulgara.query.filter.RDFTerm#getVariables() */ public Set<Variable> getVariables() { return Collections.emptySet(); } /** * Converts abbreviated URIs for XSD types into the full URI. * @param type The URI of the datatype. * @return The full URI of the datatype. */ private static final URI normalizeType(URI type) { if (XSD.DOM.equals(type.getScheme())) { // fragments won't exist for xsd:.... type = URI.create(XSD.NAMESPACE + type.getRawSchemeSpecificPart()); } return type; } /////////////////////////////////////////////////////////////////////////////////// // Maps to data specific functionality, and supporting functions /////////////////////////////////////////////////////////////////////////////////// /** A map of XSD datatypes onto the data conversion operations for their types */ protected static Map<URI,TypeInfo> infoMap = new HashMap<URI,TypeInfo>(); /** This interface tests if datatype matches the data to give an EBV of <code>true</code> */ public interface TypeInfo { /** Returns an EBV of <code>true</code> iff the data matches the type sufficiently */ public boolean ebv(String data) throws QueryException; /** Returns an EBV of <code>true</code> iff the number is not zero */ public boolean ebv(Number v) throws QueryException; /** Returns data parsed out of the string literal */ public Object toData(String representation) throws QueryException; /** Returns the URI for this type */ public URI getTypeURI(); /** Returns the abbreviated URI for this type, in the absence of a namespace. */ public URI getTypeURI2(); /** * Creates a new ValueLiteral compatible for this data * @param data The data in the correct native {@link java.lang.Class} to be converted. * @param type The original datatype for the data. * @return A new literal containing the data. */ public ValueLiteral newLiteral(Object data, URI type); /** * Get the value of a given number, according to the type of the implementing class. * @param n The number to convert. * @return The new Number of the required type. */ public Number valueOf(Number n); } /** Simple extension to TypeInfo to store the type URI for all implementing classes */ private static abstract class AbstractXSD implements TypeInfo { private final URI typeURI; private final URI typeURI2; protected AbstractXSD(URI type, URI type2) { typeURI = type; typeURI2 = type2; } public URI getTypeURI() { return typeURI; } public URI getTypeURI2() { return typeURI2; } public Number valueOf(Number n) { throw new UnsupportedOperationException("Numeric casts only applicable to numbers"); } // public ValueLiteral newLiteral(Object data, URI type) { throw new UnsupportedOperationException(); } } /** * Helper method for static initialization * @param info The info to add to the infoMap */ private static void addDefaultTypeInfo(TypeInfo info) { infoMap.put(info.getTypeURI(), info); infoMap.put(info.getTypeURI2(), info); } // initialize the types static { addDefaultTypeInfo(XSDDecimal.INSTANCE); addDefaultTypeInfo(XSDString.INSTANCE); addDefaultTypeInfo(XSDBoolean.INSTANCE); addDefaultTypeInfo(XSDDouble.INSTANCE); addDefaultTypeInfo(XSDFloat.INSTANCE); addDefaultTypeInfo(XSDLong.INSTANCE); addDefaultTypeInfo(XSDInteger.INSTANCE); addDefaultTypeInfo(XSDShort.INSTANCE); addDefaultTypeInfo(XSDByte.INSTANCE); addDefaultTypeInfo(XSDDate.INSTANCE); infoMap.put(XSD.INTEGER_URI, XSDLong.INSTANCE); infoMap.put(XSD.NON_POSITIVE_INTEGER_URI, XSDLong.INSTANCE); infoMap.put(XSD.NEGATIVE_INTEGER_URI, XSDLong.INSTANCE); infoMap.put(XSD.NON_NEGATIVE_INTEGER_URI, XSDLong.INSTANCE); infoMap.put(XSD.POSITIVE_INTEGER_URI, XSDInteger.INSTANCE); infoMap.put(XSD.UNSIGNED_LONG_URI, XSDLong.INSTANCE); infoMap.put(XSD.UNSIGNED_INT_URI, XSDLong.INSTANCE); infoMap.put(XSD.UNSIGNED_SHORT_URI, XSDInteger.INSTANCE); infoMap.put(XSD.UNSIGNED_BYTE_URI, XSDShort.INSTANCE); infoMap.put(XSDAbbrev.INTEGER_URI, XSDLong.INSTANCE); infoMap.put(XSDAbbrev.NON_POSITIVE_INTEGER_URI, XSDLong.INSTANCE); infoMap.put(XSDAbbrev.NEGATIVE_INTEGER_URI, XSDLong.INSTANCE); infoMap.put(XSDAbbrev.NON_NEGATIVE_INTEGER_URI, XSDLong.INSTANCE); infoMap.put(XSDAbbrev.POSITIVE_INTEGER_URI, XSDInteger.INSTANCE); infoMap.put(XSDAbbrev.UNSIGNED_LONG_URI, XSDLong.INSTANCE); infoMap.put(XSDAbbrev.UNSIGNED_INT_URI, XSDLong.INSTANCE); infoMap.put(XSDAbbrev.UNSIGNED_SHORT_URI, XSDInteger.INSTANCE); infoMap.put(XSDAbbrev.UNSIGNED_BYTE_URI, XSDShort.INSTANCE); } ////////////////////////////////////////////////////////////////// // Implementing classes ////////////////////////////////////////////////////////////////// static class XSDString extends AbstractXSD { public static final XSDString INSTANCE = new XSDString(); private XSDString() { super(XSD.STRING_URI, XSDAbbrev.STRING_URI); } public boolean ebv(String data) { return data != null && data.length() != 0; } public boolean ebv(Number data) { throw new IllegalArgumentException("Found a number in a string operation"); } public Object toData(String r) { return r; } public ValueLiteral newLiteral(Object data, URI type) { return new TypedLiteral((String)data, getTypeURI()); } } private static class XSDBoolean extends AbstractXSD { public static final XSDBoolean INSTANCE = new XSDBoolean(); private XSDBoolean() { super(XSD.BOOLEAN_URI, XSDAbbrev.BOOLEAN_URI); } public boolean ebv(String data) { return Boolean.parseBoolean(data); } public boolean ebv(Number data) { throw new IllegalArgumentException("Found a number in a boolean operation"); } public Object toData(String r) { return Boolean.parseBoolean(r); } public ValueLiteral newLiteral(Object data, URI type) { return new Bool((Boolean)data); } } private static class XSDDecimal extends AbstractXSD { public static final XSDDecimal INSTANCE = new XSDDecimal(); private XSDDecimal() { super(XSD.DECIMAL_URI, XSDAbbrev.DECIMAL_URI); } public boolean ebv(String data) { try { if (data == null) return false; BigDecimal d = new BigDecimal(data); return !BigDecimal.ZERO.equals(d); } catch (NumberFormatException nfe) { return false; } } public boolean ebv(Number data) { return !BigDecimal.ZERO.equals(((BigDecimal)data)); } public Object toData(String r) throws QueryException { try { return new BigDecimal(r); } catch (NumberFormatException nfe) { throw new QueryException("Type Error: Cannot convert to a Decimal: " + r); } } public ValueLiteral newLiteral(Object data, URI type) { if (data instanceof Double) return new NumericLiteral((Double)data, type); if (data instanceof Long) return new NumericLiteral((Long)data, type); return new NumericLiteral((BigDecimal)data, type); } public Number valueOf(Number n) { return n.doubleValue(); } } private static class XSDDouble extends AbstractXSD { public static final XSDDouble INSTANCE = new XSDDouble(); private XSDDouble() { super(XSD.DOUBLE_URI, XSDAbbrev.DOUBLE_URI); } public boolean ebv(String data) { try { if (data == null) return false; Double d = Double.parseDouble(data); return 0 != Double.parseDouble(data) && !d.isNaN(); } catch (NumberFormatException nfe) { return false; } } public boolean ebv(Number data) { return data.doubleValue() != 0.0D; } public Object toData(String r) throws QueryException { try { return Double.parseDouble(r); } catch (NumberFormatException nfe) { throw new QueryException("Type Error: Cannot convert to a Double: " + r); } } public ValueLiteral newLiteral(Object data, URI type) { return new NumericLiteral((Double)data, type); } public Number valueOf(Number n) { return n.doubleValue(); } } private static class XSDFloat extends AbstractXSD { public static final XSDFloat INSTANCE = new XSDFloat(); private XSDFloat() { super(XSD.FLOAT_URI, XSDAbbrev.FLOAT_URI); } public boolean ebv(String data) { try { if (data == null) return false; Float f = Float.parseFloat(data); return 0 != f && !f.isNaN(); } catch (NumberFormatException nfe) { return false; } } public boolean ebv(Number data) { return data.floatValue() != 0.0; } public Object toData(String r) throws QueryException { try { return Float.parseFloat(r); } catch (NumberFormatException nfe) { throw new QueryException("Type Error: Cannot convert to a Float: " + r); } } public ValueLiteral newLiteral(Object data, URI type) { return new NumericLiteral((Float)data, type); } public Number valueOf(Number n) { return n.floatValue(); } } private static class XSDLong extends AbstractXSD { public static final XSDLong INSTANCE = new XSDLong(); private XSDLong() { super(XSD.LONG_URI, XSDAbbrev.LONG_URI); } public boolean ebv(String data) { try { return data != null && 0 != Long.parseLong(data); } catch (NumberFormatException nfe) { return false; } } public boolean ebv(Number data) { return data.longValue() != 0L; } public Object toData(String r) throws QueryException { try { return Long.parseLong(r); } catch (NumberFormatException nfe) { throw new QueryException("Type Error: Cannot convert to a Long: " + r); } } public ValueLiteral newLiteral(Object data, URI type) { return new NumericLiteral((Long)data, type); } public Number valueOf(Number n) { return n.longValue(); } } private static class XSDInteger extends AbstractXSD { public static final XSDInteger INSTANCE = new XSDInteger(); private XSDInteger() { super(XSD.INT_URI, XSDAbbrev.INT_URI); } public boolean ebv(String data) { try { return data != null && 0 != Integer.parseInt(data); } catch (NumberFormatException nfe) { return false; } } public boolean ebv(Number data) { return data.intValue() != 0; } public Object toData(String r) throws QueryException { try { return Integer.parseInt(r); } catch (NumberFormatException nfe) { throw new QueryException("Type Error: Cannot convert to an Integer: " + r); } } public ValueLiteral newLiteral(Object data, URI type) { return new NumericLiteral((Integer)data, type); } public Number valueOf(Number n) { return n.intValue(); } } private static class XSDShort extends AbstractXSD { public static final XSDShort INSTANCE = new XSDShort(); private XSDShort() { super(XSD.SHORT_URI, XSDAbbrev.SHORT_URI); } public boolean ebv(String data) { try { return data != null && 0 != Short.parseShort(data); } catch (NumberFormatException nfe) { return false; } } public boolean ebv(Number data) { return data.shortValue() != 0; } public Object toData(String r) throws QueryException { try { return Short.parseShort(r); } catch (NumberFormatException nfe) { throw new QueryException("Type Error: Cannot convert to a Short: " + r); } } public ValueLiteral newLiteral(Object data, URI type) { return new NumericLiteral((Short)data, type); } public Number valueOf(Number n) { return n.shortValue(); } } private static class XSDByte extends AbstractXSD { public static final XSDByte INSTANCE = new XSDByte(); private XSDByte() { super(XSD.BYTE_URI, XSDAbbrev.BYTE_URI); } public boolean ebv(String data) { try { return data != null && 0 != Byte.parseByte(data); } catch (NumberFormatException nfe) { return false; } } public boolean ebv(Number data) { return data.byteValue() != 0; } public Object toData(String r) throws QueryException { try { return Byte.parseByte(r); } catch (NumberFormatException nfe) { throw new QueryException("Type Error: Cannot convert to a Byte: " + r); } } public ValueLiteral newLiteral(Object data, URI type) { return new NumericLiteral((Byte)data, type); } public Number valueOf(Number n) { return n.byteValue(); } } private static class XSDDate extends AbstractXSD { public static final XSDDate INSTANCE = new XSDDate(); private XSDDate() { super(XSD.DATE_TIME_URI, XSDAbbrev.DATE_TIME_URI); } public boolean ebv(String data) throws QueryException { throw new QueryException("Unable to convert a date to a boolean"); } public boolean ebv(Number data) throws QueryException { throw new QueryException("Unable to convert a date to a boolean"); } public Object toData(String r) { return DateTime.parseDate(r); } public ValueLiteral newLiteral(Object data, URI type) { return new DateTime((Date)data); } } }