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

/**
 * Validator BPEL model dependency
 */

import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;

import javax.xml.XMLConstants;
import javax.xml.namespace.QName;

import org.eclipse.bpel.validator.model.IConstants;
import org.eclipse.bpel.validator.model.IFunctionMeta;
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;
import org.eclipse.bpel.validator.model.ARule;
import org.eclipse.bpel.validator.model.Validator;
import org.eclipse.bpel.validator.tools.ParserTool;

import org.eclipse.bpel.xpath10.*;
import org.eclipse.bpel.xpath10.parser.XPath10Exception;
import org.eclipse.bpel.xpath10.parser.XPath10Factory;


/**
 * This validator validates the XPath expressions used in the BPEL source.
 * <p>
 * The validation consists of 2 steps:
 * <ul>
 * <li>Compilation and syntax check,and
 * <li>Static analysis on the expression
 * </ul>
 * <p>
 * The static analysis is performed by visiting the parsed expression tree and
 * checking certain things against the actual BPEL model.
 * <p>
 * The nodes which may contain XPath expressions in BPEL 2.0 and their expected
 * type is as follows:
 * 
 * <ul>
 * <li> branches - unsigned integer
 * <li> condition - boolean
 * <li> finalCounterValue - unsigned integer
 * <li> for - duration
 * <li> from - any
 * <li> joinCondition - boolean
 * <li> repeatEvery - duration
 * <li> startCounterValue - unsigned integer
 * <li> to - variable reference expression ($var/foo)
 * <li> transitionCondition - boolean
 * <li> until - deadline
 * 
 * </ul>
 * 
 * 
 * <p>
 * 
 * @author Michal Chmielewski (michal.chmielewski@oracle.com)
 * @date Sep 27, 2006
 * 
 */

@SuppressWarnings( { "nls", "boxing" })
public class XPathValidator extends Validator {

	/** The string format of the XPath expression */
	String exprString;

	String exprStringTrimmed;

	/** the parsed XPath expression */
	Expr xpathExpr;

	/** For static analysis */
	XPathVisitor mVisitor;

	/** expression name by node name */
	protected String fExprByNode;

	/** parent node */
	protected INode fParentNode;

	/**
	 * Start the validation of this XPathExpression
	 */

	@Override
	public void start() {
		super.start();

		exprString = mModelQuery.lookup(mNode,
				IModelQueryLookups.LOOKUP_TEXT_TEXT, null, null);

		exprStringTrimmed = exprString.trim();
	
		fParentNode = mNode.parentNode();

		/** fExprByNode is a key to a localization map */
		fExprByNode = "text.node." + mNode.nodeName().getLocalPart();
	}

	/**
	 * Do a quick sanity check.
	 */

	public void rule_SanityCheck_1() {
		IProblem problem;

		if (isEmptyOrWhitespace(exprString)) {
			problem = createError();
			problem.fill("XPATH_EMPTY_EXPRESSION", toString(mNode.nodeName()), fExprByNode);
			exprString = null;

			disableRules();
		}
	}

	/**
	 * Check syntax of this expression ...
	 * 
	 */

	public void rule_CheckExpressionSyntax_2() {

		if (exprString == null) {
			return;
		}

		IProblem problem;

		try {
			xpathExpr = XPath10Factory.create( exprString );			
		} catch (XPath10Exception e) {
			problem = createError();
			problem.fill("XPATH_EXPRESSION_SYNTAX", toString(mNode.nodeName()) ,
					exprStringTrimmed, fExprByNode, e.getMessage());
			repointOffsets(problem, e.getPosition(), e.getPosition() + 3);
			// TODO: Position in the expression ... ?
			disableRules();
		}
	}

	protected void repointOffsets(IProblem problem, Expr expr) {
		if (expr.getPosition() < 0 || expr.getEndPosition() < 0) {
			return;
		}
		repointOffsets(problem, expr.getPosition(), expr.getEndPosition());
	}

	/**
	 * @param problem
	 * @param offset
	 * @param offsetEnd
	 */
	protected void repointOffsets(IProblem problem, int offset, int offsetEnd) {

		int charStart = mModelQuery.lookup(mNode,
				IModelQueryLookups.LOOKUP_NUMBER_CHAR_START_2, -1);
		int charEnd = mModelQuery.lookup(mNode,
				IModelQueryLookups.LOOKUP_NUMBER_CHAR_END_2, -1);

		int lineNo = mModelQuery.lookup(mNode,
				IModelQueryLookups.LOOKUP_NUMBER_LINE_NO_2, -1);
		int columnNo = mModelQuery.lookup(mNode,
				IModelQueryLookups.LOOKUP_NUMBER_COLUMN_NO_2, -1);

		if (charStart < 0 || charEnd < 0) {
			return;
		}

		charStart += offset;
		charEnd += offsetEnd;

		// Technically that should be it, but there are 3 issues:
		// - have to re-point column
		// - have to re-point row
		// - have to adjust for \n in charStart and charEnd (JAXEN issue).

		for (int i = 0, j = Math.min(exprString.length(), offset); i < j; i++) {

			if (exprString.charAt(i) == '\n') {
				// This is a JAXEN problem/hack
				charStart += 1;

				if (lineNo > 0) {
					lineNo += 1;
				}
				columnNo = 0;
			}

			if (columnNo >= 0) {
				columnNo += 1;
			}
		}

		// Now do the same thing for charEnd
		for (int i = 0, j = Math.min(exprString.length(), offsetEnd); i < j; i++) {
			// This is a JAXEN problem/hack
			if (exprString.charAt(i) == '\n') {
				charEnd += 1;
			}
		}

		problem.setAttribute(IProblem.CHAR_START, charStart);
		problem.setAttribute(IProblem.CHAR_END, charEnd);

		if (lineNo > 0) {
			problem.setAttribute(IProblem.LINE_NUMBER, lineNo);
		}
		if (columnNo > 0) {
			problem.setAttribute(IProblem.COLUMN_NUMBER, columnNo);
		}

	}

	/**
	 * Perform static analysis of this XPath expression.
	 * 
	 */
	@ARule(sa = 0, desc = "Create the static analysis visitor for expression analysis", author = "michal.chmielewski@oracle.com", date = "01/30/2007")
	public void rule_StaticXPathExpressionAnalysis_10() {

		mVisitor = new XPathVisitor(this);

		INode typeNode = getValue("expression.type", null);

		if (typeNode != null) {
			mVisitor.contextPush(typeNode);
		}
	}

	/**
	 * Sets the value of the expression result. This is run as the last rule in
	 * pass1 and it examines the top element on the stack that the
	 * visitor/validator combination has produced.
	 * 
	 */

	public void rule_ExpressionType_999999() {

		if (mVisitor == null) {
			return;
		}

		Object obj = mVisitor.contextPop();

		// Type needs to be re-mapped to XSD types that our model understands.

		if (XPathVisitor.isSimpleType(obj)) {

			setValue("expression.value", obj);

			QName typeQName = null;

			if (obj instanceof String) {

				typeQName = new QName(XMLConstants.W3C_XML_SCHEMA_NS_URI,
						"string");

			} else if (obj instanceof Number) {

				Number num = (Number) obj;

				if (num.intValue() == num.doubleValue()) {

					if (mNode.equals(ND_START_COUNTER_VALUE)
							|| mNode.equals(ND_FINAL_COUNTER_VALUE)) {
						if (num.intValue() >= 0) {
							typeQName = new QName(
									XMLConstants.W3C_XML_SCHEMA_NS_URI,
									"unsignedInt");
						}
					}

					if (typeQName == null) {
						typeQName = new QName(
								XMLConstants.W3C_XML_SCHEMA_NS_URI, "integer");
					}
				} else {
					typeQName = new QName(XMLConstants.W3C_XML_SCHEMA_NS_URI,
							"double");
				}

			} else if (obj instanceof Boolean) {

				typeQName = new QName(XMLConstants.W3C_XML_SCHEMA_NS_URI,
						"boolean");

			}

			INode basicType = mModelQuery.lookup(mNode.rootNode(),
					IModelQueryLookups.LOOKUP_NODE_XSD_TYPE, typeQName);

			if (isUndefined(basicType)) {
				setValue("expression.type", null);
			} else {
				setValue("expression.type", basicType);
			}

		} else if (obj instanceof INode) {

			setValue("expression.type", obj);

		} else if (obj instanceof List) {

			// node list, we can't check the types

		} else {
			// everything else we ignore
		}

	}

	/**
	 * Check the deadline expression.
	 */

	public void checkDeadlineExpression() {

		IProblem problem;
		Expr expr = xpathExpr;

		if (expr instanceof LiteralExpr) {
			LiteralExpr lexpr = (LiteralExpr) expr;
			try {
				ParserTool.parseDateAndTime(lexpr.getLiteral());
			} catch (Exception e) {
				problem = createError();
				problem.fill("XPATH_EXPRESSION_SYNTAX", toString(mNode.nodeName()), lexpr
						.getLiteral(), fExprByNode, e.getMessage());
				repointOffsets(problem, expr);
			}
		} else if (expr instanceof FunctionCallExpr) {
			mVisitor.visit((FunctionCallExpr) expr);
		} else {
			problem = createError();
			problem.fill("XPATH_EXPRESSION_TYPE", toString(mNode.nodeName()), exprStringTrimmed,
					fExprByNode);
			repointOffsets(problem, expr);
		}

		disableRules();
	}

	/**
	 * Check the duration expression variant.
	 */

	public void checkDurationExpression() {

		IProblem problem;
		Expr expr = xpathExpr ;
		if (expr instanceof LiteralExpr) {
			LiteralExpr lexpr = (LiteralExpr) expr;
			try {
				ParserTool.parseDuration(lexpr.getLiteral());
			} catch (Exception e) {
				problem = createError();
				problem.fill("XPATH_EXPRESSION_SYNTAX", toString(mNode.nodeName()), lexpr
						.getLiteral(), fExprByNode, e.getLocalizedMessage());
				repointOffsets(problem, expr);
			}
		} else if (expr instanceof FunctionCallExpr) {
			mVisitor.visit((FunctionCallExpr) expr);
		} else {
			problem = createError();
			problem.fill("XPATH_EXPRESSION_TYPE", toString(mNode.nodeName()), exprStringTrimmed,
					fExprByNode);
			repointOffsets(problem, expr);
		}

		disableRules();
	}

	/**
	 * Check boolean expressions
	 */

	public void checkBooleanExpression() {

		Expr expr = xpathExpr ;

		IProblem problem;

		if (expr instanceof LogicalExpr) {
			mVisitor.visit((LogicalExpr) expr);
		} else if (expr instanceof EqualityExpr) {
			mVisitor.visit((EqualityExpr) expr);
		} else if (expr instanceof RelationalExpr) {
			mVisitor.visit((RelationalExpr) expr);
		} else if (expr instanceof FunctionCallExpr) {
			FunctionCallExpr fce = (FunctionCallExpr) expr;

			if (isBooleanFunction(fce) == false) {

				problem = createWarning();
				problem.fill("XPATH_EXPRESSION_TYPE", toString(mNode.nodeName()),
						exprStringTrimmed, fExprByNode);
				repointOffsets(problem, fce);
			}

			mVisitor.visit(fce);

		} else {
			problem = createError();
			problem.fill("XPATH_EXPRESSION_TYPE", toString(mNode.nodeName()), exprStringTrimmed,
					fExprByNode

			);
			repointOffsets(problem, expr);
		}

		// Once validated this type of expression we are done.

		disableRules();
	}

	/**
	 * Check the integer expression.
	 * 
	 * Not this is not a rule.
	 */

	public void checkIntegerExpression() {

		IProblem problem;
		Expr expr = xpathExpr;

		if (expr instanceof MultiplicativeExpr) {
			mVisitor.visit((MultiplicativeExpr) expr);
		} else if (expr instanceof AdditiveExpr) {
			mVisitor.visit((AdditiveExpr) expr);
		} else if (expr instanceof UnaryExpr) {
			mVisitor.visit((UnaryExpr) expr);
		} else if (expr instanceof NumberExpr) {
			mVisitor.visit((NumberExpr) expr);
		} else if (expr instanceof VariableReferenceExpr) {
			mVisitor.visit((VariableReferenceExpr) expr);
		} else {
			problem = createError();
			problem.fill("XPATH_EXPRESSION_TYPE", toString(mNode.nodeName()), exprStringTrimmed,
					fExprByNode);
		}

		disableRules();
	}

	/**
	 * Check function call expression.
	 * 
	 * @param expr
	 */
	@ARule(sa = 1015, desc = "Check functions in XPath expressions", author = "michal.chmielewski@oracle.com", date = "03/02/2007", tag = "functions", order = 1)
	public void CheckFunctions(FunctionCallExpr expr) {

		String functionPrefix = expr.getPrefix();
		String nsURI = lookupNamespace(functionPrefix);

		if (XPathVisitor.isBPELNS(nsURI)) {
			runRules("bpel.functions", expr);
		}

		IFunctionMeta meta = lookup(expr);

		checkFunctionMeta(expr, meta);
		checkFunctionCall(expr, meta);

	}

	/**
	 * Check the GetVariableProperty function.
	 * 
	 * @param expr
	 */

	@ARule(sa = 30, desc = "Arguments to getVariableProperty must be quoted strings", author = "michal.chmielewski@oracle.com", date = "01/29/2007", tag = "bpel.functions")
	public void CheckGetVariableProperty(FunctionCallExpr expr) {

		String fn = expr.getFunctionName();
		if ("getVariableProperty".equals(fn) == false) {
			return;
		}

		List<?> params = expr.getParameters();
		IProblem problem;

		int psize = params.size();
		if (psize != 2) {
			problem = createError();
			int pz = (params.size() - 2 < 0) ? 0 : 1;
			problem.fill("XPATH_FN_ARGS", toString(mNode.nodeName()), fn, expr.getText(), pz, 2);
			repointOffsets(problem, expr);

			if (psize < 1) {
				return;
			}
		}

		for (int i = 0, j = Math.min(2, params.size()); i < j; i++) {
			Expr p = (Expr) params.get(i);
			if ((p instanceof LiteralExpr) == false) {
				problem = createError();
				problem.fill("XPATH_FN_LITERAL_ARGS", toString(mNode.nodeName()), fn, expr
						.getText(), i + 1, p.getText());

				repointOffsets(problem, p);
			}
		}

		// check to see if the variable exists, it must be the 1st argument
		Expr p1 = (Expr) params.get(0);
		if (p1 instanceof LiteralExpr) {
			LiteralExpr p1l = (LiteralExpr) p1;

			// check to make sure we don't print the same message twice.
			if (duplicateThing("duplicate.variable.check.", p1l.getLiteral()) == false) {

				INode variableNode = mModelQuery.lookup(mNode,
						IModelQueryLookups.LOOKUP_NODE_VARIABLE, p1l
								.getLiteral());

				if (isUndefined(variableNode)) {
					problem = createError();
					problem.fill("XPATH_UNDEF_VARIABLE", //$NON-NLS-1$
							p1l.getLiteral(), expr.getText());
					repointOffsets(problem, p1l);
				}
			}
		}

	}

	/**
	 * @param expr
	 */
	@ARule(sa = 31, desc = "The second argument MUST be a string literal conforming to the definition of QName in section 3", author = "michal.chmielewski@oracle.com", date = "01/29/2007", tag = "bpel.functions")
	public void CheckGetVariableProperty2ndArgument(FunctionCallExpr expr) {

		String fn = expr.getFunctionName();

		if ("getVariableProperty".equals(fn) == false) {
			return;
		}

		List<?> params = expr.getParameters();

		if (params.size() < 2
				|| ((params.get(1) instanceof LiteralExpr) == false)) {
			// already handled in previous rule.
			return;
		}

		LiteralExpr p2l = (LiteralExpr) params.get(1);

		// Make sure that the 2nd argument is a QName
		QName qname = mModelQuery.createQName(mNode, p2l.getLiteral());
		IProblem problem;

		if (isNamespaceOK(qname) == false) {
			problem = createError();

			problem.fill("XPATH_UNRESOLVED_NAMESPACE_PREFIX",
					qname.getPrefix(), qname.getLocalPart());

			repointOffsets(problem, p2l);

			return;
		}

		// look up variable property in the model ?
	}

	/**
	 * Check XslTransform function
	 * 
	 * @param expr
	 */

	@ARule(sa = 39, desc = "1st arguments to doXslTransform must be quoted string", author = "michal.chmielewski@oracle.com", date = "01/29/2007", tag = "bpel.functions")
	public void CheckDoXslTransform_10(FunctionCallExpr expr) {

		String fn = expr.getFunctionName();
		if ("doXslTransform".equals(fn) == false) {
			return;
		}

		List<?> params = expr.getParameters();
		IProblem problem;
		int psize = params.size();

		if (psize < 2) {

			problem = createError();
			problem.fill("XPATH_FN_ARGS", toString(mNode.nodeName()), fn, expr.getText(), 0, 2);
			repointOffsets(problem, expr);

			if (psize < 1) {
				return;
			}
		}

		Expr p = (Expr) params.get(0);

		if ((p instanceof LiteralExpr) == false) {
			problem = createError();
			problem.fill("XPATH_FN_LITERAL_ARGS", toString(mNode.nodeName()), fn,
					expr.getText(), 1, p.getText());

			repointOffsets(problem, p);
			return;
		}

		LiteralExpr lexpr = (LiteralExpr) p;
		// quick check if this is valid URI ...

		@SuppressWarnings("unused")
		java.net.URI uri = null;
		try {
			uri = new java.net.URI(lexpr.getLiteral());
		} catch (URISyntaxException e) {
			problem = createWarning();
			problem.fill("XPATH_URI_SYNTAX", toString(mNode.nodeName()), lexpr
					.getLiteral(), expr.getText(), e.getMessage());
			repointOffsets(problem, lexpr);
		}

	}

	/**
	 * @param expr
	 */
	@ARule(sa = 40, desc = "There must be an even number of arguments doXslTransform", author = "michal.chmielewski@oracle.com", date = "01/29/2007", tag = "bpel.functions")
	public void CheckDoXslTransform_11(FunctionCallExpr expr) {

		String fn = expr.getFunctionName();
		if ("doXslTransform".equals(fn) == false) {
			return;
		}

		List<?> params = expr.getParameters();
		if (params.size() < 2) {
			// handled in other rule
			return;
		}

		IProblem problem;
		if (params.size() % 2 != 0) {
			problem = createError();
			problem.fill("XPATH_FN_ARGS", toString(mNode.nodeName()), fn, expr.getText(), 0, 2);
			repointOffsets(problem, expr);
		}
	}

	/**
	 * Even number of arguments must be present.
	 * 
	 * @param expr
	 */

	@ARule(sa = 41, desc = "3rd,5th,7th, etc. arguments must be QName strings", author = "michal.chmielewski@oracle.com", date = "01/29/2007", tag = "bpel.functions")
	public void CheckDoXslTransform_15(FunctionCallExpr expr) {

		String fn = expr.getFunctionName();
		if ("doXslTransform".equals(fn) == false) {
			return;
		}

		List<?> params = expr.getParameters();
		if (params.size() < 3) {
			// handled in other rule
			return;
		}

		IProblem problem;

		// check the 3rd, 5th, 7th etc. arguments of the XPath expression to
		// make sure
		// that they are QNames.
		for (int i = 2, j = params.size(); i < j; i += 2) {
			Expr p = (Expr) params.get(i);
			if (p instanceof LiteralExpr) {
				LiteralExpr p2l = (LiteralExpr) p;

				QName qname = mModelQuery.createQName(mNode, p2l.getLiteral());

				if (isNamespaceOK(qname) == false) {
					problem = createError();
					problem.fill("XPATH_UNRESOLVED_NAMESPACE_PREFIX", qname
							.getPrefix(), qname.getLocalPart());
					repointOffsets(problem, p);
				}
			} else {
				problem = createError();
				problem.fill("XPATH_FN_LITERAL_ARGS",
						toString(mNode.nodeName()), fn, expr.getText(), i + 1,
						p.getText());
				repointOffsets(problem, p);
			}
		}

		//

	}

	/**
	 * Check variables used in XPath expressions.
	 * 
	 * @param expr
	 */

	@ARule(sa = 0, desc = "Check the variable namespace prefix", author = "michal.chmielewski@oracle.com", date = "01/30/2007", tag = "variables")
	public void rule_CheckVariable_10(VariableReferenceExpr expr) {

		if (isJoinCondition()) {
			return;
		}

		String prefix = expr.getPrefix();
		String name = expr.getVariableName();
		IProblem problem;

		// check to make sure we don't print the same message twice.
		if (duplicateThing("duplicate.variable.check.", name)) {
			return;
		}

		if (isEmpty(prefix) == false) {

			problem = createError();
			problem.fill("XPATH_INVALID_VARREF_PREFIX", //$NON-NLS-1$
					prefix + ":" + name); //$NON-NLS-1$
		}

		int i = name.indexOf('.');
		String varName, partName;
		if (i < 0) {
			varName = name;
			partName = null;
		} else {
			varName = name.substring(0, i);
			partName = name.substring(i + 1);
		}

		INode variable = mModelQuery.lookup(mNode,
				IModelQueryLookups.LOOKUP_NODE_VARIABLE, varName);

		if (isUndefined(variable)) {

			problem = createError();
			problem.fill("XPATH_UNDEF_VARIABLE", //$NON-NLS-1$
					varName, expr.getText());

			repointOffsets(problem, expr);

			// if it does not exist, we are done
			mVisitor.contextPush(Collections.EMPTY_LIST);
			return;
		}

		// Get the type reference of the variable
		INode varTypeNode = getValue(variable, "type", null);

		// If the variable has problems, leave now
		if (hasProblems(variable) || isUndefined(varTypeNode)) {
			mVisitor.contextPush(Collections.EMPTY_LIST);
			return;
		}

		mVisitor.contextPush(Collections.EMPTY_LIST);

		// check variable parts
		INode variablePart = null;

		if (partName != null) {

			variablePart = mModelQuery.lookup(varTypeNode,
					IModelQueryLookups.LOOKUP_NODE_TYPE_OF_PART, partName);
		}

		if (partName != null) {

			if (WSDL_ND_MESSAGE.equals(varTypeNode.nodeName()) == false) {
				problem = createError();
				problem.fill("XPATH_VARIABLE_PART", varName, partName, expr
						.getText(), 0);
				repointOffsets(problem, expr);

			} else if (isUndefined(variablePart)) {

				problem = createError();
				problem.fill("XPATH_UNDEF_VARIABLE_PART", //$NON-NLS-1$
						varName, partName, expr.getText());
				repointOffsets(problem, expr);

			} else {
				mVisitor.contextPop();
				mVisitor.contextPush(variablePart);
			}

		} else {
			// there is no part name specified, but variable does have more then
			// 1 part.
			if (WSDL_ND_MESSAGE.equals(varTypeNode.nodeName())) {
				problem = createError();
				problem.fill("XPATH_VARIABLE_PART", varName,
						"text.term.unspecified", expr.getText(), 1);
				repointOffsets(problem, expr);

			} else {
				mVisitor.contextPop();
				mVisitor.contextPush(varTypeNode);
			}
		}

	}

	boolean isNamespaceOK(QName qname) {
		if (qname == null) {
			return false;
		}
		return (isEmpty(qname.getNamespaceURI()) == isEmpty(qname.getPrefix()));
	}

	protected boolean isJoinCondition() {
		/** ND_ are QNames, so make sure you compare apples to apples ... */
		return ND_JOIN_CONDITION.equals(mNode.nodeName());
	}

	protected IModelQuery getModelQuery() {
		return mModelQuery;
	}

	protected INode getNode() {
		return mNode;
	}

	/**
	 * This exists here for visibility to XPathVisitor.
	 * 
	 * @see org.eclipse.bpel.validator.model.Validator#createError()
	 */

	@Override
	protected IProblem createError() {
		return super.createError();
	}

	/**
	 * This exists here for visibility to XPathVisitor.
	 * 
	 * @see org.eclipse.bpel.validator.model.Validator#createInfo()
	 */
	@Override
	protected IProblem createInfo() {
		return super.createInfo();
	}

	/**
	 * This exists here for visibility to XPathVisitor.
	 * 
	 * @see org.eclipse.bpel.validator.model.Validator#createWarning()
	 */
	@Override
	protected IProblem createWarning() {
		return super.createWarning();
	}

	/**
	 * @see org.eclipse.bpel.validator.model.Validator#runRules(java.lang.String,
	 *      java.lang.Object[])
	 */

	@Override
	protected void runRules(String tag, Object... args) {
		// this exists here for visibility to XPathVisitor.
		super.runRules(tag, args);
	}

	protected boolean duplicateThing(String... args) {

		String key = null;
		if (args.length == 1) {
			key = args[0];
		} else {
			StringBuilder sb = new StringBuilder();
			for (String a : args) {
				sb.append(a);
			}
			key = sb.toString();
		}

		if (containsValueKey(key)) {
			return true;
		}

		setValue(key, Boolean.TRUE);
		return false;
	}

	protected boolean checkPrefix(String prefix, String name) {

		if (isEmptyOrWhitespace(prefix)) {
			return true;
		}

		String nsURI = lookupNamespace(prefix);

		if (isEmpty(nsURI)) {
			IProblem problem = createError();
			problem.fill("XPATH_UNRESOLVED_NAMESPACE_PREFIX", //$NON-NLS-1$
					prefix, name);

			return false;
		}

		return true;
	}

	protected String lookupNamespace(String prefix) {
		return mModelQuery.lookup(mNode,IModelQueryLookups.LOOKUP_TEXT_PREFIX2NS, prefix, null);
	}

	/**
	 * Check if the function is a boolean function.
	 * 
	 * @param functionExpr
	 * @return true if a function is boolean, false otherwise.
	 */
	boolean isBooleanFunction(FunctionCallExpr functionExpr) {

		IFunctionMeta meta = lookup(functionExpr);
		if (meta == null) {
			return false;
		}
		return meta.getReturnType() == Boolean.class;
	}

	/**
	 * Empty prefix in functionExpr implies an XPath function.
	 * 
	 * @param functionExpr
	 */
	protected void checkFunctionMeta(FunctionCallExpr functionExpr,
			IFunctionMeta meta) {

		String fnCall = functionExpr.getFunctionName();
		if (isEmptyOrWhitespace(functionExpr.getPrefix()) == false) {
			fnCall = functionExpr.getPrefix() + ":" + fnCall;
		}

		if (duplicateThing("function.meta.", fnCall)) {
			return;
		}

		IProblem problem;

		if (meta == null) {
			problem = createWarning();
			problem.fill("XPATH_FUNCTION_UNKNOWN", //$NON-NLS-1$
					toString(mNode.nodeName()), fnCall);
			repointOffsets(problem, functionExpr);

			return;
		}

		if (meta.isDeprecated()) {
			problem = createWarning();
			problem.fill("XPATH_FUNCTION_DEPRECATED", //$NON-NLS-1$
					toString(mNode.nodeName()), fnCall, meta
							.getDeprecateComment());
			repointOffsets(problem, functionExpr);
		}
	}

	protected void checkFunctionCall(FunctionCallExpr functionExpr,
			IFunctionMeta meta) {

		if (meta == null || functionExpr == null) {
			return;
		}

		String fnCall = functionExpr.getFunctionName();
		if (isEmptyOrWhitespace(functionExpr.getPrefix()) == false) {
			fnCall = functionExpr.getPrefix() + ":" + fnCall;
		}

		IProblem problem;

		List<?> params = functionExpr.getParameters();
		if (params.size() < meta.getMinArity()) {
			problem = createError();
			problem.fill(
					"XPATH_FUNCTION_MIN_ARGS", //$NON-NLS-1$
					toString(mNode.nodeName()), fnCall, meta.getMinArity(),
					params.size());
			repointOffsets(problem, functionExpr);
		}

		if (params.size() > meta.getMaxArity()) {
			problem = createError();
			problem.fill(
					"XPATH_FUNCTION_MAX_ARGS", //$NON-NLS-1$
					toString(mNode.nodeName()), fnCall, meta.getMaxArity(),
					params.size());
		}

	}

	protected IFunctionMeta lookup(FunctionCallExpr functionExpr) {

		String ns = null;
		if (isEmptyOrWhitespace(functionExpr.getPrefix())) {
			ns = IConstants.XMLNS_XPATH_EXPRESSION_LANGUAGE;
		} else {
			ns = lookupNamespace(functionExpr.getPrefix());
		}
		return mModelQuery.lookupFunction(
				IConstants.XMLNS_XPATH_EXPRESSION_LANGUAGE, ns, functionExpr
						.getFunctionName());
	}

}
