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 java.util.Comparator;
009:
010: import info.aduna.lang.ObjectUtil;
011:
012: import org.openrdf.model.BNode;
013: import org.openrdf.model.Literal;
014: import org.openrdf.model.URI;
015: import org.openrdf.model.Value;
016: import org.openrdf.model.datatypes.XMLDatatypeUtil;
017: import org.openrdf.query.algebra.Compare.CompareOp;
018: import org.openrdf.query.algebra.evaluation.ValueExprEvaluationException;
019:
020: /**
021: * A comparator that compares values according the SPARQL value ordering as
022: * specified in <A
023: * href="http://www.w3.org/TR/rdf-sparql-query/#modOrderBy">SPARQL Query
024: * Language for RDF</a>.
025: *
026: * @author james
027: * @author Arjohn Kampman
028: */
029: public class ValueComparator implements Comparator<Value> {
030:
031: public int compare(Value o1, Value o2) {
032: // check equality
033: if (ObjectUtil.nullEquals(o1, o2)) {
034: return 0;
035: }
036:
037: // 1. (Lowest) no value assigned to the variable
038: if (o1 == null) {
039: return -1;
040: }
041: if (o2 == null) {
042: return 1;
043: }
044:
045: // 2. Blank nodes
046: boolean b1 = o1 instanceof BNode;
047: boolean b2 = o2 instanceof BNode;
048: if (b1 && b2) {
049: return 0;
050: }
051: if (b1) {
052: return -1;
053: }
054: if (b2) {
055: return 1;
056: }
057:
058: // 3. IRIs
059: boolean u1 = o1 instanceof URI;
060: boolean u2 = o2 instanceof URI;
061: if (u1 && u2) {
062: return compareURIs((URI) o1, (URI) o2);
063: }
064: if (u1) {
065: return -1;
066: }
067: if (u2) {
068: return 1;
069: }
070:
071: // 4. RDF literals
072: return compareLiterals((Literal) o1, (Literal) o2);
073: }
074:
075: private int compareURIs(URI leftURI, URI rightURI) {
076: return leftURI.toString().compareTo(rightURI.toString());
077: }
078:
079: private int compareLiterals(Literal leftLit, Literal rightLit) {
080: // Additional constraint for ORDER BY: "A plain literal is lower
081: // than an RDF literal with type xsd:string of the same lexical
082: // form."
083:
084: if (!QueryEvaluationUtil.isStringLiteral(leftLit)
085: || !QueryEvaluationUtil.isStringLiteral(rightLit)) {
086: try {
087: boolean isSmaller = QueryEvaluationUtil
088: .compareLiterals(leftLit, rightLit,
089: CompareOp.LT);
090:
091: if (isSmaller) {
092: return -1;
093: } else {
094: return 1;
095: }
096: } catch (ValueExprEvaluationException e) {
097: // literals cannot be compared using the '<' operator, continue
098: // below
099: }
100: }
101:
102: int result = 0;
103:
104: // Sort by datatype first, plain literals come before datatyped literals
105: URI leftDatatype = leftLit.getDatatype();
106: URI rightDatatype = rightLit.getDatatype();
107:
108: if (leftDatatype != null) {
109: if (rightDatatype != null) {
110: // Both literals have datatypes
111: result = compareDatatypes(leftDatatype, rightDatatype);
112: } else {
113: result = 1;
114: }
115: } else if (rightDatatype != null) {
116: result = -1;
117: }
118:
119: if (result == 0) {
120: // datatypes are equal or both literals are untyped; sort by language
121: // tags, simple literals come before literals with language tags
122: String leftLanguage = leftLit.getLanguage();
123: String rightLanguage = rightLit.getLanguage();
124:
125: if (leftLanguage != null) {
126: if (rightLanguage != null) {
127: result = leftLanguage.compareTo(rightLanguage);
128: } else {
129: result = 1;
130: }
131: } else if (rightLanguage != null) {
132: result = -1;
133: }
134: }
135:
136: if (result == 0) {
137: // Literals are equal as fas as their datatypes and language tags are
138: // concerned, compare their labels
139: result = leftLit.getLabel().compareTo(rightLit.getLabel());
140: }
141:
142: return result;
143: }
144:
145: /**
146: * Compares two literal datatypes and indicates if one should be ordered
147: * after the other. This algorithm ensures that compatible ordered datatypes
148: * (numeric and date/time) are grouped together so that
149: * {@link QueryEvaluationUtil#compareLiterals(Literal, Literal, CompareOp)}
150: * is used in consecutive ordering steps.
151: */
152: private int compareDatatypes(URI leftDatatype, URI rightDatatype) {
153: if (XMLDatatypeUtil.isNumericDatatype(leftDatatype)) {
154: if (XMLDatatypeUtil.isNumericDatatype(rightDatatype)) {
155: // both are numeric datatypes
156: return compareURIs(leftDatatype, rightDatatype);
157: } else {
158: return -1;
159: }
160: } else if (XMLDatatypeUtil.isNumericDatatype(rightDatatype)) {
161: return 1;
162: } else if (XMLDatatypeUtil.isCalendarDatatype(leftDatatype)) {
163: if (XMLDatatypeUtil.isCalendarDatatype(rightDatatype)) {
164: // both are calendar datatypes
165: return compareURIs(leftDatatype, rightDatatype);
166: } else {
167: return -1;
168: }
169: } else if (XMLDatatypeUtil.isCalendarDatatype(rightDatatype)) {
170: return 1;
171: } else {
172: // incompatible or unordered datatypes
173: return compareURIs(leftDatatype, rightDatatype);
174: }
175: }
176: }
|