package client.net.sf.saxon.ce.expr.sort; import client.net.sf.saxon.ce.expr.*; import client.net.sf.saxon.ce.tree.iter.EmptyIterator; import client.net.sf.saxon.ce.om.Item; import client.net.sf.saxon.ce.om.SequenceIterator; import client.net.sf.saxon.ce.trans.XPathException; import client.net.sf.saxon.ce.type.ItemType; import client.net.sf.saxon.ce.type.TypeHierarchy; import client.net.sf.saxon.ce.value.Cardinality; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * Expression equivalent to the imaginary syntax * expr sortby (sort-key)+ */ public class SortExpression extends Expression implements SortKeyEvaluator { private Expression select = null; private SortKeyDefinition[] sortKeyDefinitions = null; private AtomicComparer[] comparators = null; // created early if all comparators can be created statically // transient because Java RuleBasedCollator is not serializable /** * Create a sort expression * @param select the expression whose result is to be sorted * @param sortKeys the set of sort key definitions to be used, in major to minor order */ public SortExpression(Expression select, SortKeyDefinition[] sortKeys) { this.select = select; sortKeyDefinitions = sortKeys; Iterator children = iterateSubExpressions(); while (children.hasNext()) { Expression exp = (Expression) children.next(); adoptChildExpression(exp); } } /** * Get the expression defining the sequence being sorted * @return the expression whose result is to be sorted */ public Expression getBaseExpression() { return select; } /** * Get the immediate sub-expressions of this expression. Default implementation * returns a zero-length array, appropriate for an expression that has no * sub-expressions. * * @return an iterator containing the sub-expressions of this expression */ public Iterator<Expression> iterateSubExpressions() { return iterateSubExpressions(true); } private Iterator<Expression> iterateSubExpressions(boolean includeSortKey) { List list = new ArrayList(8); list.add(select); for (int i = 0; i < sortKeyDefinitions.length; i++) { if (includeSortKey) { list.add(sortKeyDefinitions[i].getSortKey()); } Expression e = sortKeyDefinitions[i].order; if (e != null) { list.add(e); } e = sortKeyDefinitions[i].caseOrder; if (e != null) { list.add(e); } e = sortKeyDefinitions[i].dataTypeExpression; if (e != null) { list.add(e); } e = sortKeyDefinitions[i].language; if (e != null) { list.add(e); } e = sortKeyDefinitions[i].collationName; if (e != null) { list.add(e); } e = sortKeyDefinitions[i].stable; if (e != null) { list.add(e); } } return list.iterator(); } /** * Given an expression that is an immediate child of this expression, test whether * the evaluation of the parent expression causes the child expression to be * evaluated repeatedly * @param child the immediate subexpression * @return true if the child expression is evaluated repeatedly */ public boolean hasLoopingSubexpression(Expression child) { return isSortKey(child); } /** * Replace one subexpression by a replacement subexpression * * @param original the original subexpression * @param replacement the replacement subexpression * @return true if the original subexpression is found */ public boolean replaceSubExpression(Expression original, Expression replacement) { boolean found = false; if (select == original) { select = replacement; found = true; } for (int i = 0; i < sortKeyDefinitions.length; i++) { if (sortKeyDefinitions[i].getSortKey() == original) { sortKeyDefinitions[i].setSortKey(replacement); found = true; } if (sortKeyDefinitions[i].getOrder() == original) { sortKeyDefinitions[i].setOrder(replacement); found = true; } if (sortKeyDefinitions[i].getCaseOrder() == original) { sortKeyDefinitions[i].setCaseOrder(replacement); found = true; } if (sortKeyDefinitions[i].getDataTypeExpression() == original) { sortKeyDefinitions[i].setDataTypeExpression(replacement); found = true; } if (sortKeyDefinitions[i].getLanguage() == original) { sortKeyDefinitions[i].setLanguage(replacement); found = true; } if (sortKeyDefinitions[i].collationName == original) { sortKeyDefinitions[i].collationName = replacement; found = true; } if (sortKeyDefinitions[i].stable == original) { sortKeyDefinitions[i].stable = replacement; found = true; } } return found; } /** * Simplify an expression * @param visitor an expression visitor */ public Expression simplify(ExpressionVisitor visitor) throws XPathException { select = visitor.simplify(select); return this; } /** * Type-check the expression */ public Expression typeCheck(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException { Expression select2 = visitor.typeCheck(select, contextItemType); if (select2 != select) { adoptChildExpression(select2); select = select2; } ItemType sortedItemType = select.getItemType(visitor.getConfiguration().getTypeHierarchy()); boolean allKeysFixed = true; for (int i = 0; i < sortKeyDefinitions.length; i++) { if (!(sortKeyDefinitions[i].isFixed())) { allKeysFixed = false; break; } } if (allKeysFixed) { comparators = new AtomicComparer[sortKeyDefinitions.length]; } for (int i = 0; i < sortKeyDefinitions.length; i++) { Expression sortKey = sortKeyDefinitions[i].getSortKey(); sortKey = visitor.typeCheck(sortKey, sortedItemType); if (visitor.getStaticContext().isInBackwardsCompatibleMode()) { sortKey = new FirstItemExpression(sortKey); } else { RoleLocator role = new RoleLocator(RoleLocator.INSTRUCTION, "xsl:sort/select", 0); role.setErrorCode("XTTE1020"); sortKey = CardinalityChecker.makeCardinalityChecker(sortKey, StaticProperty.ALLOWS_ZERO_OR_ONE, role); } sortKeyDefinitions[i].setSortKey(sortKey); sortKeyDefinitions[i].typeCheck(visitor, contextItemType); if (sortKeyDefinitions[i].isFixed()) { AtomicComparer comp = sortKeyDefinitions[i].makeComparator( visitor.getStaticContext().makeEarlyEvaluationContext()); sortKeyDefinitions[i].setFinalComparator(comp); if (allKeysFixed) { comparators[i] = comp; } } if (!ExpressionTool.dependsOnFocus(sortKey)) { visitor.getStaticContext().issueWarning( "Sort key will have no effect because its value does not depend on the context item", sortKey.getSourceLocator()); } } return this; } /** * Perform optimisation of an expression and its subexpressions. * <p/> * <p>This method is called after all references to functions and variables have been resolved * to the declaration of the function or variable, and after all type checking has been done.</p> * * @param visitor an expression visitor * @param contextItemType the static type of "." at the point where this expression is invoked. * The parameter is set to null if it is known statically that the context item will be undefined. * If the type of the context item is not known statically, the argument is set to * {@link client.net.sf.saxon.ce.type.Type#ITEM_TYPE} * @return the original expression, rewritten if appropriate to optimize execution * @throws XPathException if an error is discovered during this phase * (typically a type error) */ public Expression optimize(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException { Expression select2 = visitor.optimize(select, contextItemType); if (select2 != select) { adoptChildExpression(select2); select = select2; } // optimize the sort keys ItemType sortedItemType = select.getItemType(visitor.getConfiguration().getTypeHierarchy()); for (int i = 0; i < sortKeyDefinitions.length; i++) { Expression sortKey = sortKeyDefinitions[i].getSortKey(); sortKey = visitor.optimize(sortKey, sortedItemType); sortKeyDefinitions[i].setSortKey(sortKey); } if (Cardinality.allowsMany(select.getCardinality())) { return this; } else { return select; } } /** * Offer promotion for this subexpression. The offer will be accepted if the subexpression * is not dependent on the factors (e.g. the context item) identified in the PromotionOffer. * By default the offer is not accepted - this is appropriate in the case of simple expressions * such as constant values and variable references where promotion would give no performance * advantage. This method is always called at compile time. * * @param offer details of the offer, for example the offer to move * expressions that don't depend on the context to an outer level in * the containing expression * @param parent * @return if the offer is not accepted, return this expression unchanged. * Otherwise return the result of rewriting the expression to promote * this subexpression * @throws client.net.sf.saxon.ce.trans.XPathException * if any error is detected */ public Expression promote(PromotionOffer offer, Expression parent) throws XPathException { Expression exp = offer.accept(parent, this); if (exp != null) { return exp; } else { select = doPromotion(select, offer); for (int i = 0; i < sortKeyDefinitions.length; i++) { final Expression sk2 = sortKeyDefinitions[i].getSortKey().promote(offer, parent); sortKeyDefinitions[i].setSortKey(sk2); if (sortKeyDefinitions[i].order != null) { sortKeyDefinitions[i].order = sortKeyDefinitions[i].order.promote(offer, parent); } if (sortKeyDefinitions[i].stable != null) { sortKeyDefinitions[i].stable = sortKeyDefinitions[i].stable.promote(offer, parent); } if (sortKeyDefinitions[i].caseOrder != null) { sortKeyDefinitions[i].caseOrder = sortKeyDefinitions[i].caseOrder.promote(offer, parent); } if (sortKeyDefinitions[i].dataTypeExpression != null) { sortKeyDefinitions[i].dataTypeExpression = sortKeyDefinitions[i].dataTypeExpression.promote(offer, parent); } if (sortKeyDefinitions[i].language != null) { sortKeyDefinitions[i].language = sortKeyDefinitions[i].language.promote(offer, parent); } if (sortKeyDefinitions[i].collationName != null) { sortKeyDefinitions[i].collationName = sortKeyDefinitions[i].collationName.promote(offer, parent); } } return this; } } /** * Test whether a given expression is one of the sort keys * @param child the given expression * @return true if the given expression is one of the sort keys */ public boolean isSortKey(Expression child) { for (int i = 0; i < sortKeyDefinitions.length; i++) { Expression exp = sortKeyDefinitions[i].getSortKey(); if (exp == child) { return true; } } return false; } /** * Determine the static cardinality */ public int computeCardinality() { return select.getCardinality(); } /** * Determine the data type of the items returned by the expression, if possible * * @param th the type hierarchy cache * @return a value such as Type.STRING, Type.BOOLEAN, Type.NUMBER, Type.NODE, * or Type.ITEM (meaning not known in advance) */ public ItemType getItemType(TypeHierarchy th) { return select.getItemType(th); } /** * Get the static properties of this expression (other than its type). The result is * bit-significant. These properties are used for optimizations. In general, if * property bit is set, it is true, but if it is unset, the value is unknown. */ public int computeSpecialProperties() { int props = 0; if ((select.getSpecialProperties() & StaticProperty.CONTEXT_DOCUMENT_NODESET) != 0) { props |= StaticProperty.CONTEXT_DOCUMENT_NODESET; } if ((select.getSpecialProperties() & StaticProperty.SINGLE_DOCUMENT_NODESET) != 0) { props |= StaticProperty.SINGLE_DOCUMENT_NODESET; } if ((select.getSpecialProperties() & StaticProperty.NON_CREATIVE) != 0) { props |= StaticProperty.NON_CREATIVE; } return props; } /** * Enumerate the results of the expression */ public SequenceIterator iterate(XPathContext context) throws XPathException { SequenceIterator iter = select.iterate(context); if (iter instanceof EmptyIterator) { return iter; } XPathContext xpc = context.newMinorContext(); AtomicComparer[] comps = comparators; if (comparators == null) { comps = new AtomicComparer[sortKeyDefinitions.length]; for (int s = 0; s < sortKeyDefinitions.length; s++) { AtomicComparer comp = sortKeyDefinitions[s].getFinalComparator(); if (comp == null) { comp = sortKeyDefinitions[s].makeComparator(xpc); } comps[s] = comp; } } return new SortedIterator(xpc, iter, this, comps); } /** * Callback for evaluating the sort keys */ public Item evaluateSortKey(int n, XPathContext c) throws XPathException { return sortKeyDefinitions[n].getSortKey().evaluateItem(c); } } // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. // This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.