package client.net.sf.saxon.ce.expr.instruct;
import client.net.sf.saxon.ce.LogController;
import client.net.sf.saxon.ce.expr.*;
import client.net.sf.saxon.ce.expr.parser.CodeInjector;
import client.net.sf.saxon.ce.functions.BooleanFn;
import client.net.sf.saxon.ce.om.Item;
import client.net.sf.saxon.ce.om.SequenceIterator;
import client.net.sf.saxon.ce.om.StandardNames;
import client.net.sf.saxon.ce.om.StructuredQName;
import client.net.sf.saxon.ce.style.StyleElement;
import client.net.sf.saxon.ce.trace.XSLTTraceListener;
import client.net.sf.saxon.ce.trans.XPathException;
import client.net.sf.saxon.ce.tree.iter.EmptyIterator;
import client.net.sf.saxon.ce.tree.util.FastStringBuffer;
import client.net.sf.saxon.ce.type.ItemType;
import client.net.sf.saxon.ce.type.Type;
import client.net.sf.saxon.ce.type.TypeHierarchy;
import client.net.sf.saxon.ce.value.BooleanValue;
import client.net.sf.saxon.ce.value.Cardinality;
import client.net.sf.saxon.ce.value.SequenceType;
import java.util.ArrayList;
import java.util.Iterator;
import com.google.gwt.logging.client.LogConfiguration;
/**
* Compiled representation of an xsl:choose or xsl:if element in the stylesheet.
* Also used for typeswitch in XQuery.
*/
public class Choose extends Instruction {
// The class implements both xsl:choose and xsl:if. There is a list of boolean
// expressions (conditions) and a list of corresponding actions: the conditions
// are evaluated in turn, and when one is found that is true, the corresponding
// action is evaluated. For xsl:if, there is always one condition and one action.
// An xsl:otherwise is compiled as if it were xsl:when test="true()". If no
// condition is satisfied, the instruction returns an empty sequence.
private Expression[] conditions;
private Expression[] actions;
/**
* Construct an xsl:choose instruction
* @param conditions the conditions to be tested, in order
* @param actions the actions to be taken when the corresponding condition is true
*/
public Choose(Expression[] conditions, Expression[] actions) {
this.conditions = conditions;
this.actions = actions;
if (conditions.length != actions.length) {
throw new IllegalArgumentException("Choose: unequal length arguments");
}
for (int i=0; i<conditions.length; i++) {
adoptChildExpression(conditions[i]);
adoptChildExpression(actions[i]);
}
}
/**
* Make a simple conditional expression (if (condition) then (thenExp) else (elseExp)
* @param condition the condition to be tested
* @param thenExp the expression to be evaluated if the condition is true
* @param elseExp the expression to be evaluated if the condition is false
* @return the expression
*/
public static Expression makeConditional(Expression condition, Expression thenExp, Expression elseExp) {
if (Literal.isEmptySequence(elseExp)) {
Expression[] conditions = new Expression[] {condition};
Expression[] actions = new Expression[] {thenExp};
return new Choose(conditions, actions);
} else {
Expression[] conditions = new Expression[] {condition, new Literal(BooleanValue.TRUE)};
Expression[] actions = new Expression[] {thenExp, elseExp};
return new Choose(conditions, actions);
}
}
/**
* Make a simple conditional expression (if (condition) then (thenExp) else ()
* @param condition the condition to be tested
* @param thenExp the expression to be evaluated if the condition is true
* @return the expression
*/
public static Expression makeConditional(Expression condition, Expression thenExp) {
Expression[] conditions = new Expression[] {condition};
Expression[] actions = new Expression[] {thenExp};
return new Choose(conditions, actions);
}
/**
* Test whether an expression is a single-branch choose, that is, an expression of the form
* if (condition) then exp else ()
* @param exp the expression to be tested
* @return true if the expression is a choose expression and there is only one condition,
* so that the expression returns () if this condition is false
*/
public static boolean isSingleBranchChoice(Expression exp) {
return (exp instanceof Choose && ((Choose)exp).conditions.length == 1);
}
/**
* Get the array of conditions to be tested
* @return the array of condition expressions
*/
public Expression[] getConditions() {
return conditions;
}
/**
* Get the array of actions to be performed
* @return the array of expressions to be evaluated when the corresponding condition is true
*/
public Expression[] getActions() {
return actions;
}
private String[] conditionTests = null;
public void setConditionTests(String[] conditionTests) {
this.conditionTests = conditionTests;
}
/**
* Get the name of this instruction for diagnostic and tracing purposes
* We assume that if there was
* only one condition then it was an xsl:if; this is not necessarily so, but
* it's adequate for tracing purposes.
*/
public int getInstructionNameCode() {
return (conditions.length==1 ? StandardNames.XSL_IF : StandardNames.XSL_CHOOSE);
}
/**
* Simplify an expression. This performs any static optimization (by rewriting the expression
* as a different expression).
*
* @exception XPathException if an error is discovered during expression
* rewriting
* @return the simplified expression
* @param visitor expression visitor object
*/
public Expression simplify(ExpressionVisitor visitor) throws XPathException {
for (int i=0; i<conditions.length; i++) {
conditions[i] = visitor.simplify(conditions[i]);
try {
actions[i] = visitor.simplify(actions[i]);
} catch (XPathException err) {
// mustn't throw the error unless the branch is actually selected, unless its a type error
if (err.isTypeError()) {
throw err;
} else {
actions[i] = new ErrorExpression(err);
}
}
}
return this;
}
public Expression typeCheck(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException {
for (int i=0; i<conditions.length; i++) {
conditions[i] = visitor.typeCheck(conditions[i], contextItemType);
XPathException err = TypeChecker.ebvError(conditions[i], visitor.getConfiguration().getTypeHierarchy());
if (err != null) {
err.setLocator(conditions[i].getSourceLocator());
throw err;
}
}
for (int i=0; i<actions.length; i++) {
try {
actions[i] = visitor.typeCheck(actions[i], contextItemType);
} catch (XPathException err) {
// mustn't throw the error unless the branch is actually selected, unless its a static or type error
if (err.isStaticError()) {
throw err;
} else if (err.isTypeError()) {
// if this is an "empty" else branch, don't be draconian about the error handling. It might be
// the user knows the otherwise branch isn't needed because one of the when branches will always
// be satisfied.
if (Literal.isEmptySequence(actions[i])) {
actions[i] = new ErrorExpression(err);
} else {
throw err;
}
} else {
actions[i] = new ErrorExpression(err);
}
}
}
return this;
}
/**
* Determine whether this expression implements its own method for static type checking
*
* @return true - this expression has a non-trivial implementation of the staticTypeCheck()
* method
*/
public boolean implementsStaticTypeCheck() {
return true;
}
/**
* Static type checking for conditional expressions is delegated to the expression itself,
* and is performed separately on each branch of the conditional, so that dynamic checks are
* added only on those branches where the check is actually required. This also results in a static
* type error if any branch is incapable of delivering a value of the required type. One reason
* for this approach is to avoid doing dynamic type checking on a recursive function call as this
* prevents tail-call optimization being used.
* @param req the required type
* @param backwardsCompatible true if backwards compatibility mode applies
* @param role the role of the expression in relation to the required type
* @param visitor an expression visitor
* @return the expression after type checking (perhaps augmented with dynamic type checking code)
* @throws XPathException if failures occur, for example if the static type of one branch of the conditional
* is incompatible with the required type
*/
public Expression staticTypeCheck(SequenceType req,
boolean backwardsCompatible,
RoleLocator role, ExpressionVisitor visitor)
throws XPathException {
for (int i=0; i<actions.length; i++) {
actions[i] = TypeChecker.staticTypeCheck(actions[i], req, backwardsCompatible, role, visitor);
}
// If the last condition isn't true(), then we need to consider the fall-through case, which returns
// an empty sequence
if (!Literal.isConstantBoolean(conditions[conditions.length-1], true) &&
!Cardinality.allowsZero(req.getCardinality())) {
String cond = (conditions.length == 1 ? "the condition is not" : "none of the conditions is");
XPathException err = new XPathException(
"Conditional expression: If " + cond + " satisfied, an empty sequence will be returned, " +
"but this is not allowed as the " + role.getMessage());
err.setErrorCode(role.getErrorCode());
err.setIsTypeError(true);
throw err;
}
return this;
}
public Expression optimize(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException {
for (int i=0; i<conditions.length; i++) {
conditions[i] = visitor.optimize(conditions[i], contextItemType);
Expression ebv = BooleanFn.rewriteEffectiveBooleanValue(conditions[i], visitor, contextItemType);
if (ebv != null && ebv != conditions[i]) {
conditions[i] = ebv;
adoptChildExpression(ebv);
}
if (conditions[i] instanceof Literal &&
!(((Literal)conditions[i]).getValue() instanceof BooleanValue)) {
final boolean b;
try {
b = ((Literal)conditions[i]).getValue().effectiveBooleanValue();
} catch (XPathException err) {
err.setLocator(getSourceLocator());
throw err;
}
conditions[i] = new Literal(BooleanValue.get(b));
}
}
for (int i=0; i<actions.length; i++) {
try {
actions[i] = visitor.optimize(actions[i], contextItemType);
} catch (XPathException err) {
// mustn't throw the error unless the branch is actually selected, unless its a type error
if (err.isTypeError()) {
throw err;
} else {
actions[i] = new ErrorExpression(err);
}
}
}
if (actions.length == 0) {
return Literal.makeEmptySequence();
}
return this;
}
/**
* An implementation of Expression must provide at least one of the methods evaluateItem(), iterate(), or process().
* This method indicates which of these methods is prefered. For instructions this is the process() method.
*/
public int getImplementationMethod() {
int m = Expression.PROCESS_METHOD | Expression.ITERATE_METHOD;
if (!Cardinality.allowsMany(getCardinality())) {
m |= Expression.EVALUATE_METHOD;
}
return m;
}
/**
* Mark tail-recursive calls on functions. For most expressions, this does nothing.
*
* @return 0 if no tail call was found; 1 if a tail call on a different function was found;
* 2 if a tail recursive call was found and if this call accounts for the whole of the value.
*/
public int markTailFunctionCalls(StructuredQName qName, int arity) {
int result = 0;
for (int i=0; i<actions.length; i++) {
result = Math.max(result, actions[i].markTailFunctionCalls(qName, arity));
}
return result;
}
/**
* Get the item type of the items returned by evaluating this instruction
* @return the static item type of the instruction
* @param th Type hierarchy cache
*/
public ItemType getItemType(TypeHierarchy th) {
ItemType type = actions[0].getItemType(th);
for (int i=1; i<actions.length; i++) {
type = Type.getCommonSuperType(type, actions[i].getItemType(th), th);
}
return type;
}
/**
* Compute the cardinality of the sequence returned by evaluating this instruction
* @return the static cardinality
*/
public int computeCardinality() {
int card = 0;
boolean includesTrue = false;
for (int i=0; i<actions.length; i++) {
card = Cardinality.union(card, actions[i].getCardinality());
if (Literal.isConstantBoolean(conditions[i], true)) {
includesTrue = true;
}
}
if (!includesTrue) {
// we may drop off the end and return an empty sequence (typical for xsl:if)
card = Cardinality.union(card, StaticProperty.ALLOWS_ZERO);
}
return card;
}
/**
* Get the static properties of this expression (other than its type). The result is
* bit-signficant. 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.
*
* @return a set of flags indicating static properties of this expression
*/
public int computeSpecialProperties() {
// The special properties of a conditional are those which are common to every branch of the conditional
int props = actions[0].getSpecialProperties();
for (int i=1; i<actions.length; i++) {
props &= actions[i].getSpecialProperties();
}
return props;
}
/**
* Determine whether this instruction creates new nodes.
* This implementation returns true if any of the "actions" creates new nodes.
* (Nodes created by the conditions can't contribute to the result).
*/
public final boolean createsNewNodes() {
for (int i=0; i<actions.length; i++) {
int props = actions[i].getSpecialProperties();
if ((props & StaticProperty.NON_CREATIVE) == 0) {
return true;
}
}
return false;
}
/**
* Get all the XPath expressions associated with this instruction
* (in XSLT terms, the expression present on attributes of the instruction,
* as distinct from the child instructions in a sequence construction)
*/
public Iterator<Expression> iterateSubExpressions() {
return new Iterator<Expression>() {
boolean doingConditions = true;
int index = 0;
public boolean hasNext() {
return doingConditions || index<actions.length;
}
public Expression next() {
if (doingConditions) {
if (index < conditions.length) {
return conditions[index++];
} else {
doingConditions = false;
index = 0;
return actions[index++];
}
} else {
if (index < actions.length) {
return actions[index++];
} else {
return null;
}
}
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* 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;
for (int i=0; i<conditions.length; i++) {
if (conditions[i] == original) {
conditions[i] = replacement;
found = true;
}
}
for (int i=0; i<actions.length; i++) {
if (actions[i] == original) {
actions[i] = replacement;
found = true;
}
}
return found;
}
/**
* Handle promotion offers, that is, non-local tree rewrites.
* @param offer The type of rewrite being offered
* @throws XPathException
*/
protected void promoteInst(PromotionOffer offer) throws XPathException {
// xsl:when acts as a guard: expressions inside the when mustn't be evaluated if the when is false,
// and conditions after the first mustn't be evaluated if a previous condition is true. So we
// don't pass all promotion offers on
if (offer.action == PromotionOffer.UNORDERED ||
offer.action == PromotionOffer.REPLACE_CURRENT) {
for (int i=0; i<conditions.length; i++) {
conditions[i] = doPromotion(conditions[i], offer);
}
for (int i=0; i<actions.length; i++) {
actions[i] = doPromotion(actions[i], offer);
}
} else {
// in other cases, only the first xsl:when condition is promoted
conditions[0] = doPromotion(conditions[0], offer);
}
}
/**
* The toString() method for an expression attempts to give a representation of the expression
* in an XPath-like form, but there is no guarantee that the syntax will actually be true XPath.
* In the case of XSLT instructions, the toString() method gives an abstracted view of the syntax
* @return a representation of the expression as a string
*/
public String toString() {
FastStringBuffer sb = new FastStringBuffer(FastStringBuffer.SMALL);
sb.append("if (");
for (int i=0; i<conditions.length; i++) {
sb.append(conditions[i].toString());
sb.append(") then (");
sb.append(actions[i].toString());
if (i == conditions.length - 1) {
sb.append(")");
} else {
sb.append(") else if (");
}
}
return sb.toString();
}
/**
* Process this instruction, that is, choose an xsl:when or xsl:otherwise child
* and process it.
* @param context the dynamic context of this transformation
* @throws XPathException if any non-recoverable dynamic error occurs
* @return a TailCall, if the chosen branch ends with a call of call-template or
* apply-templates. It is the caller's responsibility to execute such a TailCall.
* If there is no TailCall, returns null.
*/
public TailCall processLeavingTail(XPathContext context) throws XPathException {
for (int i=0; i<conditions.length; i++) {
final boolean b;
try {
b = conditions[i].effectiveBooleanValue(context);
} catch (XPathException e) {
e.maybeSetLocation(conditions[i].getSourceLocator());
throw e;
}
if (b) {
enterConditionTrace(i);
TailCall tail;
if (actions[i] instanceof TailCallReturner) {
tail = ((TailCallReturner)actions[i]).processLeavingTail(context);
} else {
actions[i].process(context);
tail = null;
}
leaveConditionTrace(i);
return tail;
}
}
return null;
}
/**
* Evaluate an expression as a single item. This always returns either a single Item or
* null (denoting the empty sequence). No conversion is done. This method should not be
* used unless the static type of the expression is a subtype of "item" or "item?": that is,
* it should not be called if the expression may return a sequence. There is no guarantee that
* this condition will be detected.
*
* @param context The context in which the expression is to be evaluated
* @exception XPathException if any dynamic error occurs evaluating the
* expression
* @return the node or atomic value that results from evaluating the
* expression; or null to indicate that the result is an empty
* sequence
*/
public Item evaluateItem(XPathContext context) throws XPathException {
for (int i=0; i<conditions.length; i++) {
final boolean b;
try {
b = conditions[i].effectiveBooleanValue(context);
} catch (XPathException e) {
e.maybeSetLocation(conditions[i].getSourceLocator());
throw e;
}
if (b) {
enterConditionTrace(i);
Item result = actions[i].evaluateItem(context);
leaveConditionTrace(i);
return result;
}
}
return null;
}
/**
* Return an Iterator to iterate over the values of a sequence. The value of every
* expression can be regarded as a sequence, so this method is supported for all
* expressions. This default implementation relies on the process() method: it
* "pushes" the results of the instruction to a sequence in memory, and then
* iterates over this in-memory sequence.
*
* In principle instructions should implement a pipelined iterate() method that
* avoids the overhead of intermediate storage.
*
* @exception XPathException if any dynamic error occurs evaluating the
* expression
* @param context supplies the context for evaluation
* @return a SequenceIterator that can be used to iterate over the result
* of the expression
*/
public SequenceIterator iterate(XPathContext context) throws XPathException {
for (int i=0; i<conditions.length; i++) {
final boolean b;
try {
b = conditions[i].effectiveBooleanValue(context);
} catch (XPathException e) {
e.maybeSetLocation(conditions[i].getSourceLocator());
throw e;
}
SequenceIterator result;
if (b) {
// for XSLIF conditionTests will always be null
enterConditionTrace(i);
result = (actions[i]).iterate(context);
leaveConditionTrace(i);
return result;
}
}
return EmptyIterator.getInstance();
}
private void enterConditionTrace(int i) {
if (LogConfiguration.loggingIsEnabled() && LogController.traceIsEnabled()) {
if(conditionTests != null) {
XSLTTraceListener xlt = (XSLTTraceListener)LogController.getTraceListener();
xlt.enterChooseItem(conditionTests[i]);
}
}
}
private void leaveConditionTrace(int i) {
if (LogConfiguration.loggingIsEnabled() && LogController.traceIsEnabled()) {
if(conditionTests != null) {
XSLTTraceListener xlt = (XSLTTraceListener)LogController.getTraceListener();
xlt.leaveChooseItem(conditionTests[i]);
}
}
}
}
// 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.