package com.intellij.javascript.flex; import com.intellij.codeInsight.daemon.EmptyResolveMessageProvider; import com.intellij.javascript.flex.mxml.FlexCommonTypeNames; import com.intellij.lang.javascript.flex.FlexBundle; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.impl.source.resolve.reference.impl.providers.BasicAttributeValueReference; import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileBasedUserDataCache; import com.intellij.psi.util.CachedValue; import com.intellij.psi.xml.*; import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.text.StringTokenizer; import com.intellij.xml.util.XmlTagUtil; import gnu.trove.THashMap; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; // this class is not a reference contributor any more, it is kept for MXML Design Preview plugin compatibility public class FlexReferenceContributor { static final String TRANSITION_TAG_NAME = "Transition"; public static final String SOURCE_ATTR_NAME = "source"; public static final String DESTINATION_ATTR_NAME = "destination"; static final String DELIMS = ", "; public static final String CLASS_REFERENCE = "ClassReference"; public static boolean isClassReferenceType(final String type) { return "Class".equals(type) || FlexCommonTypeNames.IFACTORY.equals(type); } public static class StateReference extends BasicAttributeValueReference implements EmptyResolveMessageProvider, PsiPolyVariantReference { private static final String DUMMY_STATE_GROUP_TAG = "DummyStateGroupTag"; private static FileBasedUserDataCache<Map<String, XmlTag>> statesCache = new FileBasedUserDataCache<Map<String, XmlTag>>() { public Key<CachedValue<Map<String, XmlTag>>> ourDataKey = Key.create("mx.states"); @Override protected Map<String, XmlTag> doCompute(PsiFile file) { final Map<String, XmlTag> tags = new THashMap<>(); file.getOriginalFile().accept(new XmlRecursiveElementVisitor() { @Override public void visitXmlTag(XmlTag tag) { super.visitXmlTag(tag); if ("State".equals(tag.getLocalName())) { String name = tag.getAttributeValue(FlexStateElementNames.NAME); if (name != null) tags.put(name, tag); String groups = tag.getAttributeValue(FlexStateElementNames.STATE_GROUPS); if (groups != null) { StringTokenizer tokenizer = new StringTokenizer(groups, DELIMS); while (tokenizer.hasMoreElements()) { String s = tokenizer.nextElement(); XmlTag cachedTag = tags.get(s); if (cachedTag == null) { PsiFile fromText = PsiFileFactory.getInstance(tag.getProject()) .createFileFromText("dummy.mxml", FlexApplicationComponent.MXML, "<" + DUMMY_STATE_GROUP_TAG + " name=\"" + s + "\" />"); cachedTag = ((XmlFile)fromText).getDocument().getRootTag(); tags.put(s, cachedTag); } } } } } }); return tags; } @Override protected Key<CachedValue<Map<String, XmlTag>>> getKey() { return ourDataKey; } }; private final boolean myStateGroupsOnly; public StateReference(PsiElement element) { super(element); myStateGroupsOnly = false; } public StateReference(PsiElement element, TextRange range) { this(element, range, false); } public StateReference(PsiElement element, TextRange range, boolean stateGroupsOnly) { super(element, range); myStateGroupsOnly = stateGroupsOnly; } public PsiElement resolve() { ResolveResult[] results = multiResolve(false); return results.length == 1 ? results[0].getElement() : null; } @NotNull public ResolveResult[] multiResolve(boolean incompleteCode) { final List<ResolveResult> result = new ArrayList<>(1); process(new StateProcessor() { public boolean process(@NotNull final XmlTag t, @NotNull String name) { result.add(new ResolveResult() { public PsiElement getElement() { return t.getAttribute(FlexStateElementNames.NAME).getValueElement(); } public boolean isValidResult() { return true; } }); return true; } public String getHint() { return getCanonicalText(); } }); return result.toArray(new ResolveResult[result.size()]); } interface StateProcessor { boolean process(@NotNull XmlTag t, @NotNull String name); @Nullable String getHint(); } @NotNull public Object[] getVariants() { final Set<String> list = new THashSet<>(); process(new StateProcessor() { public boolean process(@NotNull XmlTag t, @NotNull String name) { list.add(name); return true; } public String getHint() { return null; } }); final PsiElement parent = myElement instanceof XmlAttributeValue ? myElement.getParent() : null; final PsiElement tag = parent instanceof XmlAttribute ? parent.getParent() : null; if (tag instanceof XmlTag && TRANSITION_TAG_NAME.equals(((XmlTag)tag).getLocalName())) { list.add("*"); } return ArrayUtil.toObjectArray(list); } @Override public boolean isReferenceTo(PsiElement element) { for (ResolveResult r : multiResolve(false)) { if (myElement.getManager().areElementsEquivalent(element, r.getElement())) return true; } return false; } private boolean process(StateProcessor processor) { String s = processor.getHint(); Map<String, XmlTag> map = statesCache.compute(getElement().getContainingFile()); if (s == null) { for (Map.Entry<String, XmlTag> t : map.entrySet()) { XmlTag tag = t.getValue(); if (myStateGroupsOnly && !DUMMY_STATE_GROUP_TAG.equals(tag.getName())) continue; if (!processor.process(tag, t.getKey())) return false; } } else { XmlTag tag = map.get(s); if (myStateGroupsOnly && !DUMMY_STATE_GROUP_TAG.equals(tag.getName())) return true; if (tag != null) return processor.process(tag, s); } return true; } public boolean isSoft() { return false; } @NotNull public String getUnresolvedMessagePattern() { return FlexBundle.message("cannot.resolve.state"); } public PsiElement handleElementRename(final String newElementName) throws IncorrectOperationException { if (myElement instanceof XmlTag) { final XmlToken startTagNameElement = XmlTagUtil.getStartTagNameElement((XmlTag)myElement); if (startTagNameElement != null) { final TextRange rangeInTagNameElement = myRange.shiftRight(-(startTagNameElement.getTextOffset() - myElement.getTextOffset())); final TextRange startTagNameElementRange = startTagNameElement.getTextRange().shiftRight(-myElement.getTextRange().getStartOffset()); if (startTagNameElementRange.contains(rangeInTagNameElement)) { final StringBuilder newName = new StringBuilder(startTagNameElement.getText()); newName.replace(rangeInTagNameElement.getStartOffset(), rangeInTagNameElement.getEndOffset(), newElementName); ((XmlTag)myElement).setName(newName.toString()); } } return myElement; } return super.handleElementRename(newElementName); } } }