blob: dd1403ef3438a7d9264dcf9353fead61d91711f2 [file] [log] [blame]
/*
* 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.
*/
/*
* Created by IntelliJ IDEA.
* User: sweinreuter
* Date: 04.05.2006
* Time: 22:32:31
*/
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;
}
}
}