blob: 27d23f84994250b21d182506ed806337b44c52e8 [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.extensions.Extensions;
import com.intellij.openapi.util.Key;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.XmlRecursiveElementVisitor;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlComment;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ArrayUtil;
import com.intellij.xml.util.XmlDeclareIdInCommentAction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* @author peter
*/
public class IdRefReference extends BasicAttributeValueReference {
private final boolean myIdAttrsOnly;
public IdRefReference(final PsiElement element, int offset, boolean idAttrsOnly) {
super(element, offset);
myIdAttrsOnly = idAttrsOnly;
}
public IdRefReference(final PsiElement element) {
super(element);
myIdAttrsOnly = false;
}
@Nullable
protected PsiElement getIdValueElement(PsiElement element) {
if (element instanceof XmlTag) {
final XmlTag tag = (XmlTag)element;
XmlAttribute attribute = tag.getAttribute(IdReferenceProvider.ID_ATTR_NAME, null);
if (!myIdAttrsOnly) {
if (attribute == null) {
attribute = tag.getAttribute(IdReferenceProvider.NAME_ATTR_NAME, null);
}
if (attribute == null) {
attribute = tag.getAttribute(IdReferenceProvider.STYLE_ID_ATTR_NAME, null);
}
}
return attribute != null ? attribute.getValueElement() : getImplicitIdRefValueElement(tag);
}
else {
return element;
}
}
@Nullable
protected String getIdValue(final PsiElement element) {
if (element instanceof XmlTag) {
final XmlTag tag = (XmlTag)element;
String s = tag.getAttributeValue(IdReferenceProvider.ID_ATTR_NAME);
if (!myIdAttrsOnly) {
if (s == null) s = tag.getAttributeValue(IdReferenceProvider.NAME_ATTR_NAME);
if (s == null) s = tag.getAttributeValue(IdReferenceProvider.STYLE_ID_ATTR_NAME);
}
return s != null ? s: getImplicitIdRefValue(tag);
} else if (element instanceof PsiComment) {
return getImplicitIdValue((PsiComment) element);
}
return null;
}
@Nullable
public static XmlAttribute getImplicitIdRefAttr(@NotNull XmlTag tag) {
for (ImplicitIdRefProvider idRefProvider : Extensions.getExtensions(ImplicitIdRefProvider.EXTENSION_POINT_NAME)) {
XmlAttribute value = idRefProvider.getIdRefAttribute(tag);
if (value != null) return value;
}
return null;
}
@Nullable
public static XmlAttributeValue getImplicitIdRefValueElement(@NotNull XmlTag tag) {
for (ImplicitIdRefProvider idRefProvider : Extensions.getExtensions(ImplicitIdRefProvider.EXTENSION_POINT_NAME)) {
XmlAttribute value = idRefProvider.getIdRefAttribute(tag);
if (value != null) return value.getValueElement();
}
return null;
}
@Nullable
public static String getImplicitIdRefValue(@NotNull XmlTag tag) {
XmlAttributeValue attribute = getImplicitIdRefValueElement(tag);
return attribute != null ? attribute.getValue() : null;
}
protected static boolean isAcceptableTagType(final XmlTag subTag) {
return subTag.getAttributeValue(IdReferenceProvider.ID_ATTR_NAME) != null ||
subTag.getAttributeValue(IdReferenceProvider.FOR_ATTR_NAME) != null || getImplicitIdRefValue(subTag) != null ||
(subTag.getAttributeValue(IdReferenceProvider.NAME_ATTR_NAME) != null &&
!subTag.getName().contains(".directive"));
}
private static final FileBasedUserDataCache<List<PsiElement>> ourCachedIdsCache = new FileBasedUserDataCache<List<PsiElement>>() {
private final Key<CachedValue<List<PsiElement>>> ourCachedIdsValueKey = Key.create("my.ids.cached.value");
@Override
protected List<PsiElement> doCompute(PsiFile file) {
final List<PsiElement> result = new ArrayList<PsiElement>();
file.accept(new XmlRecursiveElementVisitor(true) {
@Override
public void visitXmlTag(XmlTag tag) {
if (isAcceptableTagType(tag)) result.add(tag);
super.visitXmlTag(tag);
}
@Override
public void visitComment(final PsiComment comment) {
if (isDeclarationComment(comment)) result.add(comment);
super.visitComment(comment);
}
@Override
public void visitXmlComment(final XmlComment comment) {
if (isDeclarationComment(comment)) result.add(comment);
super.visitComment(comment);
}
});
return result;
}
@Override
protected Key<CachedValue<List<PsiElement>>> getKey() {
return ourCachedIdsValueKey;
}
};
private static boolean isDeclarationComment(@NotNull final PsiComment comment) {
return comment.getText().contains("@declare id=");
}
@Nullable
private static String getImplicitIdValue(@NotNull final PsiComment comment) {
return XmlDeclareIdInCommentAction.getImplicitlyDeclaredId(comment);
}
private void process(PsiElementProcessor<PsiElement> processor) {
final PsiFile psiFile = getElement().getContainingFile();
process(processor, psiFile);
}
public static void process(final PsiElementProcessor<PsiElement> processor, PsiFile file) {
for (PsiElement e : ourCachedIdsCache.compute(file)) {
if (!processor.execute(e)) return;
}
}
@Override
@Nullable
public PsiElement resolve() {
final PsiElement[] result = new PsiElement[1];
process(new PsiElementProcessor<PsiElement>() {
final String canonicalText = getCanonicalText();
@Override
public boolean execute(@NotNull final PsiElement element) {
final String idValue = getIdValue(element);
if (idValue != null && idValue.equals(canonicalText)) {
result[0] = getIdValueElement(element);
return false;
}
return true;
}
});
return result[0];
}
@Override
@NotNull
public Object[] getVariants() {
final List<String> result = new LinkedList<String>();
process(new PsiElementProcessor<PsiElement>() {
@Override
public boolean execute(@NotNull final PsiElement element) {
String value = getIdValue(element);
if (value != null) {
result.add(value);
}
return true;
}
});
return ArrayUtil.toObjectArray(result);
}
@Override
public boolean isSoft() {
return false;
}
}