001: package net.sf.saxon.sort;
002:
003: import net.sf.saxon.expr.Expression;
004: import net.sf.saxon.expr.XPathContext;
005: import net.sf.saxon.expr.Container;
006: import net.sf.saxon.expr.StaticContext;
007: import net.sf.saxon.instruct.Executable;
008: import net.sf.saxon.trans.DynamicError;
009: import net.sf.saxon.trans.XPathException;
010: import net.sf.saxon.value.EmptySequence;
011: import net.sf.saxon.value.StringValue;
012:
013: import java.io.Serializable;
014: import java.util.Comparator;
015: import java.net.URI;
016: import java.net.URISyntaxException;
017:
018: /**
019: * A SortKeyDefinition defines one component of a sort key. <BR>
020: *
021: * Note that most attributes defining the sort key can be attribute value templates,
022: * and can therefore vary from one invocation to another. We hold them as expressions. As
023: * soon as they are all known (which in general is only at run-time), the SortKeyDefinition
024: * is replaced by a FixedSortKeyDefinition in which all these values are fixed.
025: */
026:
027: // TODO: optimise also for the case where the attributes depend only on global variables
028: // or parameters, in which case the same Comparator can be used for the duration of a
029: // transformation.
030: // TODO: at present the SortKeyDefinition is evaluated to obtain a Comparator, which can
031: // be used to compare two sort keys. It would be more efficient to use a Collator to
032: // obtain collation keys for all the items to be sorted, as these can be compared more
033: // efficiently.
034:
035: public class SortKeyDefinition implements Serializable {
036:
037: private static StringValue defaultOrder = new StringValue(
038: "ascending");
039: private static StringValue defaultCaseOrder = new StringValue(
040: "#default");
041: private static StringValue defaultLanguage = StringValue.EMPTY_STRING;
042:
043: protected Expression sortKey;
044: protected Expression order = defaultOrder;
045: protected Expression dataTypeExpression = EmptySequence
046: .getInstance();
047: // used when the type is not known till run-time
048: protected Expression caseOrder = defaultCaseOrder;
049: protected Expression language = defaultLanguage;
050: protected Expression collationName = null;
051: protected Comparator collation; // usually a Collator, but not always
052: protected String baseURI; // needed in case collation URI is relative
053: protected boolean emptyFirst = true;
054: // used only in XQuery at present
055: protected Container parentExpression;
056:
057: // Note, the "collation" defines the collating sequence for the sort key. The
058: // "comparer" is what is actually used to do comparisons, after taking into account
059: // ascending/descending, caseOrder, etc.
060:
061: // The comparer is transient because a RuleBasedCollator is not serializable. This means that
062: // when a stylesheet is compiled, the comparer is discarded, which means a new comparer will be
063: // constructed for each sort at run-time.
064:
065: public void setParentExpression(Container container) {
066: parentExpression = container;
067: }
068:
069: public Container getParentExpression() {
070: return parentExpression;
071: }
072:
073: /**
074: * Set the expression used as the sort key
075: */
076:
077: public void setSortKey(Expression exp) {
078: sortKey = exp;
079: }
080:
081: /**
082: * Get the expression used as the sort key
083: */
084:
085: public Expression getSortKey() {
086: return sortKey;
087: }
088:
089: /**
090: * Set the order. This is supplied as an expression which must evaluate to "ascending"
091: * or "descending". If the order is fixed, supply e.g. new StringValue("ascending").
092: * Default is "ascending".
093: */
094:
095: public void setOrder(Expression exp) {
096: order = exp;
097: }
098:
099: public Expression getOrder() {
100: return order;
101: }
102:
103: /**
104: * Set the data type. This is supplied as an expression which must evaluate to "text",
105: * "number", or a QName. If the data type is fixed, the valus should be supplied using
106: * setDataType() and not via this method.
107: */
108:
109: public void setDataTypeExpression(Expression exp) {
110: dataTypeExpression = exp;
111: }
112:
113: public Expression getDataTypeExpression() {
114: return dataTypeExpression;
115: }
116:
117: /**
118: * Set the case order. This is supplied as an expression which must evaluate to "upper-first"
119: * or "lower-first" or "#default". If the order is fixed, supply e.g. new StringValue("lower-first").
120: * Default is "#default".
121: */
122:
123: public void setCaseOrder(Expression exp) {
124: caseOrder = exp;
125: }
126:
127: public Expression getCaseOrder() {
128: return caseOrder;
129: }
130:
131: /**
132: * Set the language. This is supplied as an expression which evaluates to the language name.
133: * If the order is fixed, supply e.g. new StringValue("de").
134: */
135:
136: public void setLanguage(Expression exp) {
137: language = exp;
138: }
139:
140: public Expression getLanguage() {
141: return language;
142: }
143:
144: /**
145: * Set the collation.
146: */
147:
148: public void setCollationName(Expression collationName) {
149: this .collationName = collationName;
150: }
151:
152: public Expression getCollationName() {
153: return collationName;
154: }
155:
156: public void setCollation(Comparator collation) {
157: this .collation = collation;
158: }
159:
160: public Comparator getCollation() {
161: return collation;
162: }
163:
164: public void setBaseURI(String baseURI) {
165: this .baseURI = baseURI;
166: }
167:
168: public String getBaseURI() {
169: return baseURI;
170: }
171:
172: /**
173: * Set whether empty sequence comes before other values or after them
174: * @param emptyFirst true if () is considered lower than any other value
175: */
176:
177: public void setEmptyFirst(boolean emptyFirst) {
178: this .emptyFirst = emptyFirst;
179: }
180:
181: public boolean getEmptyFirst() {
182: return emptyFirst;
183: }
184:
185: public SortKeyDefinition simplify(StaticContext env, Executable exec)
186: throws XPathException {
187:
188: if (order instanceof StringValue
189: && (dataTypeExpression == null || dataTypeExpression instanceof StringValue)
190: && caseOrder instanceof StringValue
191: && language instanceof StringValue && collation != null) {
192:
193: FixedSortKeyDefinition fskd = new FixedSortKeyDefinition();
194: fskd.setSortKey(sortKey);
195: fskd.setOrder(order);
196: fskd.setDataTypeExpression(dataTypeExpression);
197: fskd.setCaseOrder(caseOrder);
198: fskd.setLanguage(language);
199: fskd.setEmptyFirst(emptyFirst);
200: fskd.collation = collation;
201: fskd.baseURI = baseURI;
202: fskd.bindComparer(env.makeEarlyEvaluationContext());
203: return fskd;
204: } else {
205: return this ;
206: }
207: }
208:
209: /**
210: * Evaluate any aspects of the sort definition that were specified as AVTs, for example
211: * ascending/descending, language, case-order, data-type. This is done at the start of each
212: * sort. A FixedSortKeyDefinition is a SortKeyDefinition in which these properties are all
213: * known values.
214: */
215:
216: public FixedSortKeyDefinition reduce(XPathContext context)
217: throws XPathException {
218:
219: FixedSortKeyDefinition sknew = new FixedSortKeyDefinition();
220:
221: Expression sortKey2 = sortKey;
222:
223: sknew.setSortKey(sortKey2);
224: sknew.setOrder((StringValue) order.evaluateItem(context));
225: sknew.setDataTypeExpression((StringValue) dataTypeExpression
226: .evaluateItem(context));
227: sknew.setCaseOrder((StringValue) caseOrder
228: .evaluateItem(context));
229: sknew.setLanguage((StringValue) language.evaluateItem(context));
230: if (collation == null && collationName != null) {
231: String cname = collationName.evaluateItem(context)
232: .getStringValue();
233: URI collationURI;
234: try {
235: collationURI = new URI(cname);
236: if (!collationURI.isAbsolute()) {
237: if (baseURI == null) {
238: throw new DynamicError(
239: "Collation URI is relative, and base URI is unknown");
240: } else {
241: URI base = new URI(baseURI);
242: collationURI = base.resolve(collationURI);
243: }
244: }
245: } catch (URISyntaxException err) {
246: throw new DynamicError("Collation name " + cname
247: + " is not a valid URI: " + err);
248: }
249: Comparator comp = context.getCollation(collationURI
250: .toString());
251: if (comp == null) {
252: throw new DynamicError("Collation " + collationURI
253: + " is not recognized");
254: }
255: sknew.setCollation(comp);
256: }
257: if (collation != null) {
258: sknew.setCollation(collation);
259: }
260: sknew.setEmptyFirst(emptyFirst);
261: sknew.bindComparer(context);
262: return sknew;
263: }
264:
265: }
266:
267: //
268: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
269: // you may not use this file except in compliance with the License. You may obtain a copy of the
270: // License at http://www.mozilla.org/MPL/
271: //
272: // Software distributed under the License is distributed on an "AS IS" basis,
273: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
274: // See the License for the specific language governing rights and limitations under the License.
275: //
276: // The Original Code is: all this file.
277: //
278: // The Initial Developer of the Original Code is Michael H. Kay.
279: //
280: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
281: //
282: // Contributor(s): none.
283: //
|