001: /*
002: * Copyright Aduna (http://www.aduna-software.com/) (c) 2007.
003: *
004: * Licensed under the Aduna BSD-style license.
005: */
006: package org.openrdf.query.algebra.evaluation.util;
007:
008: import javax.xml.datatype.DatatypeConstants;
009: import javax.xml.datatype.XMLGregorianCalendar;
010:
011: import org.openrdf.model.Literal;
012: import org.openrdf.model.URI;
013: import org.openrdf.model.Value;
014: import org.openrdf.model.datatypes.XMLDatatypeUtil;
015: import org.openrdf.model.vocabulary.XMLSchema;
016: import org.openrdf.query.algebra.Compare.CompareOp;
017: import org.openrdf.query.algebra.evaluation.ValueExprEvaluationException;
018:
019: /**
020: * @author Arjohn Kampman
021: */
022: public class QueryEvaluationUtil {
023:
024: /**
025: * Determines the effective boolean value (EBV) of the supplied value as
026: * defined in the <a href="http://www.w3.org/TR/rdf-sparql-query/#ebv">SPARQL
027: * specification</a>:
028: * <ul>
029: * <li>The EBV of any literal whose type is xsd:boolean or numeric is false
030: * if the lexical form is not valid for that datatype (e.g.
031: * "abc"^^xsd:integer).
032: * <li>If the argument is a typed literal with a datatype of xsd:boolean,
033: * the EBV is the value of that argument.
034: * <li>If the argument is a plain literal or a typed literal with a datatype
035: * of xsd:string, the EBV is false if the operand value has zero length;
036: * otherwise the EBV is true.
037: * <li>If the argument is a numeric type or a typed literal with a datatype
038: * derived from a numeric type, the EBV is false if the operand value is NaN
039: * or is numerically equal to zero; otherwise the EBV is true.
040: * <li> All other arguments, including unbound arguments, produce a type
041: * error.
042: * </ul>
043: *
044: * @param value
045: * Some value.
046: * @return The EBV of <tt>value</tt>.
047: * @throws ValueExprEvaluationException
048: * In case the application of the EBV algorithm results in a type
049: * error.
050: */
051: public static boolean getEffectiveBooleanValue(Value value)
052: throws ValueExprEvaluationException {
053: if (value instanceof Literal) {
054: Literal literal = (Literal) value;
055: String label = literal.getLabel();
056: URI datatype = literal.getDatatype();
057:
058: if (datatype == null || datatype.equals(XMLSchema.STRING)) {
059: return label.length() > 0;
060: } else if (datatype.equals(XMLSchema.BOOLEAN)) {
061: if ("true".equals(label) || "1".equals(label)) {
062: return true;
063: } else {
064: // also false for illegal values
065: return false;
066: }
067: } else if (datatype.equals(XMLSchema.DECIMAL)) {
068: try {
069: String normDec = XMLDatatypeUtil
070: .normalizeDecimal(label);
071: return !normDec.equals("0.0");
072: } catch (IllegalArgumentException e) {
073: return false;
074: }
075: } else if (XMLDatatypeUtil.isIntegerDatatype(datatype)) {
076: try {
077: String normInt = XMLDatatypeUtil.normalize(label,
078: datatype);
079: return !normInt.equals("0");
080: } catch (IllegalArgumentException e) {
081: return false;
082: }
083: } else if (XMLDatatypeUtil
084: .isFloatingPointDatatype(datatype)) {
085: try {
086: String normFP = XMLDatatypeUtil.normalize(label,
087: datatype);
088: return !normFP.equals("0.0E0")
089: && !normFP.equals("NaN");
090: } catch (IllegalArgumentException e) {
091: return false;
092: }
093: }
094: }
095:
096: throw new ValueExprEvaluationException();
097: }
098:
099: public static boolean compare(Value leftVal, Value rightVal,
100: CompareOp operator) throws ValueExprEvaluationException {
101: if (leftVal instanceof Literal && rightVal instanceof Literal) {
102: // Both left and right argument is a Literal
103: return compareLiterals((Literal) leftVal,
104: (Literal) rightVal, operator);
105: } else {
106: // All other value combinations
107: switch (operator) {
108: case EQ:
109: return valuesEqual(leftVal, rightVal);
110: case NE:
111: return !valuesEqual(leftVal, rightVal);
112: default:
113: throw new ValueExprEvaluationException(
114: "Only literals with compatible, ordered datatypes can be compared using <, <=, > and >= operators");
115: }
116: }
117: }
118:
119: private static boolean valuesEqual(Value leftVal, Value rightVal) {
120: return leftVal != null && rightVal != null
121: && leftVal.equals(rightVal);
122: }
123:
124: public static boolean compareLiterals(Literal leftLit,
125: Literal rightLit, CompareOp operator)
126: throws ValueExprEvaluationException {
127: // type precendence:
128: // - simple literal
129: // - numeric
130: // - xsd:boolean
131: // - xsd:dateTime
132: // - xsd:string
133: // - RDF term (equal and unequal only)
134:
135: URI leftDatatype = leftLit.getDatatype();
136: URI rightDatatype = rightLit.getDatatype();
137:
138: Integer compareResult = null;
139:
140: if (QueryEvaluationUtil.isStringLiteral(leftLit)
141: && QueryEvaluationUtil.isStringLiteral(rightLit)) {
142: compareResult = leftLit.getLabel().compareTo(
143: rightLit.getLabel());
144: } else if (leftDatatype != null && rightDatatype != null) {
145: URI commonDatatype = null;
146:
147: if (leftDatatype.equals(rightDatatype)) {
148: commonDatatype = leftDatatype;
149: } else if (XMLDatatypeUtil.isNumericDatatype(leftDatatype)
150: && XMLDatatypeUtil.isNumericDatatype(rightDatatype)) {
151: // left and right arguments have different datatypes, try to find a
152: // more general, shared datatype
153: if (leftDatatype.equals(XMLSchema.DOUBLE)
154: || rightDatatype.equals(XMLSchema.DOUBLE)) {
155: commonDatatype = XMLSchema.DOUBLE;
156: } else if (leftDatatype.equals(XMLSchema.FLOAT)
157: || rightDatatype.equals(XMLSchema.FLOAT)) {
158: commonDatatype = XMLSchema.FLOAT;
159: } else if (leftDatatype.equals(XMLSchema.DECIMAL)
160: || rightDatatype.equals(XMLSchema.DECIMAL)) {
161: commonDatatype = XMLSchema.DECIMAL;
162: } else {
163: commonDatatype = XMLSchema.INTEGER;
164: }
165: }
166:
167: if (commonDatatype != null) {
168: try {
169: if (commonDatatype.equals(XMLSchema.DOUBLE)) {
170: compareResult = Double.compare(leftLit
171: .doubleValue(), rightLit.doubleValue());
172: } else if (commonDatatype.equals(XMLSchema.FLOAT)) {
173: compareResult = Float.compare(leftLit
174: .floatValue(), rightLit.floatValue());
175: } else if (commonDatatype.equals(XMLSchema.DECIMAL)) {
176: compareResult = leftLit.decimalValue()
177: .compareTo(rightLit.decimalValue());
178: } else if (XMLDatatypeUtil
179: .isIntegerDatatype(commonDatatype)) {
180: compareResult = leftLit.integerValue()
181: .compareTo(rightLit.integerValue());
182: } else if (commonDatatype.equals(XMLSchema.BOOLEAN)) {
183: Boolean leftBool = Boolean.valueOf(leftLit
184: .booleanValue());
185: Boolean rightBool = Boolean.valueOf(rightLit
186: .booleanValue());
187: compareResult = leftBool.compareTo(rightBool);
188: } else if (XMLDatatypeUtil
189: .isCalendarDatatype(commonDatatype)) {
190: XMLGregorianCalendar left = leftLit
191: .calendarValue();
192: XMLGregorianCalendar right = rightLit
193: .calendarValue();
194:
195: compareResult = left.compare(right);
196:
197: // Note: XMLGregorianCalendar.compare() returns compatible
198: // values
199: // (-1, 0, 1) but INDETERMINATE needs special treatment
200: if (compareResult == DatatypeConstants.INDETERMINATE) {
201: throw new ValueExprEvaluationException(
202: "Indeterminate result for date/time comparison");
203: }
204: } else if (commonDatatype.equals(XMLSchema.STRING)) {
205: compareResult = leftLit.getLabel().compareTo(
206: rightLit.getLabel());
207: }
208: } catch (IllegalArgumentException e) {
209: // One of the basic-type method calls failed, try syntactic match
210: // before throwing an error
211: if (leftLit.equals(rightLit)) {
212: switch (operator) {
213: case EQ:
214: return true;
215: case NE:
216: return false;
217: }
218: }
219:
220: throw new ValueExprEvaluationException(e);
221: }
222: }
223: }
224:
225: if (compareResult != null) {
226: // Literals have compatible ordered datatypes
227: switch (operator) {
228: case LT:
229: return compareResult.intValue() < 0;
230: case LE:
231: return compareResult.intValue() <= 0;
232: case EQ:
233: return compareResult.intValue() == 0;
234: case NE:
235: return compareResult.intValue() != 0;
236: case GE:
237: return compareResult.intValue() >= 0;
238: case GT:
239: return compareResult.intValue() > 0;
240: default:
241: throw new IllegalArgumentException("Unknown operator: "
242: + operator);
243: }
244: } else {
245: // All other cases, e.g. literals with languages, unequal or
246: // unordered datatypes, etc. These arguments can only be compared
247: // using the operators 'EQ' and 'NE'. See SPARQL's RDFterm-equal
248: // operator
249:
250: boolean literalsEqual = leftLit.equals(rightLit);
251:
252: if (!literalsEqual) {
253: if (leftDatatype != null
254: && rightDatatype != null
255: && XMLDatatypeUtil
256: .isCalendarDatatype(leftDatatype)
257: && XMLDatatypeUtil
258: .isCalendarDatatype(rightDatatype)) {
259: // left and right arguments have different date/time datatypes,
260: // these are always unequal
261: } else if (leftDatatype != null
262: && rightLit.getLanguage() == null
263: || rightDatatype != null
264: && leftLit.getLanguage() == null) {
265: // For literals with unsupported datatypes we don't know if their
266: // values are equal
267: throw new ValueExprEvaluationException(
268: "Unable to compare literals with unsupported types");
269: }
270: }
271:
272: switch (operator) {
273: case EQ:
274: return literalsEqual;
275: case NE:
276: return !literalsEqual;
277: case LT:
278: case LE:
279: case GE:
280: case GT:
281: throw new ValueExprEvaluationException(
282: "Only literals with compatible, ordered datatypes can be compared using <, <=, > and >= operators");
283: default:
284: throw new IllegalArgumentException("Unknown operator: "
285: + operator);
286: }
287: }
288: }
289:
290: /**
291: * Checks whether the supplied value is a "simple literal" as defined in the
292: * SPARQL spec. A "simple literal" is a literal without a language tag or a
293: * datatype.
294: */
295: public static boolean isSimpleLiteral(Value v) {
296: if (v instanceof Literal) {
297: return isSimpleLiteral((Literal) v);
298: }
299:
300: return false;
301: }
302:
303: /**
304: * Checks whether the supplied literal is a "simple literal" as defined in
305: * the SPARQL spec. A "simple literal" is a literal without a language tag or
306: * a datatype.
307: */
308: public static boolean isSimpleLiteral(Literal l) {
309: return l.getLanguage() == null && l.getDatatype() == null;
310: }
311:
312: /**
313: * Checks whether the supplied literal is a "string literal". A "string
314: * literal" is either a {@link #isSimpleLiteral(Literal) simple literal} or a
315: * literal with datatype {@link XMLSchema#STRING xsd:string}.
316: */
317: public static boolean isStringLiteral(Literal l) {
318: URI datatype = l.getDatatype();
319:
320: if (datatype == null) {
321: return l.getLanguage() == null;
322: } else {
323: return datatype.equals(XMLSchema.STRING);
324: }
325: }
326: }
|