/*
* @(#)$Id: PathExpr.java 3619 2008-03-26 07:23:03Z yui $
*
* Copyright 2006-2008 Makoto YUI
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Makoto YUI - initial implementation
*/
package xbird.xquery.expr.path;
import java.io.*;
import java.util.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import xbird.xquery.XQueryException;
import xbird.xquery.dm.value.*;
import xbird.xquery.dm.value.sequence.CompositeSequence;
import xbird.xquery.expr.AbstractXQExpression;
import xbird.xquery.expr.XQExpression;
import xbird.xquery.expr.dyna.ContextItemExpr;
import xbird.xquery.expr.ext.BDQExpr;
import xbird.xquery.expr.opt.*;
import xbird.xquery.expr.path.axis.*;
import xbird.xquery.expr.var.VarRef;
import xbird.xquery.expr.var.Variable;
import xbird.xquery.expr.var.BindingVariable.AnonymousLetVariable;
import xbird.xquery.expr.var.BindingVariable.ForVariable;
import xbird.xquery.meta.*;
import xbird.xquery.misc.TypeUtil;
import xbird.xquery.optim.RewriteInfo;
import xbird.xquery.parser.XQueryParserVisitor;
import xbird.xquery.type.*;
import xbird.xquery.type.node.NodeType;
/**
*
* <DIV lang="en">
* Note: This expression will be pruned after static analysis.
* </DIV>
* <DIV lang="ja"></DIV>
*
* @author Makoto YUI (yuin405+xbird@gmail.com)
*/
public abstract class PathExpr extends AbstractXQExpression implements Externalizable {
private static final long serialVersionUID = 1L;
private static final boolean ENV_DISABLE_INDEX_ACCESS = System.getProperty("xbird.disable_index_access") != null;
private static final int ENV_INDEXING_THRESHOLD = Integer.getInteger("xbird.indexing_threshold", 1);
private static final Log LOG = LogFactory.getLog(PathExpr.class);
// Constants
public static final String ABBREV_ROOT_DESC_NODE = "//";
public static final String ROOT_DESC_NODE = "/descendant-or-self::node()/";
public static final String REVERSE_STEP = "parent::node()";
public static final String ABBREV_REVERSE_STEP = "..";
public static final String FORWARD_STEP = "attribute::";
public static final String ABBREV_FORWARD_STEP = "@";
// Variables
protected/* final */List<XQExpression> _steps;
private XQExpression _analyzedExpr = null;
public PathExpr(List<XQExpression> stepList) {
if(stepList == null || stepList.isEmpty()) {
stepList = Collections.emptyList();
}
this._steps = stepList;
this._type = NodeType.ANYNODE;
}
public PathExpr(XQExpression... steps) {
if(steps.length == 0) {
throw new IllegalArgumentException();
}
final List<XQExpression> list = new LinkedList<XQExpression>();
for(XQExpression step : steps) {
list.add(step);
}
this._steps = list;
this._type = NodeType.ANYNODE;
}
public PathExpr() { // for Externalizable
super();
}
public XQExpression visit(XQueryParserVisitor visitor, XQueryContext ctxt)
throws XQueryException {
return visitor.visit(this, ctxt);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
final int numSteps = in.readInt();
final ArrayList<XQExpression> steps = new ArrayList<XQExpression>(numSteps);
for(int i = 0; i < numSteps; i++) {
XQExpression step = (XQExpression) in.readObject();
steps.add(step);
}
this._steps = steps;
final boolean hasAnalyzedExpr = in.readBoolean();
if(hasAnalyzedExpr) {
this._analyzedExpr = (XQExpression) in.readObject();
}
}
public void writeExternal(ObjectOutput out) throws IOException {
final List<XQExpression> steps = _steps;
final int numSteps = steps.size();
out.writeInt(numSteps);
for(int i = 0; i < numSteps; i++) {
XQExpression step = steps.get(i);
out.writeObject(step);
}
final XQExpression analyzed = _analyzedExpr;
if(analyzed == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
out.writeObject(analyzed);
}
}
public XQExpression setStep(int i, XQExpression step) {
return _steps.set(i, step);
}
public List<XQExpression> getSteps() {
return _steps;
}
public void addPredicate(XQExpression pred) {
int lastidx = _steps.size() - 1;
XQExpression lastStep = _steps.get(lastidx);
if(lastStep instanceof FilterExpr) {
((FilterExpr) lastStep).addPredicate(pred);
} else {
FilterExpr filter = new FilterExpr(lastStep);
filter.addPredicate(pred);
_steps.set(lastidx, filter);
}
}
//--------------------------------------------
// static analysis/dynamic evaluation
public XQExpression staticAnalysis(StaticContext statEnv) throws XQueryException {
this._analyzed = true;
final List<XQExpression> step = _steps;
if(step.isEmpty()) {
return new ContextItemExpr();
}
// simplify PathExpr
simplify();
// pre-static analyse
final int stepsize = step.size();
final int lastpos = stepsize - 1;
for(int i = 0; i < stepsize; i++) {
XQExpression e = step.get(i);
XQExpression analyzed = e.staticAnalysis(statEnv);
if(i != lastpos) {
final Type type = analyzed.getType();
if(type instanceof AtomicType) {
reportError("err:XPTY0019", "Illegal result type was detected in '" + analyzed
+ "', its type: " + type);
}
}
step.set(i, analyzed);
}
// take the first/last StepExpr off from PathExpr
if(stepsize == 1) { // TODO needs review (relative path case, and so on..)
XQExpression last = step.get(0);
if(last instanceof PathExpr) {
throw new IllegalStateException("Unexpected expression class: "
+ last.getClass().getName());
}
this._analyzedExpr = last;
return last;
}
// formal semantics annotation
final boolean annotate = isDistinctDocOrderRequired();
boolean reverse = false;
if(annotate) {
final int size = step.size();
if(size > 1) {
XQExpression last = step.get(size - 1);
if(last instanceof ReverseAxis) {
reverse = true;
}
}
}
// normalize
XQExpression normed = normalize(statEnv);
XQExpression analysed = normed.staticAnalysis(statEnv);
assert (analysed != this);
this._type = analysed.getType();
this._analyzedExpr = analysed;
if(annotate) {
if(LOG.isDebugEnabled()) {
LOG.debug("Applying Distinct-document-order: " + analysed);
}
analysed = applyDistinctDocOrder(analysed, reverse);
}
return analysed;
}
/**
* Rewrite(simplify) PathExpr based on huristic rules.
*/
private void simplify() throws XQueryException {
XQExpression prevStep = null;
final List<XQExpression> step = _steps;
for(int i = 0; i < step.size(); i++) {
XQExpression currStep = step.get(i);
if(i > 0) {
if(prevStep instanceof DescendantOrSelfStep) {
if(currStep instanceof ChildStep) {
NodeTest prevNodeTest = ((DescendantOrSelfStep) prevStep).getNodeTest();
if(NodeTest.ANYNODE.equals(prevNodeTest)) {
// rewrite rule #1
// - '/descendant-or-self::node()/child::` => `/descendant::`
XQExpression removed = step.remove(--i);
assert (removed == prevStep);
NodeTest replacedNodeTest = ((ChildStep) currStep).getNodeTest();
DescendantStep replaced = new DescendantStep(replacedNodeTest);
step.set(i, replaced);
}
} else if(currStep instanceof DescendantOrSelfStep
|| currStep instanceof DescendantStep) {
// rewrite rule #2
// - '/descendant-or-self::node()/descendant-or-self::` => `/descendant-or-self::`
// rewrite rule #3
// - '/descendant-or-self::node()/descendant::` => `/descendant::`
NodeTest prevNodeTest = ((DescendantOrSelfStep) prevStep).getNodeTest();
if(NodeTest.ANYNODE.equals(prevNodeTest)) {
XQExpression removed = step.remove(i - 1);
assert (removed == prevStep);
}
}
}
}
prevStep = currStep;
}
}
/**
* Annotates fn:distinct-docorder.
*
* @link http://www.w3.org/TR/xquery-semantics/#sec_distinct_docorder_or_atomic_sequence
*/
private boolean isDistinctDocOrderRequired() {
XQExpression rawPrevStep = null;
final List<XQExpression> step = _steps;
final int len = step.size();
for(int i = 0; i < len; i++) {
XQExpression curStep = step.get(i);
if(i > 0) {
final boolean sortRequired = sortRequired(rawPrevStep, curStep);
if(sortRequired) {
return true;
}
}
rawPrevStep = curStep;
}
return false;
}
/**
* Apply KNormalization.
*/
private AbstractXQExpression normalize(StaticContext statEnv) throws XQueryException {
final List<XQExpression> step = _steps;
if(step.isEmpty()) {
throw new IllegalStateException();
}
PathVariable var = null;
final int steplen = step.size();
final RewriteInfo info = new RewriteInfo();
boolean indexAccessable = ENV_DISABLE_INDEX_ACCESS ? false : true;
int lastRewritten = -1;
for(int i = 0; i < steplen; i++) {
final XQExpression curStep = step.get(i);
if(indexAccessable) {
indexAccessable &= curStep.isPathIndexAccessable(statEnv, info);
}
if(indexAccessable && info.isLookaheadRequired() && (i + 1) < steplen) {
indexAccessable = false;
int ni = i + 1;
XQExpression nextStep = step.get(ni);
if(nextStep instanceof DescendantStep) {
indexAccessable = nextStep.isPathIndexAccessable(statEnv, info);
if(indexAccessable) {
FilteredPathIndexAcccessExpr filtered = new FilteredPathIndexAcccessExpr(info, curStep.getType());
var = PathVariable.create(filtered, statEnv, false);
step.set(ni, var);
lastRewritten = ni;
++i;
continue;
}
}
}
if(i == 0) {
var = PathVariable.create(curStep, statEnv, false);
} else {
if(indexAccessable) {
boolean isFilter = (curStep instanceof FilterExpr);
// If filtered, no more index access is enabled for the time being
indexAccessable = !isFilter;
if(i >= ENV_INDEXING_THRESHOLD) {
if(isFilter) {
var = PathVariable.create(curStep, statEnv, false);
} else {
PathIndexAccessExpr idxExpr = new PathIndexAccessExpr(info, curStep.getType());
var = PathVariable.create(idxExpr, statEnv, true);
}
lastRewritten = i;
} else {
final CompositePath cp = new CompositePath(var, curStep);
var = PathVariable.create(cp, statEnv, false);
}
} else {
final CompositePath cp = new CompositePath(var, curStep);
var = PathVariable.create(cp, statEnv, false);
}
}
step.set(i, var);
}
for(int i = 0; i < lastRewritten; i++) {
step.remove(0);
}
assert (var != null);
return var;
}
public Sequence<? extends Item> eval(Sequence<? extends Item> contextSeq, DynamicContext dynEnv)
throws XQueryException {
if(_analyzedExpr == null) {
throw new IllegalStateException("PathExpr should be pruned.");
}
return _analyzedExpr.eval(contextSeq, dynEnv);
}
private static XQExpression applyDistinctDocOrder(XQExpression e, boolean reverse) {
return new DistinctSortExpr(e, reverse);
}
/**
* If the input is a sequence of nodes, is sorts those nodes by document order
* and removes duplicates.
*
* @link http://www.w3.org/TR/xquery-semantics/#sec_distinct_docorder_or_atomic_sequence
*/
private static boolean sortRequired(XQExpression prev, XQExpression curr) {
assert (prev != null && curr != null);
// Note that sorting by document order enforces the restriction
// that input and output sequences contains only nodes,
// and that the last step in a path expression may actually return atomic values.
Type currType = curr.getType();
if(TypeUtil.subtypeOf(currType, TypeRegistry.safeGet("node()*"))) {
if(prev instanceof FilterExpr) {
return false;
} else if(prev instanceof AxisStep && curr instanceof AxisStep) {
int prevAxisKind = ((AxisStep) prev).getAxisKind();
int currAxisKind = ((AxisStep) curr).getAxisKind();
switch(currAxisKind) {
case AxisStep.CHILD:
case AxisStep.ATTR:
return false;
default:
break;
}
switch(prevAxisKind) {
case AxisStep.CHILD:
if(currAxisKind == AxisStep.CHILD || currAxisKind == AxisStep.SELF) {
return false;
}
break;
case AxisStep.FOLLOWING_SIBLING:
case AxisStep.PRECEDING_SIBLING:
if(currAxisKind == AxisStep.CHILD || currAxisKind == AxisStep.DESC
|| currAxisKind == AxisStep.DESC_OR_SELF
|| currAxisKind == AxisStep.SELF) {
return false;
}
break;
case AxisStep.DESC:
case AxisStep.DESC_OR_SELF:
case AxisStep.FOLLOWING:
case AxisStep.PRECEDING:
if(currAxisKind == AxisStep.SELF) { /* || currAxisKind == AxisStep.CHILD */
return false;
}
break;
case AxisStep.PARENT:
case AxisStep.SELF:
return false;
case AxisStep.ANCESTOR:
case AxisStep.ANCESTOR_OR_SELF:
if(currAxisKind == AxisStep.CHILD // TODO REVIEW
|| currAxisKind == AxisStep.FOLLOWING_SIBLING
|| currAxisKind == AxisStep.PRECEDING_SIBLING
|| currAxisKind == AxisStep.PARENT || currAxisKind == AxisStep.SELF) {
return false;
}
break;
case AxisStep.ATTR:
return false;
default:
throw new IllegalStateException("Invalid axis type: " + prevAxisKind);
}
return true;
} else {
return false;
}
}
return false;
}
//--------------------------------------------
// helpers
public static final class CompositePath extends AbstractXQExpression {
private static final long serialVersionUID = 303229194702711722L;
private final Variable srcVar;
private XQExpression filterExpr;
public CompositePath(Variable srcVar, XQExpression filterExpr) {
if(filterExpr instanceof CompositePath) {
throw new IllegalStateException();
}
this.srcVar = srcVar;
this.filterExpr = filterExpr;
}
public XQExpression visit(XQueryParserVisitor visitor, XQueryContext ctxt)
throws XQueryException {
return visitor.visit(this, ctxt);
}
public Variable getSourceVariable() {
return srcVar;
}
public XQExpression getSourceExpr() {
return srcVar.getValue();
}
public XQExpression getFilterExpr() {
return filterExpr;
}
public void setFilterExpr(XQExpression filter) {
this.filterExpr = filter;
}
public XQExpression staticAnalysis(StaticContext statEnv) throws XQueryException {
if(!_analyzed) {
this._analyzed = true;
// #1 static type analysis
//srcVar.staticAnalysis(statEnv);
final XQExpression analysedFilter = filterExpr.staticAnalysis(statEnv);
final XQExpression arrangedExpr = rewrite(srcVar, analysedFilter, statEnv);
if(arrangedExpr != null) {
return arrangedExpr.staticAnalysis(statEnv);
} else {
XQExpression prev = srcVar.getValue();
srcVar.staticAnalysis(statEnv);
XQExpression curr = srcVar.getValue();
if(curr != prev) {
this._analyzed = false;
return this.staticAnalysis(statEnv);
}
}
this.filterExpr = analysedFilter;
this._type = analysedFilter.getType();
}
return this;
}
private static XQExpression rewrite(final Variable srcVar, final XQExpression filterExpr, final StaticContext statEnv)
throws XQueryException {
if(srcVar instanceof ForVariable) {
return null; //TODO REVIEWME
}
final XQExpression srcExpr = srcVar.getValue();
if(srcExpr instanceof VarRef) {
VarRef ref = (VarRef) srcExpr;
Variable var = ref.getValue();
assert (var != null);
return rewrite(var, filterExpr, statEnv);
} else if(srcExpr instanceof Variable) {
Variable var = (Variable) srcExpr;
return rewrite(var, filterExpr, statEnv);
} else if(srcExpr instanceof BDQExpr) {
final BDQExpr distExpr = (BDQExpr) srcExpr;
final XQExpression bodyExpr = distExpr.getBodyExpression();
if(bodyExpr instanceof PathVariable) {
PathVariable bodyVar = (PathVariable) bodyExpr;
CompositePath optExpr = new CompositePath(bodyVar, filterExpr);
distExpr.setBodyExpression(optExpr);
return distExpr;
} else {
AnonymousLetVariable wrappedVar = new AnonymousLetVariable(bodyExpr);
CompositePath optExpr = new CompositePath(wrappedVar, filterExpr);
distExpr.setBodyExpression(optExpr);
return distExpr;
}
}
return null;
}
public CompositeSequence<XQNode> eval(Sequence<? extends Item> contextSeq, DynamicContext dynEnv)
throws XQueryException {
final Sequence<? extends Item> src = srcVar.eval(contextSeq, dynEnv);
final CompositeSequence<XQNode> cs = new CompositeSequence<XQNode>(src, filterExpr, dynEnv);
return cs;
}
}
}