All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lux.xpath.AbstractExpression Maven / Gradle / Ivy

There is a newer version: 1.1.0
Show newest version
package lux.xpath;

import lux.xquery.VariableContext;

/**
 * An abstract XPath or XQuery expression.
 * 
 * This class and its subclasses represent XPath expressions.  Their
 * toString() methods return valid XPath.
 */

public abstract class AbstractExpression implements Visitable {
    
    protected AbstractExpression sup;       // the enclosing (parent) expression, if any
    protected AbstractExpression subs[];    // enclosed (child) expressions, or a 0-length array, or null (do we need to make this consistent?)

    public enum Type {
        PATH_EXPRESSION, PATH_STEP, PREDICATE, BINARY_OPERATION, SET_OPERATION,
        LITERAL, ROOT, DOT, FUNCTION_CALL, SEQUENCE, UNARY_MINUS, SUBSEQUENCE,
        LET, VARIABLE, COMPUTED_ELEMENT, ELEMENT, ATTRIBUTE, TEXT, FLWOR, CONDITIONAL, COMMENT,
        DOCUMENT_CONSTRUCTOR, PROCESSING_INSTRUCTION, SATISFIES, INSTANCE_OF, CASTABLE, TREAT
    }

    private final Type type;
    
    protected AbstractExpression (Type type) {
        this.type = type;
    }

    /** Most types will correspond one-one
     * with a subclass of AbstractExpression, but this
     * enumerated value provides an integer equivalent that should be
     * useful for efficient switch operations, encoding and the like.
     * TODO: determine if this is just a waste of time; we could be using instanceof?
     * @return the type of this expression
     */
    public Type getType () {
        return type;
    }
    
    public void acceptSubs (ExpressionVisitor visitor) {
        for (int i = 0; i < subs.length && !visitor.isDone(); i++) {
            int j = visitor.isReverse() ? (subs.length-i-1) : i;
            AbstractExpression sub = subs[j].accept (visitor);
            if (sub != subs[j]) {
                subs[j]= sub;
            }
        }
    }
    
    /**
     * @return the super (containing) expression, or null if this is the outermost expression in its tree
     */
    public AbstractExpression getSuper() {
        return sup;
    }

    /**
     * @return the sub-expressions of this expression.
     */
    public AbstractExpression [] getSubs() {
        return subs;
    }
    
    protected void setSubs (AbstractExpression ... subExprs) {
        subs = subExprs;
        for (AbstractExpression sub : subs) {
            sub.sup = this;
        }
    }

    /** Each subclass must implement the toString(StringBuilder) method by
     * appending itself as a syntatically valid XPath/XQuery expression in
     * the given buffer.
     * @param buf the buffer to append to
     */
    public abstract void toString(StringBuilder buf);

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder();
        toString (buf);
        return buf.toString();
    }

    /**
     * @return the root of this expression: this will either be a Root(/), a function returning document nodes, 
     * or null.
     */
    public AbstractExpression getRoot () {
       return null; 
    }
    
    /**
     * @return whether this expression is a Root or another expression that introduces
     * a new query scope, such as a PathExpression beginning with a Root (/), or a subsequence
     * of another absolute expression.  This method returns false, supplying the common default.
     */
    public boolean isAbsolute() {
        return getRoot() != null;
    }
    
    /**
     * @return whether this expression is proven to return results in document order.  This method 
     * returns true iff all its subs return true, or it has none.  Warning: incorrect results may occur if 
     * document-ordering is falsely asserted.
     */
    public boolean isDocumentOrdered() {
        if (subs != null) {
            for (AbstractExpression sub : subs) {
                if (!sub.isDocumentOrdered())
                    return false;
            }
        }
        return true;
    }
    
    /** 
     * If this has absolute subexpressions, replace them with the replacement expression
     * (see {@link Root#replaceRoot(AbstractExpression)}
     * @param replacement the expression to use in place of '/'
     * @return this 
     */
    public AbstractExpression replaceRoot(AbstractExpression replacement) {
        if (subs != null) {
            for (int i = 0; i < subs.length; i++) {
                AbstractExpression replaced = subs[i].replaceRoot(replacement);
                if (replaced != subs[i]) {
                    subs[i] = replaced;
                }
            }
        }
        return this;
    }
    
    /**
     * append the sub-expression to the buffer, wrapping it in parentheses if its precedence is
     * lower than or equal to this expression's.  We need parens when precedence is equal because
     * otherwise operations simply group left or right, but we have the actual grouping encoded 
     * in the expression tree and need to preserve that.
     * 
     * Note: we can't just blindly wrap everything in parentheses because parens have special meaning
     * in some XPath expressions where they can introduce document-ordering.
     * 
     * @param buf the buffer to append to
     * @param sub the sub-expression
     */
    protected void appendSub(StringBuilder buf, AbstractExpression sub) {
        if (sub.getPrecedence() <= getPrecedence()) {
            buf.append ('(');
            sub.toString(buf);
            buf.append (')');            
        } else {
            sub.toString(buf);
        }
    }
    
    /**
     * @return the head of this expression; ie the leftmost path sub-expression, which is just this 
     * expression (except for PathExpressions).
     */
    public AbstractExpression getHead() {
        return this;
    }
    
    /**
     * @return the tail of this expression; ie everything after the head is removed, which is null 
     * unless this is a PathExpression {@link PathExpression#getTail}.
     */
    public AbstractExpression getTail() {
        return null;
    }

    /**
     * This method is called by the optimizer in order to determine an element or attribute QName (or wildcard) against which 
     * some expression is being compared, in order to generate an appropriate text query.
     * @return the rightmost path step in the context of this expression.
     */
    public AbstractExpression getLastContextStep () {
        return this;
    }
    
    /**
     * If this expression depends "directly" on a variable, return that variable's binding context: a for or let clause,
     * or a global variable definition. This recurses through variables, so if there are aliases it retrieves the ultimate
     * context.  We need to define directly dependent precisely; what it's used for is to determine with an order by 
     * expression is dependent on a for-variable, and ultimately whether an order by optimization can be applied. 
     * @return the binding context of the variable on which this expression depends, or null
     */
    public VariableContext getBindingContext () {
        return null;
    }

    /**
     * @return a number indicating the *outer* precedence of this expression.
     * Expressions with lower precedence numbers have lower
     * precedence, ie bind more loosely, than expressions with higher
     * precedence. Expressions with no sub-expressions are assigned a high
     * precedence.  Complex expressions can be seen as having an inner and an outer
     * precedence; for example function call expressions behave as a comma with regard 
     * to their sub-expressions, the arguments, and like parentheses to their enclosing expression.
     */
    public abstract int getPrecedence ();

    /**
     * @param other another expression
     * @return whether the two expressions are of the same type and share the same local properties
     */
    public boolean equivalent (AbstractExpression other) {
        if (other == this) {
            return true;
        }
        if (other == null) {
            return false;
        }
        if (! (getClass().isAssignableFrom(other.getClass()))) {
            return false;
        }
        return propEquals ((AbstractExpression) other);
    }

    /**
     * @param other another expression
     * @return whether this expression is query-geq (fgreater-than-or-equal) to the other, in the sense
     * that for all contexts c, exists(other|c) => exists(this|c). In particular, this implementation tests that 
     * the two expressions are of the same type and have local properties consistent with geq, by calling
     * propGreaterEqual.
     */
    public boolean geq (AbstractExpression other) {
        if (other == this) {
            return true;
        }
        if (other == null) {
            return false;
        }
        if (! (getClass().isAssignableFrom(other.getClass()))) {
            return false;
        }
        return propGreaterEqual ((AbstractExpression) other);
    }
    
    /**
    	Traverse downwards, comparing with fromExpr for equivalence until one bottoms out,
    	ignoring fromExpr (since it has already been checked).
    	@param fromExpr
    	@param fieldExpr
    	@return whether fieldExpr >= queryExpr
     */
    public boolean matchDown (AbstractExpression fieldExpr, AbstractExpression fromExpr) {
    	if (fieldExpr == fromExpr) {
    		return true;
    	}
    	if (! fieldExpr.geq(this)) {
    		// if fieldExpr does not encompass this at least formally, it is too restrictive
    		return false;
		}
		// all of queryExpr's subs *must* return a value (for a
		// non-empty result), so a necessary condition for fieldExpr
		// >= queryExpr is that every sub of fieldExpr match *some*
		// sub of queryExpr
		AbstractExpression[] fsubs = fieldExpr.getSubs();
		if (fsubs == null) {
			return subs == null || subs.length == 0 || isRestrictive();
		}
		AbstractExpression qsubMatched = null;
		OUTER: for (AbstractExpression fsub : fsubs) {
			if (fsub == fromExpr) {
				continue;
			}
			for (AbstractExpression sub : subs) {
				if (sub.matchDown(fsub, null)) {
					qsubMatched = sub;
					continue OUTER;
				}
			}
			// no equivalent sub found
			return false;
		}
		if (!isRestrictive()) {
			// at least one of queryExpr's children must return a value, so
			// in addition it is necessary that every child of queryExpr be
			// matched by some child of fieldExpr
			OUTER: for (AbstractExpression sub : subs) {
				if (sub == qsubMatched) {
					continue;
				}
				for (AbstractExpression fsub : fsubs) {
					if (sub.matchDown(fsub, null)) {
						continue OUTER;
					}
				}
				return false;
			}
		}
		return true;
	}

    /**
     * @return a hashcode that is consistent with {@link #equivalent(AbstractExpression)}
     */
    int equivHash () {
        return type.ordinal();
    }
    
    /**
     * @param oex another expression
     * @return whether the other expression and this one have all the same local properties
     */
    protected boolean propEquals (AbstractExpression oex) {
        return (oex.getType() == type);
    }
    
    /**
     * @param oex another expression of the same type as this
     * @return whether the expressions' properties imply: this ge oex
     */
    public boolean propGreaterEqual (AbstractExpression oex) {
        return propEquals(oex);
    }
    
    public boolean deepEquals (AbstractExpression oex) {
    	if (! equivalent(oex)) {
    		return false;
    	}
        if (subs == oex.subs) {
        	return true;
        }
        if (subs == null || oex.subs == null) {
        	return false;
        }
        if (subs.length != oex.subs.length) {
        	return false;
        }
        for (int i = 0; i < subs.length; i++) {
        	if (! (subs[i].deepEquals(oex.subs[i]))) {
        		return false;
        	}
        }
        return true;
    }

    /**
     * An expression is restrictive when any empty sub implies the expression is empty.
     * In other words, restrictive expressions only return results when all of their 
     * subs are non-empty.  Eg: and, intersect, predicate, path step.
     * @return whether this expression is restrictive
     */
    public boolean isRestrictive () {
        return false;
    }

}

/* 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/. */




© 2015 - 2024 Weber Informatics LLC | Privacy Policy