blob: 29cc027ef8c3cc43b407d95ac5a7b63bcc8f2886 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006 Oracle Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Oracle Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.bpel.validator.xpath;
/**
* Java JDK dependencies ...
*/
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import javax.xml.namespace.QName;
/**
* Dependency on the validation model
*/
import org.eclipse.bpel.validator.model.IConstants;
import org.eclipse.bpel.validator.model.IModelQuery;
import org.eclipse.bpel.validator.model.IModelQueryLookups;
import org.eclipse.bpel.validator.model.INode;
import org.eclipse.bpel.validator.model.IProblem;
/**
* Dependency on XPath 1.0 semantics tree model
*/
import org.eclipse.bpel.xpath10.*;
/**
* Dependency on the ParserTool
*/
/**
* This XPathExprValidationVisitor performs minimal statical analysis on
* the XPath expression that is given to it.
* <p>
* Java types are used and mapped onto XPath types in the following way:
* <ol>
* <li> String.class is XPath literal
* <li> Number.class is XPath number
* <li> Boolean.class is XPath boolean
* <li> List.class is a NodeSet
* <li> Object.class is a Node
* </ol>
*
* @author Michal Chmielewski (michal.chmielewski@oracle.com)
* @author Alex Yiu (alex.yiu@oracle.com)
*
* @date Sep 29, 2006
*
*/
@SuppressWarnings({"nls","boxing"})
public class XPathVisitor {
// NamespaceContext mNamespaceContext;
/** The XPathExpression Validator that is running this */
XPathValidator mValidator ;
/** The IModelQuery to ask certain things of the model */
IModelQuery mModelQuery;
/** The context node on which the expression is attached to */
INode mContextNode ;
// The four types that XPath evaluates into.
/** An object representing an XPath boolean */
// static final public Object BOOLEAN = new String("#boolean");
// static final public Object NUMBER = new String("#number");
// static final public Object TEXT = new String("#literal");
// static final public Object Collections.EMPTY_LIST = new String("#node-set");
/** For static checking */
Stack<Object> mContext = new Stack<Object>();
/**
* Create a brand new shiny XPathExpression visitor. It is used to visit
* the parsed XPath expression nodes and perform some static analysis on the
* expression.
*
* @param validator the XPath expression validator.
*/
public XPathVisitor ( XPathValidator validator )
{
/** The XPath Expression validator */
mValidator = validator;
/** The access to the model data */
mModelQuery = validator.getModelQuery();
/** The context INode */
mContextNode = mValidator.getNode();
}
protected void visit (PathExpr expr) {
mValidator.runRules("pathexpr",expr);
visit ( expr.getFilterExpr() );
Object last = contextPeek();
if (isSimpleType(last)) {
}
visit( expr.getLocationPath() );
}
protected void visit (LocationPath expr ) {
mValidator.runRules("location",expr);
visitList( expr.getSteps() );
}
protected void visit (FilterExpr expr) {
visit(expr.getExpr());
visitList( expr.getPredicates() );
}
protected void visit (BinaryExpr expr) {
visit(expr.getLHS());
Object lhsType = contextPop();
visit(expr.getRHS());
Object rhsType = contextPop();
if (expr instanceof LogicalExpr || expr instanceof RelationalExpr || expr instanceof EqualityExpr) {
mContext.push( true );
} else {
mContext.push( 0.0 );
}
}
protected void visit(UnaryExpr expr) {
visit(expr.getExpr());
Object obj = contextPop();
if (obj instanceof Number) {
mContext.push( ((Number)obj).doubleValue() * (-1) );
} else {
mContext.push(obj);
}
}
protected void visit(UnionExpr expr) {
visit(expr.getLHS());
Object lhs = contextPop();
visit(expr.getRHS());
Object rhs = contextPop();
// the union expression must be between Collections.EMPTY_LISTS.
IProblem problem ;
if ( isSimpleType(rhs) ) {
problem = mValidator.createError();
problem.fill("XPATH_NOT_A_Collections.EMPTY_LIST", //$NON-NLS-1$
0, expr.getLHS().getText() );
}
if (isSimpleType(lhs)) {
problem = mValidator.createError();
problem.fill("XPATH_NOT_A_Collections.EMPTY_LIST", //$NON-NLS-1$
1,expr.getLHS().getText() );
}
mContext.push( Collections.EMPTY_LIST );
}
protected void visit (NumberExpr expr) {
mContext.push( expr.getNumber() );
}
protected void visit (LiteralExpr expr) {
mContext.push( expr.getLiteral() );
}
@SuppressWarnings("nls")
protected void visit (VariableReferenceExpr expr) {
mValidator.runRules("variables",expr);
}
protected void visit (FunctionCallExpr expr) {
mValidator.runRules("functions",expr);
// TODO: Try to figure out what functions return. Most likely these would be
// node-sets, but the meta data could be more specific in telling us what type
// does a function return.
mContext.push( Collections.EMPTY_LIST );
// Now process the arguments
visitList( expr.getParameters() );
}
/**
* Steps generally involve "stepping" over the elements in the query.
*
* For this we use the value that was last on the stack as a reference point
* to evaluate the "next step". We query the model to make sure that the "next
* step" makes sense until such point that it either does and we are done
* or it stops making sense at which point we abandon the effort.
*/
protected void visit (NameStep step) {
String prefix = step.getPrefix();
IProblem problem;
Object last = contextPop();
// We throw our hands in the air when the prefix does not resolve
// and we are being asked to do a name step on an axis that we don't support
// in static checking.
if ( mValidator.checkPrefix ( prefix , step.getLocalName() ) == false) {
mContext.push(Collections.EMPTY_LIST);
return ;
}
if ( last instanceof List ) {
mContext.push( Collections.EMPTY_LIST );
} else if (last instanceof INode) {
// walk the step
INode context = (INode) last;
int axis = step.getAxis();
String nsURI = mValidator.lookupNamespace(prefix);
QName qname = new QName(nsURI,step.getLocalName(),prefix != null ? prefix : "");
boolean notChecked = false;
INode result = null;
switch (axis) {
case Axis.CHILD :
result = mModelQuery.lookup (context,IModelQueryLookups.LOOKUP_NODE_NAME_STEP , qname );
break;
case Axis.ATTRIBUTE :
result = mModelQuery.lookup (context,IModelQueryLookups.LOOKUP_NODE_NAME_STEP_ATTRIBUTE, qname);
break;
case Axis.PARENT :
result = mModelQuery.lookup(context, IModelQueryLookups.LOOKUP_NODE_NAME_STEP_PARENT, qname);
break;
case Axis.DESCENDANT :
result = mModelQuery.lookup(context, IModelQueryLookups.LOOKUP_NODE_NAME_STEP_DESCENDANT, qname);
break;
case Axis.DESCENDANT_OR_SELF :
result = mModelQuery.lookup(context, IModelQueryLookups.LOOKUP_NODE_NAME_STEP_DESCENDANT_OR_SELF, qname);
break;
/** These are un-implemented */
case Axis.ANCESTOR :
case Axis.ANCESTOR_OR_SELF :
case Axis.FOLLOWING :
case Axis.FOLLOWING_SIBLING :
case Axis.PRECEDING :
case Axis.PRECEDING_SIBLING :
case Axis.SELF :
case Axis.NAMESPACE :
case Axis.INVALID_AXIS :
notChecked = true;
break;
}
if (result != null && result.isResolved()) {
mContext.push(result);
} else {
mContext.push(Collections.EMPTY_LIST);
if (notChecked) {
// information and ignore
problem = mValidator.createWarning();
problem.fill("XPATH_AXIS_NOT_CHECKED", //$NON-NLS-1$
step.getText()
);
} else {
mContext.push(Collections.EMPTY_LIST);
// information and ignore
problem = mValidator.createError();
problem.fill("XPATH_NAME_STEP", //$NON-NLS-1$
step.getText()
);
}
}
} else {
problem = mValidator.createError();
problem.fill("XPATH_NAME_STEP",
step.getText() );
mContext.push(Collections.EMPTY_LIST);
}
// predicates work on the currently selected node as the context node.
if (mContext.size() > 0) {
mContext.push(mContext.peek());
}
visitList(step.getPredicates());
contextPop();
}
protected void visit(ProcessingInstructionNodeStep step) {
visitList(step.getPredicates());
}
protected void visit(AllNodeStep step) {
if (mContext.size() > 0) {
mContext.pop();
}
mContext.push(Collections.EMPTY_LIST);
visitList(step.getPredicates());
}
protected void visit (TextNodeStep step) {
visitList(step.getPredicates());
contextPop();
mContext.push ( new String("text()") );
}
protected void visit (CommentNodeStep step) {
visitList(step.getPredicates());
contextPop();
mContext.push (new String("comment()")) ;
}
protected void visit (Predicate predicate) {
visit(predicate.getExpr());
Object result = contextPop();
// Predicates expressions are either boolean or numeric types.
if (result instanceof Boolean || result instanceof Number ) {
// warn about predicates.
}
}
void visitList ( List<?> list) {
for(Object next: list) {
visit(next);
}
}
/**
* @param obj
*/
public void visit ( Object obj ) {
if (obj instanceof PathExpr) {
visit((PathExpr) obj);
} else if (obj instanceof LocationPath) {
visit((LocationPath) obj);
} else if (obj instanceof BinaryExpr) {
visit((BinaryExpr) obj);
} else if (obj instanceof FilterExpr) {
visit((FilterExpr)obj);
} else if (obj instanceof UnaryExpr) {
visit((UnaryExpr)obj);
} else if (obj instanceof NumberExpr) {
visit((NumberExpr)obj);
} else if (obj instanceof LiteralExpr) {
visit((LiteralExpr)obj);
} else if (obj instanceof VariableReferenceExpr) {
visit((VariableReferenceExpr)obj);
} else if (obj instanceof FunctionCallExpr) {
visit((FunctionCallExpr) obj);
} else if (obj instanceof List) {
visitList((List<?>) obj);
} else if (obj instanceof NameStep) {
visit((NameStep)obj);
} else if (obj instanceof ProcessingInstructionNodeStep) {
visit((ProcessingInstructionNodeStep) obj);
} else if (obj instanceof AllNodeStep) {
visit((AllNodeStep)obj);
} else if (obj instanceof TextNodeStep) {
visit((TextNodeStep)obj);
} else if (obj instanceof CommentNodeStep) {
visit((CommentNodeStep)obj);
} else if (obj instanceof Predicate) {
visit((Predicate)obj);
} else {
// ignore or throw error ... ?
}
}
/**
* Private methods follow
*/
protected Object contextPush ( Object obj ) {
return mContext.push( obj );
}
protected Object contextPeek () {
return mContext.size() > 0 ? mContext.peek() : null;
}
protected Object contextPop () {
return mContext.size() > 0 ? mContext.pop() : null;
}
static boolean isSimpleType ( Object obj ) {
if (obj == null) {
return false;
}
return (obj instanceof Number || obj instanceof String ||
obj instanceof Boolean );
}
static boolean isBPELNS ( String namespaceUri ) {
return IConstants.XMLNS_BPEL.equals(namespaceUri) ||
IConstants.XMLNS_BPEL20_OLD.equals(namespaceUri);
}
/**
* Returns true if the string is either null or contains just whitespace.
*/
static boolean isEmptyOrWhitespace( String value )
{
if( value == null || value.length() == 0) {
return true;
}
for( int i = 0, j = value.length(); i < j; i++ )
{
if( ! Character.isWhitespace( value.charAt(i) ) ) {
return false;
}
}
return true;
}
/**
* Test to see if a string is empty or has a value that is empty.
*
* @param value
* @return true if empty or null, false otherwise.
*/
static boolean isEmpty ( String value ) {
return value == null || value.length() == 0;
}
}