org.exist.xquery.PathExpr Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of exist-core Show documentation
Show all versions of exist-core Show documentation
eXist-db NoSQL Database Core
/*
* eXist-db Open Source Native XML Database
* Copyright (C) 2001 The eXist-db Authors
*
* [email protected]
* http://www.exist-db.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.exist.xquery;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.dom.persistent.DocumentSet;
import org.exist.dom.persistent.VirtualNodeSet;
import org.exist.xquery.util.ExpressionDumper;
import org.exist.xquery.value.*;
import org.xmldb.api.base.CompiledExpression;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* PathExpr is just a sequence of XQuery/XPath expressions, which will be called
* step by step.
*
* @author Wolfgang Meier
* @author perig
* @author ljo
*/
public class PathExpr extends AbstractExpression implements CompiledXQuery,
CompiledExpression, RewritableExpression {
protected final static Logger LOG = LogManager.getLogger(PathExpr.class);
protected final List steps = new ArrayList<>();
private boolean staticContext = false;
protected boolean inPredicate = false;
protected Expression parent;
public PathExpr(final XQueryContext context) {
super(context);
}
/**
* Add an arbitrary expression to this object's list of child-expressions.
*
* @param expression An expression to add to this path
*/
public void add(final Expression expression) {
steps.add(expression);
}
/**
* Add all the child-expressions from another PathExpr to this object's
* child-expressions.
*
* @param path A path to concatenate with this path
*/
public void add(final PathExpr path) {
steps.addAll(path.steps);
}
/**
* Add another PathExpr to this object's expression list.
*
* @param path A path to add to this path
*/
public void addPath(final PathExpr path) {
steps.add(path);
}
/**
* Add a predicate expression to the list of expressions. The predicate is
* added to the last expression in the list.
*
* @param predicate A predicate to add to the path as the last expression
*/
public void addPredicate(final Predicate predicate) {
if (!steps.isEmpty()) {
final Expression e = steps.get(steps.size() - 1);
if (e instanceof Step) {
((Step) e).addPredicate(predicate);
}
}
}
/* RewritableExpression API */
/**
* Replace the given expression by a new expression.
*
* @param oldExpr the old expression
* @param newExpr the new expression to replace the old
*/
@Override
public void replace(final Expression oldExpr, final Expression newExpr) {
final int idx = steps.indexOf(oldExpr);
if (idx < 0) {
if (LOG.isTraceEnabled()) {
LOG.trace("Expression not found when trying to replace: {}; in: {}", ExpressionDumper.dump(oldExpr), ExpressionDumper.dump(this));
}
return;
}
steps.set(idx, newExpr);
}
@Override
public Expression getPrevious(final Expression current) {
final int idx = steps.indexOf(current);
if (idx > 1) {
return steps.get(idx - 1);
}
return null;
}
@Override
public Expression getFirst() {
return steps.isEmpty() ? null : steps.get(0);
}
@Override
public void remove(final Expression oldExpr) {
final int idx = steps.indexOf(oldExpr);
if (idx < 0) {
if (LOG.isTraceEnabled()) {
LOG.trace("Expression not found when trying to remove: {}; in: {}", ExpressionDumper.dump(oldExpr), ExpressionDumper.dump(this));
}
return;
}
steps.remove(idx);
}
/* END RewritableExpression API */
@Override
public Expression getParent() {
return this.parent;
}
@Override
public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException {
this.parent = contextInfo.getParent();
inPredicate = (contextInfo.getFlags() & IN_PREDICATE) > 0;
unordered = (contextInfo.getFlags() & UNORDERED) > 0;
contextId = contextInfo.getContextId();
for (int i = 0; i < steps.size(); i++) {
// if this is a sequence of steps, the IN_PREDICATE flag
// is only passed to the first step, so it has to be removed
// for subsequent steps
final Expression expr = steps.get(i);
if ((contextInfo.getFlags() & IN_PREDICATE) > 0) {
if (i == 1) {
//take care : predicates in predicates are not marked as such ! -pb
contextInfo.setFlags(contextInfo.getFlags() & (~IN_PREDICATE));
//Where clauses should be identified. TODO : pass bound variable's inputSequence ? -pb
if ((contextInfo.getFlags() & IN_WHERE_CLAUSE) == 0) {
contextInfo.setContextId(Expression.NO_CONTEXT_ID);
}
}
}
if (i > 1) {
contextInfo.setContextStep(steps.get(i - 1));
}
contextInfo.setParent(this);
expr.analyze(contextInfo);
}
}
@Override
public Sequence eval(Sequence contextSequence, final Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
context.getProfiler().start(this);
context.getProfiler().message(this, Profiler.DEPENDENCIES,
"DEPENDENCIES", Dependency.getDependenciesName(this.getDependencies()));
if (contextSequence != null) {
context.getProfiler().message(this, Profiler.START_SEQUENCES,
"CONTEXT SEQUENCE", contextSequence);
}
if (contextItem != null) {
context.getProfiler().message(this, Profiler.START_SEQUENCES,
"CONTEXT ITEM", contextItem.toSequence());
}
}
if (contextItem != null) {
contextSequence = contextItem.toSequence();
}
Sequence result = null;
if (steps.size() == 0) {
result = Sequence.EMPTY_SEQUENCE;
} else {
//we will filter out nodes from the contextSequence
result = contextSequence;
Sequence currentContext = contextSequence;
DocumentSet contextDocs = null;
Expression expr = steps.get(0);
if (expr instanceof VariableReference) {
final Variable var = ((VariableReference) expr).getVariable(new AnalyzeContextInfo(parent, 0));
//TOUNDERSTAND : how null could be possible here ? -pb
if (var != null) {
contextDocs = var.getContextDocs();
}
}
//contextDocs == null *is* significant
setContextDocSet(contextDocs);
//To prevent processing nodes after atomic values...
//TODO : let the parser do it ? -pb
boolean gotAtomicResult = false;
Expression prev = null;
for (Expression step : steps) {
prev = expr;
expr = step;
context.getWatchDog().proceed(expr);
//TODO : maybe this could be detected by the parser ? -pb
if (gotAtomicResult && !Type.subTypeOf(expr.returnsType(), Type.NODE)
//Ugly workaround to allow preceding *text* nodes.
&& !(expr instanceof EnclosedExpr)) {
throw new XPathException(this, ErrorCodes.XPTY0019,
"left operand of '/' must be a node. Got '" +
Type.getTypeName(result.getItemType()) + " " +
result.getCardinality().getHumanDescription() + "'");
}
//contextDocs == null *is* significant
expr.setContextDocSet(contextDocs);
// switch into single step mode if we are processing in-memory nodes only
final boolean inMemProcessing = currentContext != null &&
Type.subTypeOf(currentContext.getItemType(), Type.NODE) &&
!currentContext.isPersistentSet();
//DESIGN : first test the dependency then the result
final int exprDeps = expr.getDependencies();
if (inMemProcessing ||
((Dependency.dependsOn(exprDeps, Dependency.CONTEXT_ITEM) ||
Dependency.dependsOn(exprDeps, Dependency.CONTEXT_POSITION)) &&
//A positional predicate will be evaluated one time
//TODO : reconsider since that may be expensive (type evaluation)
!(this instanceof Predicate && Type.subTypeOfUnion(this.returnsType(), Type.NUMBER)) &&
currentContext != null && !currentContext.isEmpty())) {
Sequence exprResult = new ValueSequence(Type.subTypeOf(expr.returnsType(), Type.NODE));
((ValueSequence) exprResult).keepUnOrdered(unordered);
//Restore a position which may have been modified by inner expressions
int p = context.getContextPosition();
final Sequence seq = context.getContextSequence();
for (final SequenceIterator iterInner = currentContext.iterate(); iterInner.hasNext(); p++) {
context.setContextSequencePosition(p, seq);
context.getWatchDog().proceed(expr);
final Item current = iterInner.nextItem();
//0 or 1 item
if (!currentContext.hasMany()) {
exprResult = expr.eval(currentContext, current);
} else {
exprResult.addAll(expr.eval(currentContext, current));
}
}
result = exprResult;
} else {
try {
result = expr.eval(currentContext);
} catch (XPathException ex){
// enrich exception when information is available
if (ex.getLine() < 1 || ex.getColumn() < 1) {
ex.setLocation(expr.getLine(), expr.getColumn());
}
throw ex;
}
}
//TOUNDERSTAND : why did I have to write this test :-) ? -pb
//it looks like an empty sequence could be considered as a sub-type of Type.NODE
//well, no so stupid I think...
if (result != null) {
if (steps.size() > 1 && !(result instanceof VirtualNodeSet) &&
!(expr instanceof EnclosedExpr) && !result.isEmpty() &&
!Type.subTypeOf(result.getItemType(), Type.NODE)) {
gotAtomicResult = true;
}
if (steps.size() > 1 && getLastExpression() instanceof Step) {
// remove duplicate nodes if this is a path
// expression with more than one step
result.removeDuplicates();
}
}
if (!staticContext) {
currentContext = result;
}
}
final boolean allowMixedNodesInReturn;
if(prev != null) {
allowMixedNodesInReturn = prev.allowMixedNodesInReturn() | expr.allowMixedNodesInReturn();
} else {
allowMixedNodesInReturn = expr.allowMixedNodesInReturn();
}
if (gotAtomicResult && result != null && !allowMixedNodesInReturn &&
!Type.subTypeOf(result.getItemType(), Type.ATOMIC)) {
throw new XPathException(this, ErrorCodes.XPTY0018,
"Cannot mix nodes and atomic values in the result of a path expression.");
}
}
if (context.getProfiler().isEnabled()) {
context.getProfiler().end(this, "", result);
}
return result;
}
@Override
public XQueryContext getContext() {
return context;
}
public DocumentSet getDocumentSet() {
return null;
}
/**
* Get the expression.
*
* @param pos the position.
*
* @return the expression.
*
* @deprecated use {@link #getSubExpression(int)}
*/
@Deprecated
public Expression getExpression(final int pos) {
return steps.isEmpty() ? null : steps.get(pos);
}
@Override
public Expression getSubExpression(final int pos) {
return steps.isEmpty() ? null : steps.get(pos);
}
public Expression getLastExpression() {
return steps.isEmpty() ? null : steps.get(steps.size() - 1);
}
/**
* Get the length.
*
* @return the length of the path expression.
*
* @deprecated use {@link #getSubExpressionCount()}
*/
@Deprecated
public int getLength() {
return steps.size();
}
@Override
public int getSubExpressionCount() {
return steps.size();
}
@Override
public boolean allowMixedNodesInReturn() {
if (steps.size() == 1) {
return steps.get(0).allowMixedNodesInReturn();
}
return super.allowMixedNodesInReturn();
}
public void setUseStaticContext(final boolean staticContext) {
this.staticContext = staticContext;
}
@Override
public void accept(final ExpressionVisitor visitor) {
visitor.visitPathExpr(this);
}
@Override
public void dump(final ExpressionDumper dumper) {
Expression next = null;
int count = 0;
for (final Iterator iter = steps.iterator(); iter.hasNext(); count++) {
next = iter.next();
//Open a first parenthesis
if (next instanceof LogicalOp) {
dumper.display('(');
}
if (count > 0) {
if (next instanceof Step) {
dumper.display("/");
} else {
dumper.nl();
}
}
next.dump(dumper);
}
//Close the last parenthesis
if (next instanceof LogicalOp) {
dumper.display(')');
}
}
@Override
public String toString() {
final StringBuilder result = new StringBuilder();
Expression next = null;
if (steps.size() == 0) {
result.append("()");
} else {
int count = 0;
for (final Iterator iter = steps.iterator(); iter.hasNext(); count++) {
next = iter.next();
// Open a first parenthesis
if (next instanceof LogicalOp) {
result.append('(');
}
if (count > 0) {
if (next instanceof Step) {
result.append("/");
} else {
result.append(' ');
}
}
result.append(next.toString());
}
// Close the last parenthesis
if (next instanceof LogicalOp) {
result.append(')');
}
}
return result.toString();
}
@Override
public int returnsType() {
if (steps.size() == 0) {
//Not so simple. ITEM should be re-tuned in some circumstances that have to be determined
return Type.NODE;
}
return steps.get(steps.size() - 1).returnsType();
}
@Override
public Cardinality getCardinality() {
if (steps.size() == 0) {
return Cardinality.EMPTY_SEQUENCE;
}
return (steps.get(steps.size() - 1)).getCardinality();
}
@Override
public int getDependencies() {
int deps = 0;
for (final Expression step : steps) {
deps = deps | step.getDependencies();
}
return deps;
}
public void replaceLastExpression(final Expression s) {
if (steps.size() == 0) {
return;
}
steps.set(steps.size() - 1, s);
}
public String getLiteralValue() {
if (steps.size() == 0) {
return "";
}
final Expression next = steps.get(0);
if (next instanceof LiteralValue) {
try {
return ((LiteralValue) next).getValue().getStringValue();
} catch (final XPathException e) {
//TODO : is there anything to do here ?
}
}
if (next instanceof PathExpr) {
return ((PathExpr) next).getLiteralValue();
}
return "";
}
@Override
public int getLine() {
if (line < 0 && steps.size() > 0) {
return steps.get(0).getLine();
}
return line;
}
@Override
public int getColumn() {
if (column < 0 && steps.size() > 0) {
return steps.get(0).getColumn();
}
return column;
}
@Override
public void setPrimaryAxis(final int axis) {
if (steps.size() > 0) {
steps.get(0).setPrimaryAxis(axis);
}
}
@Override
public int getPrimaryAxis() {
if (steps.size() > 0) {
return steps.get(0).getPrimaryAxis();
}
return Constants.UNKNOWN_AXIS;
}
@Override
public void resetState(final boolean postOptimization) {
super.resetState(postOptimization);
for (Expression step : steps) {
step.resetState(postOptimization);
}
}
@Override
public void reset() {
resetState(false);
}
@Override
public boolean isValid() {
return context.checkModulesValid();
}
@Override
public void dump(final Writer writer) {
final ExpressionDumper dumper = new ExpressionDumper(writer);
dump(dumper);
}
@Override
public void setContext(final XQueryContext context) {
this.context = context;
}
@Override
public Expression simplify() {
if (steps.size() == 1) {
return steps.get(0).simplify();
}
return this;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy