blob: 47a77fef37a9f28c14a20d25e5ce55895296bb55 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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 com.intellij.psi.impl.source.resolve.reference.impl.providers;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.ElementManipulators;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.impl.source.resolve.ResolveCache;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.psi.xml.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import com.intellij.xml.XmlAttributeDescriptor;
import com.intellij.xml.XmlElementDescriptor;
import com.intellij.xml.XmlNSDescriptor;
import com.intellij.xml.impl.schema.ComplexTypeDescriptor;
import com.intellij.xml.impl.schema.TypeDescriptor;
import com.intellij.xml.impl.schema.XmlNSDescriptorImpl;
import com.intellij.xml.util.XmlUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
public class TypeOrElementOrAttributeReference implements PsiReference {
public enum ReferenceType {
ElementReference, AttributeReference, GroupReference, AttributeGroupReference, TypeReference
}
@NonNls private static final String TARGET_NAMESPACE = "targetNamespace";
private final PsiElement myElement;
private TextRange myRange;
private String nsPrefix;
@Nullable
public ReferenceType getType() {
return myType;
}
public void setNamespacePrefix(String prefix) {
this.nsPrefix = prefix;
}
@Nullable private final ReferenceType myType;
protected TypeOrElementOrAttributeReference(PsiElement element, TextRange range, @Nullable ReferenceType type) {
myElement = element;
myRange = range;
assert myRange.getLength() >= 0;
myType = type;
}
TypeOrElementOrAttributeReference(PsiElement element, TextRange range) {
this(element, range, determineReferenceType(element));
}
@Nullable
private static ReferenceType determineReferenceType(PsiElement element) {
final XmlAttribute attribute = PsiTreeUtil.getParentOfType(element, XmlAttribute.class);
if (attribute == null) {
return null;
}
final XmlTag tag = attribute.getParent();
final String localName = tag.getLocalName();
final String attributeLocalName = attribute.getLocalName();
if (SchemaReferencesProvider.REF_ATTR_NAME.equals(attributeLocalName) || SchemaReferencesProvider.SUBSTITUTION_GROUP_ATTR_NAME.equals(attributeLocalName)) {
if (localName.equals(SchemaReferencesProvider.GROUP_TAG_NAME)) {
return ReferenceType.GroupReference;
} else if (localName.equals(SchemaReferencesProvider.ATTRIBUTE_GROUP_TAG_NAME)) {
return ReferenceType.AttributeGroupReference;
} else if (SchemaReferencesProvider.ELEMENT_TAG_NAME.equals(localName)) {
return ReferenceType.ElementReference;
} else if (SchemaReferencesProvider.ATTRIBUTE_TAG_NAME.equals(localName)) {
return ReferenceType.AttributeReference;
}
} else if (SchemaReferencesProvider.TYPE_ATTR_NAME.equals(attributeLocalName) ||
SchemaReferencesProvider.BASE_ATTR_NAME.equals(attributeLocalName) ||
SchemaReferencesProvider.MEMBER_TYPES_ATTR_NAME.equals(attributeLocalName) ||
SchemaReferencesProvider.ITEM_TYPE_ATTR_NAME.equals(attributeLocalName)
) {
return ReferenceType.TypeReference;
}
return null;
}
@Override
public PsiElement getElement() {
return myElement;
}
@Override
public TextRange getRangeInElement() {
return myRange;
}
@Override
@Nullable
public PsiElement resolve() {
final PsiElement psiElement = ResolveCache
.getInstance(getElement().getProject()).resolveWithCaching(this, MyResolver.INSTANCE, false, false);
return psiElement != PsiUtilCore.NULL_PSI_ELEMENT ? psiElement:null;
}
private PsiElement resolveInner() {
final XmlTag tag = PsiTreeUtil.getContextOfType(myElement, XmlTag.class, false);
if (tag == null) return PsiUtilCore.NULL_PSI_ELEMENT;
String canonicalText = getCanonicalText();
XmlNSDescriptorImpl nsDescriptor = getDescriptor(tag,canonicalText);
if (myType != null && nsDescriptor != null && nsDescriptor.getTag() != null) {
switch(myType) {
case GroupReference: return nsDescriptor.findGroup(canonicalText);
case AttributeGroupReference: return nsDescriptor.findAttributeGroup(canonicalText);
case ElementReference: {
XmlElementDescriptor descriptor = nsDescriptor.getElementDescriptor(
XmlUtil.findLocalNameByQualifiedName(canonicalText), getNamespace(tag, canonicalText),
new HashSet<XmlNSDescriptorImpl>(),
true
);
return descriptor != null ? descriptor.getDeclaration(): PsiUtilCore.NULL_PSI_ELEMENT;
}
case AttributeReference: {
//final String prefixByQualifiedName = XmlUtil.findPrefixByQualifiedName(canonicalText);
final String localNameByQualifiedName = XmlUtil.findLocalNameByQualifiedName(canonicalText);
XmlAttributeDescriptor descriptor = nsDescriptor.getAttribute(
localNameByQualifiedName,
getNamespace(tag, canonicalText),
tag
);
if (descriptor != null) return descriptor.getDeclaration();
return PsiUtilCore.NULL_PSI_ELEMENT;
}
case TypeReference: {
TypeDescriptor typeDescriptor = nsDescriptor.getTypeDescriptor(canonicalText,tag);
if (typeDescriptor instanceof ComplexTypeDescriptor) {
return ((ComplexTypeDescriptor)typeDescriptor).getDeclaration();
} else if (typeDescriptor != null) {
return myElement;
}
}
}
}
return PsiUtilCore.NULL_PSI_ELEMENT;
}
XmlNSDescriptorImpl getDescriptor(final XmlTag tag, String text) {
if (myType != ReferenceType.ElementReference &&
myType != ReferenceType.AttributeReference) {
final PsiElement parentElement = myElement.getContext();
final PsiElement grandParentElement = parentElement != null ? parentElement.getParent() : null;
boolean doRedefineCheck = false;
if (parentElement instanceof XmlAttribute &&
grandParentElement instanceof XmlTag
) {
final String attrName = ((XmlAttribute)parentElement).getName();
final String tagLocalName = ((XmlTag)grandParentElement).getLocalName();
doRedefineCheck = (SchemaReferencesProvider.REF_ATTR_NAME.equals(attrName) &&
( SchemaReferencesProvider.GROUP_TAG_NAME.equals(tagLocalName) ||
SchemaReferencesProvider.ATTRIBUTE_GROUP_TAG_NAME.equals(tagLocalName)
)
) ||
( SchemaReferencesProvider.BASE_ATTR_NAME.equals(attrName) ||
SchemaReferencesProvider.MEMBER_TYPES_ATTR_NAME.equals(attrName)
);
}
if (doRedefineCheck) {
XmlNSDescriptorImpl redefinedDescriptor = SchemaReferencesProvider.findRedefinedDescriptor(tag, text);
if (redefinedDescriptor != null) return redefinedDescriptor;
}
}
final String namespace = getNamespace(tag, text);
XmlNSDescriptor nsDescriptor = tag.getNSDescriptor(namespace,true);
final XmlDocument document = ((XmlFile)tag.getContainingFile()).getDocument();
if (nsDescriptor == null) { // import
nsDescriptor = (XmlNSDescriptor)document.getMetaData();
}
if (nsDescriptor == null) {
final XmlNSDescriptor[] descrs = new XmlNSDescriptor[1];
URLReference.processWsdlSchemas(
document.getRootTag(),
new Processor<XmlTag>() {
@Override
public boolean process(final XmlTag xmlTag) {
if (namespace.equals(xmlTag.getAttributeValue(TARGET_NAMESPACE))) {
descrs[0] = (XmlNSDescriptor)xmlTag.getMetaData();
return false;
}
return true;
}
}
);
if (descrs[0] instanceof XmlNSDescriptorImpl) return (XmlNSDescriptorImpl)descrs[0];
}
return nsDescriptor instanceof XmlNSDescriptorImpl ? (XmlNSDescriptorImpl)nsDescriptor:null;
}
private static String getNamespace(final XmlTag tag, final String text) {
final String namespacePrefix = XmlUtil.findPrefixByQualifiedName(text);
final String namespaceByPrefix = tag.getNamespaceByPrefix(namespacePrefix);
if (!namespaceByPrefix.isEmpty()) return namespaceByPrefix;
final XmlTag rootTag = ((XmlFile)tag.getContainingFile()).getRootTag();
if (rootTag != null &&
"schema".equals(rootTag.getLocalName()) &&
XmlUtil.ourSchemaUrisList.indexOf(rootTag.getNamespace()) != -1 ) {
final String targetNS = rootTag.getAttributeValue(TARGET_NAMESPACE);
if (targetNS != null) {
final String targetNsPrefix = rootTag.getPrefixByNamespace(targetNS);
if (namespacePrefix.equals(targetNsPrefix) ||
(namespaceByPrefix.isEmpty() && targetNsPrefix == null)) {
return targetNS;
}
}
}
return namespaceByPrefix;
}
@Override
@NotNull
public String getCanonicalText() {
final String text = myElement.getText();
String name = myRange.getEndOffset() <= text.length() ? myRange.substring(text) : "";
if (!name.isEmpty() && nsPrefix != null && !nsPrefix.isEmpty()) {
name = nsPrefix + ":" + name;
}
return name;
}
@Override
public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
final String canonicalText = getCanonicalText();
final PsiElement element = ElementManipulators.getManipulator(myElement)
.handleContentChange(myElement, getRangeInElement(), newElementName);
myRange = new TextRange(myRange.getStartOffset(),myRange.getEndOffset() - (canonicalText.length() - newElementName.length()));
return element;
}
@Override
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
throw new IncorrectOperationException();
}
@Override
public boolean isReferenceTo(PsiElement element) {
return myElement.getManager().areElementsEquivalent(resolve(), element);
}
@Override
@NotNull
public Object[] getVariants() {
final XmlTag tag = PsiTreeUtil.getContextOfType(myElement, XmlTag.class, true);
if (tag == null || myType == null) return ArrayUtil.EMPTY_OBJECT_ARRAY;
return getVariants(tag, myType, nsPrefix);
}
public static Object[] getVariants(XmlTag tag, ReferenceType type, String prefix) {
String[] tagNames = null;
switch (type) {
case GroupReference:
tagNames = new String[] {SchemaReferencesProvider.GROUP_TAG_NAME};
break;
case AttributeGroupReference:
tagNames = new String[] {SchemaReferencesProvider.ATTRIBUTE_GROUP_TAG_NAME};
break;
case AttributeReference:
tagNames = new String[] {SchemaReferencesProvider.ATTRIBUTE_TAG_NAME};
break;
case ElementReference:
tagNames = new String[] {SchemaReferencesProvider.ELEMENT_TAG_NAME};
break;
case TypeReference:
tagNames = new String[] {SchemaReferencesProvider.SIMPLE_TYPE_TAG_NAME, SchemaReferencesProvider.COMPLEX_TYPE_TAG_NAME};
break;
}
final XmlDocument document = ((XmlFile)tag.getContainingFile()).getDocument();
if (document == null) {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
final XmlTag rootTag = document.getRootTag();
String ourNamespace = rootTag != null ? rootTag.getAttributeValue(TARGET_NAMESPACE) : "";
if (ourNamespace == null) ourNamespace = "";
CompletionProcessor processor = new CompletionProcessor(tag, prefix);
for(String namespace: tag.knownNamespaces()) {
if (ourNamespace.equals(namespace)) continue;
final XmlNSDescriptor nsDescriptor = tag.getNSDescriptor(namespace, true);
if (nsDescriptor instanceof XmlNSDescriptorImpl) {
processNamespace(namespace, processor, nsDescriptor, tagNames);
}
}
XmlNSDescriptor nsDescriptor = (XmlNSDescriptor)document.getMetaData();
if (nsDescriptor != null) {
processNamespace(
ourNamespace,
processor,
nsDescriptor,
tagNames
);
}
return ArrayUtil.toStringArray(processor.myElements);
}
private static void processNamespace(final String namespace,
final CompletionProcessor processor,
final XmlNSDescriptor nsDescriptor,
final String[] tagNames) {
processor.namespace = namespace;
final XmlNSDescriptorImpl xmlNSDescriptor = ((XmlNSDescriptorImpl)nsDescriptor);
XmlNSDescriptorImpl.processTagsInNamespace(
xmlNSDescriptor.getTag(),
tagNames,
processor
);
}
@Override
public boolean isSoft() {
return false;
}
private static class MyResolver implements ResolveCache.Resolver {
static final MyResolver INSTANCE = new MyResolver();
@Override
public PsiElement resolve(@NotNull PsiReference ref, boolean incompleteCode) {
return ((TypeOrElementOrAttributeReference)ref).resolveInner();
}
}
private static class CompletionProcessor implements PsiElementProcessor<XmlTag> {
final List<String> myElements = new ArrayList<String>(1);
String namespace;
final XmlTag tag;
private final String prefix;
CompletionProcessor(XmlTag tag, String prefix) {
this.tag = tag;
this.prefix = prefix;
}
@Override
public boolean execute(@NotNull final XmlTag element) {
String name = element.getAttributeValue(SchemaReferencesProvider.NAME_ATTR_NAME);
final String prefixByNamespace = tag.getPrefixByNamespace(namespace);
if (prefixByNamespace != null && !prefixByNamespace.isEmpty() && prefix == null) {
name = prefixByNamespace + ":" + name;
}
myElements.add( name );
return true;
}
}
}