/* * Copyright 2005 Sascha Weinreuter * * 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. */ package org.intellij.lang.xpath.validation; import com.intellij.openapi.util.Comparing; import com.intellij.psi.util.PsiTreeUtil; import org.intellij.lang.xpath.XPath2TokenTypes; import org.intellij.lang.xpath.XPathElementType; import org.intellij.lang.xpath.XPathTokenTypes; import org.intellij.lang.xpath.context.XPathVersion; import org.intellij.lang.xpath.context.functions.Function; import org.intellij.lang.xpath.context.functions.Parameter; import org.intellij.lang.xpath.psi.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.xml.namespace.QName; public class ExpectedTypeUtil { private ExpectedTypeUtil() { } @NotNull public static XPathType getExpectedType(XPathExpression expression) { final XPathExpression parentExpr = PsiTreeUtil.getParentOfType(expression, XPathExpression.class); if (parentExpr != null) { final ExpectedTypeVisitor visitor = new ExpectedTypeVisitor(expression); parentExpr.accept(visitor); return mapType(expression, visitor.getExpectedType()); } else { return mapType(expression, expression.getXPathContext().getExpectedType(expression)); } } private static XPathType matchingType(XPathExpression lOperand, XPathElementType op) { final XPathType type = mapType(lOperand, lOperand.getType()); if (op == XPathTokenTypes.PLUS) { if (XPathType.isAssignable(XPath2Type.NUMERIC, type)) { return XPath2Type.NUMERIC; } else if (XPathType.isAssignable(XPath2Type.DATE, type) || XPathType.isAssignable(XPath2Type.TIME, type) || XPathType.isAssignable(XPath2Type.DATETIME, type)) { return XPath2Type.DURATION; } else if (XPathType.isAssignable(XPath2Type.DURATION, type)) { return XPathType.ChoiceType.create(XPath2Type.DURATION, XPath2Type.DATE, XPath2Type.TIME, XPath2Type.DATETIME); } } else if (op == XPathTokenTypes.MINUS) { if (XPathType.isAssignable(XPath2Type.NUMERIC, type)) { return XPath2Type.NUMERIC; } else if (XPathType.isAssignable(XPath2Type.DATE, type) || XPathType.isAssignable(XPath2Type.TIME, type) || XPathType.isAssignable(XPath2Type.DATETIME, type)) { return XPathType.ChoiceType.create(type, XPath2Type.DURATION); } else if (XPathType.isAssignable(XPath2Type.DURATION, type)) { return XPath2Type.DURATION; } } else if (op == XPathTokenTypes.MULT) { if (XPathType.isAssignable(XPath2Type.NUMERIC, type)) { return XPathType.ChoiceType.create(XPath2Type.NUMERIC, XPath2Type.DURATION); } else if (XPath2Type.DURATION.isAssignableFrom(type)) { return XPath2Type.NUMERIC; } } else if (op == XPath2TokenTypes.IDIV || op == XPathTokenTypes.MOD) { return XPath2Type.NUMERIC; } else if (op == XPathTokenTypes.DIV) { if (XPathType.isAssignable(XPath2Type.NUMERIC, type)) { return XPath2Type.NUMERIC; } else if (XPath2Type.DURATION.isAssignableFrom(type)) { return XPath2Type.ChoiceType.create(XPath2Type.NUMERIC, XPath2Type.DURATION); } } // TODO return XPathType.UNKNOWN; } public static XPathType mapType(XPathExpression context, XPathType type) { return context.getXPathVersion() == XPathVersion.V2 ? XPath2Type.mapType(type) : type; } public static XPathType getPredicateType(XPathExpression expression) { // special: If the result is a number, the result will be converted to true if the number is equal to // the context position and will be converted to false otherwise; // (http://www.w3.org/TR/xpath#predicates) return expression.getType() == XPathType.NUMBER ? XPathType.NUMBER : XPathType.BOOLEAN; } @Nullable private static Parameter findParameterDecl(XPathExpression[] argumentList, XPathExpression expr, Parameter[] parameters) { for (int i = 0; i < argumentList.length; i++) { XPathExpression arg = argumentList[i]; if (arg == expr) { if (i < parameters.length) { return parameters[i]; } else if (parameters.length > 0) { final Parameter last = parameters[parameters.length - 1]; if (last.kind == Parameter.Kind.VARARG) { return last; } } } } return null; } public static boolean isExplicitConversion(XPathExpression expression) { expression = unparenthesize(expression); if (!(expression instanceof XPathFunctionCall)) { return false; } final XPathFunctionCall call = ((XPathFunctionCall)expression); if (call.getArgumentList().length != 1) { return false; } else if (call.getQName().getPrefix() != null) { XPathType type = call.getType(); if (type instanceof XPath2SequenceType) { type = ((XPath2SequenceType)type).getType(); } if (type instanceof XPath2Type) { final QName funcName = expression.getXPathContext().getQName(call); if (Comparing.equal(funcName, ((XPath2Type)type).getQName())) { return true; } } return false; } return XPathType.fromString(call.getFunctionName()) != XPathType.UNKNOWN; } // TODO: put this somewhere else @Nullable public static XPathExpression unparenthesize(XPathExpression expression) { while (expression instanceof XPathParenthesizedExpression) { expression = ((XPathParenthesizedExpression)expression).getExpression(); } return expression; } private static class ExpectedTypeVisitor extends XPath2ElementVisitor { private final XPathExpression myExpression; private XPathType myExpectedType = XPathType.UNKNOWN; public ExpectedTypeVisitor(XPathExpression expression) { myExpression = expression; } @Override public void visitXPathPrefixExpression(XPathPrefixExpression o) { myExpectedType = XPathType.NUMBER; } @Override public void visitXPathBinaryExpression(XPathBinaryExpression parent) { if (myExpression == parent.getROperand()) { final XPathElementType op = parent.getOperator(); final XPathExpression lop = parent.getLOperand(); if (op == XPathTokenTypes.AND || op == XPathTokenTypes.OR) { myExpectedType = XPathType.BOOLEAN; } else if (XPath2TokenTypes.NUMBER_OPERATIONS.contains(op)) { if (isXPath1(myExpression)) { myExpectedType = XPathType.NUMBER; } else { myExpectedType = matchingType(lop, op); } } else if (XPath2TokenTypes.COMP_OPS.contains(op)) { if (lop != null && lop.getType() != XPathType.NODESET) { if ((myExpectedType = lop.getType()) == XPathType.BOOLEAN) { if (!isXPath1(myExpression)) myExpectedType = XPath2Type.BOOLEAN_STRICT; } else if (myExpectedType == XPath2Type.BOOLEAN) { myExpectedType = XPath2Type.BOOLEAN_STRICT; } } else { myExpectedType = XPathType.UNKNOWN; } } else if (XPath2TokenTypes.INTERSECT_EXCEPT.contains(op)) { myExpectedType = XPath2SequenceType.create(XPath2Type.NODE, XPath2SequenceType.Cardinality.ZERO_OR_MORE); } else if (op == XPath2TokenTypes.TO) { myExpectedType = XPath2Type.INTEGER; } else { myExpectedType = XPathType.UNKNOWN; } } else { super.visitXPathBinaryExpression(parent); } } @Override public void visitXPathFunctionCall(XPathFunctionCall call) { final XPathFunction xpathFunction = call.resolve(); if (xpathFunction != null) { final Function functionDecl = xpathFunction.getDeclaration(); if (functionDecl != null) { final Parameter p = findParameterDecl(call.getArgumentList(), myExpression, functionDecl.getParameters()); if (p != null) { if (p.type == XPath2Type.BOOLEAN) { myExpectedType = XPath2Type.BOOLEAN_STRICT; } else { myExpectedType = p.type; } } } } } @Override public void visitXPathFilterExpression(XPathFilterExpression filterExpression) { final XPathExpression filteredExpression = filterExpression.getExpression(); if (filteredExpression == myExpression) { myExpectedType = isXPath1(myExpression) ? XPathType.NODESET : XPath2Type.SEQUENCE; return; } assert filterExpression.getPredicate().getPredicateExpression() == myExpression; myExpectedType = getPredicateType(myExpression); } @Override public void visitXPathStep(XPathStep step) { final XPathPredicate[] predicates = step.getPredicates(); for (XPathPredicate predicate : predicates) { if (predicate.getPredicateExpression() == myExpression) { myExpectedType = getPredicateType(myExpression); return; } } if (isXPath1(step)) { myExpectedType = XPathType.NODESET; } else { if (step.getStep() != null) { myExpectedType = XPath2Type.SEQUENCE; } else { myExpectedType = XPath2Type.NODE; } } } @Override public void visitXPathLocationPath(XPathLocationPath expression) { myExpectedType = isXPath1(myExpression) ? XPathType.NODESET : XPath2Type.SEQUENCE; } @Override public void visitXPath2If(XPath2If expression) { if (myExpression == expression.getCondition()) { myExpectedType = XPath2Type.BOOLEAN; } } @Override public void visitXPath2QuantifiedExpr(XPath2QuantifiedExpr expression) { if (myExpression == expression.getTest()) { myExpectedType = XPath2Type.BOOLEAN; } } public XPathType getExpectedType() { return myExpectedType; } private static boolean isXPath1(XPathExpression expression) { return expression.getXPathVersion() == XPathVersion.V1; } } }