blob: bfd8a9bdb612c9871e7aaf893372b24b84d87ef4 [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.
*/
package org.intellij.lang.xpath.completion;
import com.intellij.codeInsight.completion.CompletionInitializationContext;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlElement;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.lang.xpath.XPathFile;
import org.intellij.lang.xpath.XPathTokenTypes;
import org.intellij.lang.xpath.context.ContextProvider;
import org.intellij.lang.xpath.context.NamespaceContext;
import org.intellij.lang.xpath.context.VariableContext;
import org.intellij.lang.xpath.context.XPathVersion;
import org.intellij.lang.xpath.context.functions.Function;
import org.intellij.lang.xpath.psi.*;
import javax.xml.namespace.QName;
import java.util.*;
public class CompletionLists {
public static final String INTELLIJ_IDEA_RULEZ = CompletionInitializationContext.DUMMY_IDENTIFIER_TRIMMED;
private CompletionLists() {
}
public static final Set<String> NODE_TYPE_FUNCS = new HashSet<String>(Arrays.asList(
"text", "node", "comment", "processing-instruction"
));
public static final Set<String> NODE_TYPE_FUNCS_V2 = new HashSet<String>(Arrays.asList(
"text", "node", "comment", "processing-instruction", "attribute", "element", "schema-element", "schema-attribute", "document-node"
));
public static final Set<String> OPERATORS = new HashSet<String>(Arrays.asList(
"mul", "div", "and", "or"
));
public static final Set<String> AXIS_NAMES = new HashSet<String>(Arrays.asList(
"ancestor",
"ancestor-or-self",
"attribute",
"child",
"descendant",
"descendant-or-self",
"following",
"following-sibling",
"namespace",
"parent",
"preceding",
"preceding-sibling",
"self"
));
private static final com.intellij.util.Function<String,Lookup> FUNCTION_MAPPING = new com.intellij.util.Function<String, Lookup>() {
@Override
public Lookup fun(String s) {
if (s.equals("processing-instruction")) {
return new FunctionLookup(s, s + "(pi-target?)");
} else {
return new FunctionLookup(s, s + "()");
}
}
};
public static Collection<Lookup> getFunctionCompletions(XPathElement element) {
final XPathFile xpathFile = (XPathFile)element.getContainingFile();
final ContextProvider contextProvider = ContextProvider.getContextProvider(xpathFile);
final NamespaceContext nsContext = contextProvider.getNamespaceContext();
String uri;
PrefixedName qn = null;
if (element instanceof QNameElement) {
qn = ((QNameElement)element).getQName();
if (qn != null) {
QName qName = contextProvider.getQName(qn, element);
if (qn.getPrefix() != null) {
if (qName != null) {
uri = qName.getNamespaceURI();
} else {
return Collections.emptySet();
}
} else {
uri = null;
}
} else {
uri = null;
}
} else {
uri = null;
}
final Map<Pair<QName, Integer>, ? extends Function> functions = contextProvider.getFunctionContext().getFunctions();
final List<Lookup> lookups = new ArrayList<Lookup>(functions.size());
for (Map.Entry<Pair<QName, Integer>, ? extends Function> entry : functions.entrySet()) {
final Function functionDecl = entry.getValue();
final QName f = entry.getKey().first;
final String p;
if (nsContext != null) {
String namespaceURI = f.getNamespaceURI();
if (uri != null && !namespaceURI.equals(uri)) {
continue;
}
final String prefixForURI = nsContext.getPrefixForURI(namespaceURI, PsiTreeUtil.getContextOfType(element, XmlElement.class, true));
if (prefixForURI == null && namespaceURI.length() > 0) {
continue;
}
p = qn == null || qn.getPrefix() == null ? makePrefix(prefixForURI) : "";
} else {
p = "";
}
lookups.add(FunctionLookup.newFunctionLookup(p + f.getLocalPart(), functionDecl));
}
return lookups;
}
public static Collection<Lookup> getVariableCompletions(XPathElement reference) {
final ContextProvider contextProvider = ContextProvider.getContextProvider(reference);
final VariableContext resolver = contextProvider.getVariableContext();
if (resolver != null) {
final Object[] variablesInScope = resolver.getVariablesInScope(reference);
final List<Lookup> lookups = new ArrayList<Lookup>(variablesInScope.length);
for (final Object o : variablesInScope) {
if (o instanceof PsiNamedElement) {
final String type;
if (o instanceof XPathVariable) {
final XPathType t = ((XPathVariable)o).getType();
if (t != XPathType.UNKNOWN) {
type = t.getName();
} else {
type = "";
}
} else {
type = "";
}
final String name = ((PsiNamedElement)o).getName();
lookups.add(new VariableLookup("$" + name, type, ((PsiNamedElement)o).getIcon(0), (PsiElement)o));
} else {
lookups.add(new VariableLookup("$" + String.valueOf(o), PlatformIcons.VARIABLE_ICON));
}
}
return lookups;
} else {
return Collections.emptySet();
}
}
public static Collection<Lookup> getNodeTestCompletions(final XPathNodeTest element) {
if (!element.isNameTest()) {
return Collections.emptyList();
}
final PrefixedName prefixedName = element.getQName();
assert prefixedName != null;
final String canonicalText = prefixedName.toString();
final String suffix = canonicalText.substring(canonicalText.indexOf(INTELLIJ_IDEA_RULEZ));
final XPathAxisSpecifier axisSpecifier = element.getStep().getAxisSpecifier();
final ContextProvider contextProvider = ContextProvider.getContextProvider(element);
final XmlElement context = contextProvider.getContextElement();
final boolean insidePrefix = suffix.contains(INTELLIJ_IDEA_RULEZ + ":");
final Set<Lookup> list = new HashSet<Lookup>();
addNameCompletions(contextProvider, element, list);
final String namespacePrefix = prefixedName.getPrefix();
if (namespacePrefix == null || insidePrefix) {
addNamespaceCompletions(contextProvider.getNamespaceContext(), list, context);
}
final XPathNodeTest.PrincipalType principalType = addContextNames(element, contextProvider, prefixedName, list);
if (namespacePrefix == null && !insidePrefix) {
if (axisSpecifier == null || axisSpecifier.isDefaultAxis()) {
list.addAll(getAxisCompletions());
// wow, this code sux. find a better implementation
PsiElement sibling = element.getParent().getPrevSibling();
while (sibling instanceof PsiWhiteSpace) {
sibling = sibling.getPrevSibling();
}
boolean check = sibling != null;
if (!check) {
XPathLocationPath lp = null;
do {
lp = PsiTreeUtil.getParentOfType(lp == null ? element : lp, XPathLocationPath.class, true);
} while (lp != null && lp.getPrevSibling() == null);
check = lp == null || (sibling = lp.getPrevSibling()) != null;
}
if (check) {
if (sibling instanceof XPathToken && XPathTokenTypes.PATH_OPS.contains(((XPathToken)sibling).getTokenType())) {
// xx/yy<caret> : prevSibl = /
} else {
list.addAll(getFunctionCompletions(element));
list.addAll(getVariableCompletions(element));
}
}
}
if (principalType == XPathNodeTest.PrincipalType.ELEMENT && prefixedName.getPrefix() == null) {
list.addAll(getNodeTypeCompletions(element));
}
}
return list;
}
private static XPathNodeTest.PrincipalType addContextNames(XPathNodeTest element, ContextProvider contextProvider, PrefixedName prefixedName, Set<Lookup> list) {
final NamespaceContext namespaceContext = contextProvider.getNamespaceContext();
final XmlElement context = contextProvider.getContextElement();
final XPathNodeTest.PrincipalType principalType = element.getPrincipalType();
if (principalType == XPathNodeTest.PrincipalType.ELEMENT) {
final Set<QName> elementNames = contextProvider.getElements(false);
if (elementNames != null) {
for (QName pair : elementNames) {
if ("*".equals(pair.getLocalPart())) continue;
if (namespaceMatches(prefixedName, pair.getNamespaceURI(), namespaceContext, context, true)) {
if (prefixedName.getPrefix() == null && namespaceContext != null) {
final String p = namespaceContext.getPrefixForURI(pair.getNamespaceURI(), context);
list.add(new NodeLookup(makePrefix(p) + pair.getLocalPart(), XPathNodeTest.PrincipalType.ELEMENT));
} else {
list.add(new NodeLookup(pair.getLocalPart(), XPathNodeTest.PrincipalType.ELEMENT));
}
}
}
}
} else if (principalType == XPathNodeTest.PrincipalType.ATTRIBUTE) {
final Set<QName> attributeNames = contextProvider.getAttributes(false);
if (attributeNames != null) {
for (QName pair : attributeNames) {
if ("*".equals(pair.getLocalPart())) continue;
if (namespaceMatches(prefixedName, pair.getNamespaceURI(), namespaceContext, context, false)) {
if (prefixedName.getPrefix() == null && namespaceContext != null) {
final String p = namespaceContext.getPrefixForURI(pair.getNamespaceURI(), context);
list.add(new NodeLookup(makePrefix(p) + pair.getLocalPart(), XPathNodeTest.PrincipalType.ATTRIBUTE));
} else {
list.add(new NodeLookup(pair.getLocalPart(), XPathNodeTest.PrincipalType.ATTRIBUTE));
}
}
}
}
}
return principalType;
}
private static String makePrefix(String p) {
return (p != null && p.length() > 0 ? p + ":" : "");
}
private static void addNamespaceCompletions(NamespaceContext namespaceContext, Set<Lookup> list, XmlElement context) {
if (namespaceContext != null) {
final Collection<String> knownPrefixes = namespaceContext.getKnownPrefixes(context);
for (String prefix : knownPrefixes) {
if (prefix != null && prefix.length() > 0) {
list.add(new NamespaceLookup(prefix));
}
}
}
}
private static void addNameCompletions(ContextProvider contextProvider, final XPathNodeTest element, final Set<Lookup> list) {
final PrefixedName prefixedName = element.getQName();
final XPathNodeTest.PrincipalType principalType = element.getPrincipalType();
final Set<PsiFile> files = new HashSet<PsiFile>();
final XPathFile file = (XPathFile)element.getContainingFile();
files.add(file);
ContainerUtil.addAll(files, contextProvider.getRelatedFiles(file));
for (PsiFile xpathFile : files) {
xpathFile.accept(new PsiRecursiveElementVisitor() {
public void visitElement(PsiElement e) {
if (e instanceof XPathNodeTest) {
final XPathNodeTest nodeTest = (XPathNodeTest)e;
final XPathNodeTest.PrincipalType _principalType = nodeTest.getPrincipalType();
if (_principalType == principalType) {
final PrefixedName _prefixedName = nodeTest.getQName();
if (_prefixedName != null && prefixedName != null) {
final String localName = _prefixedName.getLocalName();
if (!"*".equals(localName) && !localName.contains(INTELLIJ_IDEA_RULEZ)) {
if (Comparing.equal(_prefixedName.getPrefix(), prefixedName.getPrefix())) {
list.add(new NodeLookup(localName, _principalType));
} else if (prefixedName.getPrefix() == null) {
list.add(new NodeLookup(_prefixedName.toString(), _principalType));
}
}
}
}
}
super.visitElement(e);
}
});
}
}
private static boolean namespaceMatches(PrefixedName prefixedName,
String uri,
NamespaceContext namespaceContext,
XmlElement context,
boolean allowDefault) {
if (namespaceContext == null) return true;
final String namespaceURI;
if (prefixedName.getPrefix() != null) {
if (uri == null || uri.length() == 0) return false;
namespaceURI = namespaceContext.getNamespaceURI(prefixedName.getPrefix(), context);
} else {
if (!allowDefault) return (uri == null || uri.length() == 0);
if ((namespaceURI = namespaceContext.getDefaultNamespace(context)) == null) {
return (uri == null || uri.length() == 0);
}
}
return uri.equals(namespaceURI);
}
public static Collection<Lookup> getNodeTypeCompletions(XPathElement context) {
final Set<String> funcs = context.getXPathVersion() == XPathVersion.V1 ?
NODE_TYPE_FUNCS : NODE_TYPE_FUNCS_V2;
return ContainerUtil.map(funcs, FUNCTION_MAPPING);
}
public static Collection<Lookup> getAxisCompletions() {
final ArrayList<Lookup> lookups = new ArrayList<Lookup>(AXIS_NAMES.size());
for (String s : AXIS_NAMES) {
lookups.add(new AxisLookup(s));
}
return lookups;
}
@SuppressWarnings({"RawUseOfParameterizedType"})
public static Class[] getAllInterfaces(Class<?> clazz) {
Set<Class> set = new HashSet<Class>();
do {
ContainerUtil.addAll(set, clazz.getInterfaces());
clazz = clazz.getSuperclass();
} while (clazz != null);
return set.toArray(new Class[set.size()]);
}
}