/* * 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.psi.impl; import com.intellij.lang.ASTNode; import com.intellij.navigation.ItemPresentation; import com.intellij.navigation.NavigationItem; import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiReference; import com.intellij.psi.impl.light.LightElement; import com.intellij.util.IncorrectOperationException; import icons.XpathIcons; import org.intellij.lang.xpath.XPath2ElementTypes; import org.intellij.lang.xpath.XPathTokenTypes; import org.intellij.lang.xpath.context.ContextProvider; import org.intellij.lang.xpath.context.XPathVersion; import org.intellij.lang.xpath.context.functions.Function; import org.intellij.lang.xpath.psi.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.xml.namespace.QName; public class XPathFunctionCallImpl extends XPathElementImpl implements XPathFunctionCall { public XPathFunctionCallImpl(ASTNode node) { super(node); } @NotNull public XPathExpression[] getArgumentList() { final ASTNode[] nodes = getNode().getChildren(XPath2ElementTypes.EXPRESSIONS); final XPathExpression[] expressions = new XPathExpression[nodes.length]; for (int i = 0; i < expressions.length; i++) { expressions[i] = (XPathExpression)nodes[i].getPsi(); } return expressions; } public PsiElement add(@NotNull PsiElement psiElement) throws IncorrectOperationException { if (psiElement instanceof XPathExpression) { if (getNode().getChildren(XPath2ElementTypes.EXPRESSIONS).length > 0) { final XPathExpression child = XPathChangeUtil.createExpression(this, "f(a,b)"); final ASTNode comma = child.getNode().findChildByType(XPathTokenTypes.COMMA); assert comma != null; final PsiElement psi = comma.getPsi(); assert psi != null; add(psi); } } final ASTNode paren = getNode().findChildByType(XPathTokenTypes.RPAREN); if (paren != null) { return super.addBefore(psiElement, paren.getPsi()); } return super.add(psiElement); } @NotNull public String getFunctionName() { final ASTNode node = getNameNode(); final String name = node != null ? node.getText() : null; assert name != null : unexpectedPsiAssertion(); return name; } @Nullable protected ASTNode getNameNode() { return getNode().findChildByType(XPathTokenTypes.FUNCTION_NAME); } @Nullable protected ASTNode getPrefixNode() { return getNode().findChildByType(XPathTokenTypes.EXT_PREFIX); } @NotNull public PrefixedName getQName() { final ASTNode node = getNameNode(); assert node != null : unexpectedPsiAssertion(); return new PrefixedNameImpl(getPrefixNode(), node); } @Nullable public XPathFunction resolve() { final Reference reference = getReference(); return reference != null ? reference.resolve() : null; } @Nullable public Reference getReference() { final ASTNode nameNode = getNameNode(); if (nameNode != null) { return new Reference(nameNode); } return null; } @NotNull public PsiReference[] getReferences() { if (getPrefixNode() != null && getNameNode() != null) { return new PsiReference[]{getReference(), new PrefixReferenceImpl(this, getPrefixNode())}; } return super.getReferences(); } @NotNull public XPathType getType() { final XPathFunction f = resolve(); if (f == null) return XPathType.UNKNOWN; final Function function = f.getDeclaration(); return function != null ? function.getReturnType() : XPathType.UNKNOWN; } class Reference extends ReferenceBase { private volatile Pair<String, XPathFunction> myFunction; public Reference(ASTNode node) { super(XPathFunctionCallImpl.this, node); } @Nullable public XPathFunction resolve() { if (myFunction != null && myFunction.first.equals(getQName().toString())) { return myFunction.second; } else { final XPathFunctionCallImpl call = XPathFunctionCallImpl.this; final ContextProvider contextProvider = call.getXPathContext(); final QName name = contextProvider.getQName(call); if (name == null) return null; final Function functionDecl = contextProvider.getFunctionContext().resolve(name, getArgumentList().length); final XPathFunction impl = functionDecl == null ? null : functionDecl instanceof XPathFunction ? (XPathFunction)functionDecl : new FunctionImpl(functionDecl); return (myFunction = Pair.create(getQName().toString(), impl)).second; } } @NotNull public Object[] getVariants() { return EMPTY_ARRAY; } @Override public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException { final XPathFunctionCall child = (XPathFunctionCall)XPathChangeUtil.createExpression(getElement(), newElementName + "()"); final PrefixedNameImpl newName = ((PrefixedNameImpl)child.getQName()); final PrefixedNameImpl oldName = ((PrefixedNameImpl)getQName()); final ASTNode localNode = newName.getLocalNode(); getNode().replaceChild(oldName.getLocalNode(), localNode); final PsiElement psi = getNode().getPsi(); assert psi != null; return psi; } class FunctionImpl extends LightElement implements XPathFunction, ItemPresentation, NavigationItem { private final Function myFunctionDecl; public FunctionImpl(Function functionDecl) { super(getElement().getManager(), getElement().getContainingFile().getLanguage()); myFunctionDecl = functionDecl; } @Override public PsiElement getContext() { return XPathFunctionCallImpl.this; } public String getName() { return myFunctionDecl != null ? myFunctionDecl.getName() : getFunctionName(); } public String toString() { return "Function: " + getName(); } @SuppressWarnings({"ConstantConditions"}) public String getText() { return getName(); } public ItemPresentation getPresentation() { return this; } @Nullable public Icon getIcon(boolean open) { return getIcon(0); } @Nullable public String getLocationString() { return null; } @Nullable public String getPresentableText() { return myFunctionDecl != null ? myFunctionDecl.buildSignature() + ": " + myFunctionDecl.getReturnType().getName() : null; } public Icon getIcon(int i) { return XpathIcons.Function; } public void accept(@NotNull PsiElementVisitor visitor) { } public PsiElement copy() { return this; } public PsiElement setName(@NotNull String name) throws IncorrectOperationException { throw new IncorrectOperationException(); } public boolean isValid() { return true; } public int hashCode() { final String name = getName(); return name != null ? name.hashCode() : 0; } public boolean equals(Object obj) { if (obj == null || obj.getClass() != getClass()) return false; final String name = ((FunctionImpl)obj).getName(); return name != null && name.equals(getName()) || getName() == null; } public Function getDeclaration() { return myFunctionDecl; } @Override public boolean isWritable() { return false; } @Override public boolean isPhysical() { // hack // required to prevent renaming of functions. Shouldn't IDEA check for isWritable()? // com.intellij.refactoring.rename.PsiElementRenameHandler: // if (!PsiManager.getInstance(project).isInProject(element) && element.isPhysical()) { ... } return true; } @Override public ContextProvider getXPathContext() { return ContextProvider.getContextProvider(getElement()); } @Override public XPathVersion getXPathVersion() { return getElement().getXPathVersion(); } public void accept(XPathElementVisitor visitor) { visitor.visitXPathFunction(this); } } } public void accept(XPathElementVisitor visitor) { visitor.visitXPathFunctionCall(this); } }