package com.intellij.javascript.flex;
import com.intellij.codeInsight.daemon.EmptyResolveMessageProvider;
import com.intellij.codeInsight.daemon.XmlErrorMessages;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.LocalQuickFixProvider;
import com.intellij.javascript.flex.resolve.ActionScriptClassResolver;
import com.intellij.lang.ASTNode;
import com.intellij.lang.LanguageNamesValidation;
import com.intellij.lang.javascript.JavaScriptSupportLoader;
import com.intellij.lang.javascript.flex.ReferenceSupport;
import com.intellij.lang.javascript.psi.ecmal4.JSAttribute;
import com.intellij.lang.javascript.psi.ecmal4.JSAttributeNameValuePair;
import com.intellij.lang.javascript.psi.ecmal4.impl.JSAttributeImpl;
import com.intellij.lang.javascript.psi.ecmal4.impl.JSAttributeNameValuePairImpl;
import com.intellij.lang.javascript.psi.impl.JSChangeUtil;
import com.intellij.lang.javascript.psi.impl.JSReferenceSet;
import com.intellij.lang.javascript.validation.fixes.CreateClassOrInterfaceFix;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceProvider;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ProcessingContext;
import com.intellij.xml.XmlAttributeDescriptor;
import com.intellij.xml.XmlElementDescriptor;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author yole
*/
public class FlexAttributeReferenceProvider extends PsiReferenceProvider {
@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) {
JSAttributeNameValuePairImpl nameValuePair = (JSAttributeNameValuePairImpl) element;
final @NonNls String name = nameValuePair.getName();
return valueRefs(nameValuePair, name);
}
@NonNls private static final String BUNDLE_ATTR_NAME = "bundle";
private static final FlexPropertiesSupport.PropertyReferenceInfoProvider<JSAttributeNameValuePairImpl> ourPropertyInfoProvider =
new FlexPropertiesSupport.PropertyReferenceInfoProvider<JSAttributeNameValuePairImpl>() {
@Nullable
public TextRange getReferenceRange(JSAttributeNameValuePairImpl element) {
return getValueRange(element);
}
public String getBundleName(JSAttributeNameValuePairImpl element) {
JSAttributeNameValuePair pair = ((JSAttribute)element.getParent()).getValueByName(BUNDLE_ATTR_NAME);
return pair != null ? pair.getSimpleValue() : null;
}
public boolean isSoft(JSAttributeNameValuePairImpl element) {
return false;
}
};
private static final Key<JSReferenceSet> METADATA_REFERENCE_KEY = Key.create("com.intellij.lang.javascript.METADATA_REFERENCE_KEY");
private static final FlexPropertiesSupport.BundleReferenceInfoProvider<JSAttributeNameValuePairImpl> ourBundleInfoProvider =
new FlexPropertiesSupport.BundleReferenceInfoProvider<JSAttributeNameValuePairImpl>() {
public TextRange getReferenceRange(JSAttributeNameValuePairImpl element) {
return getValueRange(element);
}
public boolean isSoft(JSAttributeNameValuePairImpl element) {
return false;
}
};
public static PsiReference[] valueRefs(JSAttributeNameValuePairImpl element, String name) {
final JSAttributeImpl jsAttribute = (JSAttributeImpl)element.getParent();
final XmlElementDescriptor descriptor = jsAttribute.getBackedDescriptor();
final XmlAttributeDescriptor attributeDescriptor =
descriptor == null ? null : descriptor.getAttributeDescriptor(StringUtil.notNullize(name, JSAttributeNameValuePair.DEFAULT), null);
if (name == null) {
return getDefaultPropertyRefs(element, attributeDescriptor);
}
final String baseClassFqns = attributeDescriptor == null ? null : attributeDescriptor.getDefaultValue();
if (baseClassFqns != null) {
return getClassRefs(element, baseClassFqns);
}
else if ("source".equals(name)) {
return getPathRefsCheckingParent(element);
}
else if ("key".equals(name)) {
return getPropertyRefsCheckingParent(element);
}
else if (BUNDLE_ATTR_NAME.equals(name)) {
return getBundleRefsCheckingParent(element);
}
else if (attributeDescriptor != null && attributeDescriptor.isEnumerated()) {
final String[] enumeratedValues = attributeDescriptor.getEnumeratedValues();
final TextRange range = getValueRange(element);
if (enumeratedValues != null && enumeratedValues.length > 0 && range != null) {
return new PsiReference[]{new EnumeratedAttributeValueReference(element, range, enumeratedValues)};
}
}
return PsiReference.EMPTY_ARRAY;
}
private static PsiReference[] getClassRefs(@NotNull final JSAttributeNameValuePairImpl element, final @NotNull String baseClassFqns) {
final ASTNode valueNode = element.findValueNode();
if (valueNode != null) {
final int offsetInParent = valueNode.getPsi().getStartOffsetInParent();
final String text = valueNode.getText();
JSReferenceSet referenceSet = element.getUserData(METADATA_REFERENCE_KEY);
if (referenceSet == null) {
referenceSet = new JSReferenceSet(element, "", offsetInParent, false, true);
element.putUserData(METADATA_REFERENCE_KEY, referenceSet);
referenceSet.setLocalQuickFixProvider(new ClassRefQuickFixProvider(element, referenceSet));
}
if (!"Object".equals(baseClassFqns)) {
referenceSet.setBaseClassFqns(StringUtil.split(baseClassFqns, ","));
}
referenceSet.update(text, offsetInParent);
return referenceSet.getReferences();
}
return PsiReference.EMPTY_ARRAY;
}
private static PsiReference[] getDefaultPropertyRefs(final JSAttributeNameValuePairImpl element, final XmlAttributeDescriptor attributeDescriptor) {
final String baseClassFqn = attributeDescriptor == null ? null : attributeDescriptor.getDefaultValue();
if (baseClassFqn != null) {
return getClassRefs(element, baseClassFqn);
}
final @NonNls String parentName = ((JSAttribute)element.getParent()).getName();
if ("ResourceBundle".equals(parentName)) {
return FlexPropertiesSupport.getResourceBundleReference(element, ourBundleInfoProvider);
}
if (FlexAnnotationNames.EMBED.equals(parentName)) return getPathRefs(element);
if ("DefaultProperty".equals(parentName)) {
final ASTNode valueNode = element.findValueNode();
if (valueNode != null) {
JSReferenceSet referenceSet = element.getUserData(METADATA_REFERENCE_KEY);
if (referenceSet == null) {
referenceSet = new JSReferenceSet(element, false);
element.putUserData(METADATA_REFERENCE_KEY, referenceSet);
}
referenceSet.update(valueNode.getText(), valueNode.getPsi().getStartOffsetInParent());
return referenceSet.getReferences();
}
}
return PsiReference.EMPTY_ARRAY;
}
private static PsiReference[] getBundleRefsCheckingParent(JSAttributeNameValuePairImpl element) {
JSAttribute attribute = (JSAttribute)element.getParent();
final @NonNls String parentName = attribute.getName();
if (!FlexAnnotationNames.RESOURCE.equals(parentName)) return PsiReference.EMPTY_ARRAY;
return FlexPropertiesSupport.getResourceBundleReference(element, ourBundleInfoProvider);
}
private static PsiReference[] getPropertyRefsCheckingParent(JSAttributeNameValuePairImpl element) {
JSAttribute attribute = (JSAttribute)element.getParent();
final @NonNls String parentName = attribute.getName();
if (!FlexAnnotationNames.RESOURCE.equals(parentName)) return PsiReference.EMPTY_ARRAY;
return FlexPropertiesSupport.getPropertyReferences(element, ourPropertyInfoProvider);
}
private static PsiReference[] getPathRefsCheckingParent(JSAttributeNameValuePairImpl element) {
final @NonNls String parentName = ((JSAttribute)element.getParent()).getName();
if (!FlexAnnotationNames.EMBED.equals(parentName)) return PsiReference.EMPTY_ARRAY;
return getPathRefs(element);
}
private static PsiReference[] getPathRefs(JSAttributeNameValuePairImpl element) {
final ASTNode valueNode = element.findValueNode();
if (valueNode != null && StringUtil.isQuotedString(valueNode.getText())) {
return ReferenceSupport.getFileRefs(element, valueNode.getPsi(), valueNode.getPsi().getStartOffsetInParent() + 1,
ReferenceSupport.LookupOptions.EMBEDDED_ASSET);
}
return PsiReference.EMPTY_ARRAY;
}
@Nullable
private static TextRange getValueRange(JSAttributeNameValuePairImpl element) {
ASTNode valueNode = element.findValueNode();
if (valueNode == null) return null;
int valueStart = valueNode.getPsi().getStartOffsetInParent();
int length = valueNode.getTextLength();
return StringUtil.isQuotedString(valueNode.getText())
? new TextRange(valueStart + 1, valueStart + length - 1) : new TextRange(valueStart, valueStart + length);
}
private static class ClassRefQuickFixProvider implements LocalQuickFixProvider {
private final JSAttributeNameValuePairImpl myElement;
private final JSReferenceSet myReferenceSet;
public ClassRefQuickFixProvider(JSAttributeNameValuePairImpl element, JSReferenceSet referenceSet) {
myElement = element;
myReferenceSet = referenceSet;
}
public LocalQuickFix[] getQuickFixes() {
final String fqn = myElement.getSimpleValue();
if (fqn != null && LanguageNamesValidation.INSTANCE.forLanguage(JavaScriptSupportLoader.JAVASCRIPT.getLanguage())
.isIdentifier(StringUtil.getShortName(fqn), null)) {
final String[] baseClasses = myReferenceSet.getBaseClassFqns();
String baseClass = null;
if (baseClasses.length == 1 && !"Object".equals(baseClasses[0])) {
baseClass = baseClasses[0];
}
else if (baseClasses.length > 0) {
//
for (String aClass : baseClasses) {
if (ActionScriptClassResolver.findClassByQNameStatic(aClass, myElement) != null) {
if (baseClass == null) {
baseClass = aClass;
}
else {
// more than one class resolved, but CreateClassOrInterfaceFix accepts only one
baseClass = null;
break;
}
}
}
}
final CreateClassOrInterfaceFix fix = new CreateClassOrInterfaceFix(fqn, baseClass, myElement);
fix.setCreatedClassFqnConsumer(fqn1 -> {
if (myElement.isValid()) {
if (!fqn1.equals(StringUtil.stripQuotesAroundValue(myElement.getValueNode().getText()))) {
final ASTNode oldValueNode = myElement.getValueNode();
final String oldText = oldValueNode.getText();
char quoteChar = oldText.length() > 0 ? oldText.charAt(0) : '"';
if (quoteChar != '\'' && quoteChar != '"') {
quoteChar = '"';
}
final ASTNode newNode = JSChangeUtil.createExpressionFromText(myElement.getProject(), quoteChar + fqn1 + quoteChar);
myElement.getNode().replaceChild(oldValueNode, newNode.getFirstChildNode());
}
}
});
return new LocalQuickFix[]{fix};
}
return LocalQuickFix.EMPTY_ARRAY;
}
}
private static class EnumeratedAttributeValueReference implements PsiReference, EmptyResolveMessageProvider {
private final JSAttributeNameValuePairImpl myElement;
private final String[] myAllowedValues;
private final String myValue;
private final TextRange myRange;
private final boolean myResolveOk;
public EnumeratedAttributeValueReference(final JSAttributeNameValuePairImpl element,
final TextRange range,
final String[] allowedValues) {
myElement = element;
myAllowedValues = allowedValues;
myRange = range;
final ASTNode valueNode = element.getValueNode();
myValue = valueNode == null ? "" : StringUtil.stripQuotesAroundValue(valueNode.getText());
myResolveOk = ArrayUtil.contains(myValue, allowedValues);
}
public PsiElement getElement() {
return myElement;
}
public TextRange getRangeInElement() {
return myRange;
}
public PsiElement resolve() {
if (myResolveOk) {
final XmlAttributeDescriptor attributeDescriptor =
((JSAttributeImpl)myElement.getParent()).getBackedDescriptor().getAttributeDescriptor(myElement.getName(), null);
return attributeDescriptor == null ? null : attributeDescriptor.getDeclaration();
}
return null;
}
@NotNull
public String getCanonicalText() {
return myValue;
}
public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
throw new IncorrectOperationException();
}
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
throw new IncorrectOperationException();
}
public boolean isReferenceTo(PsiElement element) {
return false;
}
@NotNull
public Object[] getVariants() {
return myAllowedValues;
}
public boolean isSoft() {
return false;
}
@NotNull
public String getUnresolvedMessagePattern() {
return XmlErrorMessages.message("wrong.value", "attribute");
}
}
}