| /******************************************************************************* |
| * 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; |
| } |
| } |