blob: 97613a6f2bcf4a0216932f7181cfc84295e20a66 [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.codeInsight.daemon.EmptyResolveMessageProvider;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.xml.SchemaPrefixReference;
import com.intellij.psi.xml.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ProcessingContext;
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.List;
import java.util.regex.Pattern;
/**
* @by Maxim.Mossienko
* @author Konstantin Bulenkov
*/
public class SchemaReferencesProvider extends PsiReferenceProvider {
@NonNls private static final String VALUE_ATTR_NAME = "value";
@NonNls private static final String PATTERN_TAG_NAME = "pattern";
@NonNls static final String NAME_ATTR_NAME = "name";
@NonNls static final String MEMBER_TYPES_ATTR_NAME = "memberTypes";
@NonNls static final String ITEM_TYPE_ATTR_NAME = "itemType";
@NonNls static final String BASE_ATTR_NAME = "base";
@NonNls static final String GROUP_TAG_NAME = "group";
@NonNls static final String ATTRIBUTE_GROUP_TAG_NAME = "attributeGroup";
@NonNls static final String ATTRIBUTE_TAG_NAME = "attribute";
@NonNls static final String ELEMENT_TAG_NAME = "element";
@NonNls static final String SIMPLE_TYPE_TAG_NAME = "simpleType";
@NonNls static final String COMPLEX_TYPE_TAG_NAME = "complexType";
@NonNls static final String REF_ATTR_NAME = "ref";
@NonNls static final String TYPE_ATTR_NAME = "type";
@NonNls static final String SUBSTITUTION_GROUP_ATTR_NAME = "substitutionGroup";
public String[] getCandidateAttributeNamesForSchemaReferences() {
return new String[] {REF_ATTR_NAME,TYPE_ATTR_NAME, BASE_ATTR_NAME,NAME_ATTR_NAME, SUBSTITUTION_GROUP_ATTR_NAME,MEMBER_TYPES_ATTR_NAME,
VALUE_ATTR_NAME, ITEM_TYPE_ATTR_NAME};
}
static class RegExpReference extends BasicAttributeValueReference implements EmptyResolveMessageProvider {
private String message;
public RegExpReference(final PsiElement element) {
super(element);
}
private static final Pattern pattern = Pattern.compile("^(?:\\\\i|\\\\l)");
private static final Pattern pattern2 = Pattern.compile("([^\\\\])(?:\\\\i|\\\\l)");
@Override
@Nullable
public PsiElement resolve() {
try {
String text = getCanonicalText();
// \i and \l are special classes that does not present in java reg exps, so replace their occurences with more usable \w
text = pattern2.matcher(pattern.matcher(text).replaceFirst("\\\\w")).replaceAll("$1\\\\w");
Pattern.compile(text);
message = null;
return myElement;
}
catch (Exception e) {
message = PsiBundle.message("invalid.regular.expression.message", getCanonicalText());
return null;
}
}
@Override
@NotNull
public Object[] getVariants() {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
@Override
public boolean isSoft() {
return false;
}
@Override
@NotNull
public String getUnresolvedMessagePattern() {
return message;
}
}
public static class NameReference implements PsiReference {
private final PsiElement myElement;
public NameReference(PsiElement element) {
myElement = element;
}
@Override
public PsiElement getElement() {
return myElement;
}
@Override
public TextRange getRangeInElement() {
return ElementManipulators.getValueTextRange(myElement);
}
@Override
@Nullable
public PsiElement resolve() {
return myElement.getParent().getParent();
}
@Override
@NotNull
public String getCanonicalText() {
String text = myElement.getText();
return text.substring(1,text.length()- 1);
}
@Override
public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
return ElementManipulators.getManipulator(myElement).handleContentChange(
myElement,
getRangeInElement(),
newElementName.substring(newElementName.indexOf(':') + 1)
);
}
@Override
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
return null;
}
@Override
public boolean isReferenceTo(PsiElement element) {
return myElement.getManager().areElementsEquivalent(resolve(), element);
}
@Override
@NotNull
public Object[] getVariants() {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
@Override
public boolean isSoft() {
return true;
}
}
@Override
@NotNull
public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull final ProcessingContext context) {
final PsiElement parent = element.getParent();
if (!(parent instanceof XmlAttribute)) return PsiReference.EMPTY_ARRAY;
final String attrName = ((XmlAttribute)parent).getName();
if (VALUE_ATTR_NAME.equals(attrName)) {
if (PATTERN_TAG_NAME.equals(((XmlAttribute)parent).getParent().getLocalName())) {
return new PsiReference[] { new RegExpReference(element) };
} else {
return PsiReference.EMPTY_ARRAY;
}
} else if (NAME_ATTR_NAME.equals(attrName)) {
return new PsiReference[] { new NameReference(element) };
} else if (MEMBER_TYPES_ATTR_NAME.equals(attrName)) {
final List<PsiReference> result = new ArrayList<PsiReference>(1);
final String text = element.getText();
int lastIndex = 1;
final int testLength = text.length();
for(int i = 1; i < testLength; ++i) {
if (Character.isWhitespace(text.charAt(i))) {
if (lastIndex != i) result.add( new TypeOrElementOrAttributeReference(element, new TextRange(lastIndex, i) ) );
lastIndex = i + 1;
}
}
if (lastIndex != testLength - 1) result.add( new TypeOrElementOrAttributeReference(element, new TextRange(lastIndex, testLength - 1) ) );
return result.toArray(new PsiReference[result.size()]);
} else {
final PsiReference prefix = createSchemaPrefixReference(element);
final PsiReference ref = createTypeOrElementOrAttributeReference(element, prefix == null ? null : prefix.getCanonicalText());
return prefix == null ? new PsiReference[] {ref} : new PsiReference[] {ref, prefix};
}
}
public static PsiReference createTypeOrElementOrAttributeReference(final PsiElement element) {
return createTypeOrElementOrAttributeReference(element, null);
}
public static PsiReference createTypeOrElementOrAttributeReference(final PsiElement element, String ns) {
final int length = element.getTextLength();
int offset = (element instanceof XmlAttributeValue) ?
XmlUtil.findPrefixByQualifiedName(((XmlAttributeValue)element).getValue()).length() : 0;
if (offset > 0) offset++;
final TypeOrElementOrAttributeReference ref = new TypeOrElementOrAttributeReference(element, length >= 2 ? new TextRange(1 + offset, length - 1) : TextRange.EMPTY_RANGE);
ref.setNamespacePrefix(ns);
return ref;
}
@Nullable
private static PsiReference createSchemaPrefixReference(final PsiElement element) {
if (element instanceof XmlAttributeValue) {
final XmlAttributeValue attributeValue = (XmlAttributeValue)element;
final String prefix = XmlUtil.findPrefixByQualifiedName(attributeValue.getValue());
if (!prefix.isEmpty()) {
return new SchemaPrefixReference(attributeValue, TextRange.from(1, prefix.length()), prefix, null);
}
}
return null;
}
@Nullable
public static XmlNSDescriptorImpl findRedefinedDescriptor(XmlTag tag, String text) {
final String localName = XmlUtil.findLocalNameByQualifiedName(text);
for(XmlTag parentTag = tag.getParentTag(); parentTag != null; parentTag = parentTag.getParentTag()) {
if (localName.equals(parentTag.getAttributeValue("name"))) {
final XmlTag grandParent = parentTag.getParentTag();
if (grandParent != null && "redefine".equals(grandParent.getLocalName())) {
return XmlNSDescriptorImpl.getRedefinedElementDescriptor(grandParent);
}
}
}
return null;
}
}