package com.intellij.javascript.flex.mxml; import com.intellij.javascript.flex.FlexPredefinedTagNames; import com.intellij.lang.javascript.JSLanguageDialect; import com.intellij.lang.javascript.JavaScriptSupportLoader; import com.intellij.lang.javascript.flex.XmlBackedJSClassImpl; import com.intellij.lang.javascript.psi.JSField; import com.intellij.lang.javascript.psi.JSFile; import com.intellij.lang.javascript.psi.JSType; import com.intellij.lang.javascript.psi.ecmal4.*; import com.intellij.lang.javascript.psi.resolve.ImplicitJSFieldImpl; import com.intellij.lang.javascript.psi.resolve.JSResolveUtil; import com.intellij.lang.javascript.psi.resolve.ResolveProcessor; import com.intellij.lang.javascript.psi.types.JSContext; import com.intellij.lang.javascript.psi.types.JSNamedType; import com.intellij.lang.javascript.psi.types.JSTypeSourceFactory; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.util.CachedValue; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlElement; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Map; import static com.intellij.lang.javascript.psi.JSCommonTypeNames.OBJECT_CLASS_NAME; /** * @author yole */ public class MxmlJSClass extends XmlBackedJSClassImpl { @NonNls public static final String XML_TAG_NAME = "XML"; @NonNls public static final String XMLLIST_TAG_NAME = "XMLList"; @NonNls public static final String PRIVATE_TAG_NAME = MxmlLanguageInjector.PRIVATE_TAG_NAME; public static final @NonNls String MXML_URI4 = "library://ns.adobe.com/flex/spark"; public static final @NonNls String MXML_URI5 = "library://ns.adobe.com/flex/halo"; public static final @NonNls String MXML_URI6 = "library://ns.adobe.com/flex/mx"; public static final @NonNls String[] MXML_URIS = {JavaScriptSupportLoader.MXML_URI, JavaScriptSupportLoader.MXML_URI3, MXML_URI4, MXML_URI5, MXML_URI6}; public static final @NonNls String[] FLEX_4_NAMESPACES = {JavaScriptSupportLoader.MXML_URI3, MXML_URI4, MXML_URI5, MXML_URI6}; private static final String OPERATION_TAG_NAME = "operation"; private static final String HTTP_SERVICE_TAG_NAME = "HTTPService"; private static final String WEB_SERVICE_TAG_NAME = "WebService"; private static final String[] REQUEST_TAG_POSSIBLE_NAMESPACES = {JavaScriptSupportLoader.MXML_URI, MXML_URI4, MXML_URI6}; private static final String REQUEST_TAG_NAME = "request"; private static final String[] TAGS_THAT_ALLOW_ANY_XML_CONTENT = {PRIVATE_TAG_NAME, XML_TAG_NAME, XMLLIST_TAG_NAME, FlexPredefinedTagNames.MODEL}; @NonNls private static final String FXG_SUPER_CLASS = "spark.core.SpriteVisualElement"; private static final Logger LOG = Logger.getInstance(MxmlJSClass.class); private static Key<CachedValue<List<JSField>>> ourSkinComponentPredefinedFieldsKey = Key.create("ourskinComponentPredefinedVarsKey"); private static MxmlResolveUtil.ImplicitFieldProvider skinComponentPredefinedFields = new MxmlResolveUtil.ImplicitFieldProvider() { protected void doComputeVars(final List<JSField> vars, final XmlFile xmlFile) { for (XmlTag t : xmlFile.getDocument().getRootTag().findSubTags(FlexPredefinedTagNames.METADATA, JavaScriptSupportLoader.MXML_URI3)) { JSResolveUtil.processInjectedFileForTag(t, new JSResolveUtil.JSInjectedFilesVisitor() { @Override protected void process(JSFile file) { for (PsiElement elt : file.getChildren()) { if (elt instanceof JSAttributeList) { JSAttribute[] hostAnnotation = ((JSAttributeList)elt).getAttributesByName("HostComponent"); if (hostAnnotation.length == 1 && vars.size() == 0) { JSAttributeNameValuePair valuePair = hostAnnotation[0].getValueByName(null); vars.add( new ImplicitJSFieldImpl( "hostComponent", valuePair != null ? valuePair.getSimpleValue() : OBJECT_CLASS_NAME, JSAttributeList.AccessType.PUBLIC, xmlFile ) ); return; } } } } }); } } }; private static MxmlResolveUtil.ImplicitFieldProvider inlineComponentRenderPredefinedVars = new MxmlResolveUtil.ImplicitFieldProvider() { protected void doComputeVars(List<JSField> vars, XmlFile xmlFile) { JSClass cls = XmlBackedJSClassFactory.getXmlBackedClass(xmlFile); final JSType type = JSNamedType.createType(cls.getQualifiedName(), JSTypeSourceFactory.createTypeSource(cls, true), JSContext.INSTANCE); vars.add(new ImplicitJSFieldImpl("outerDocument", type, xmlFile)); } }; private static Key<CachedValue<List<JSField>>> ourInlineComponentRenderPredefinedVarsKey = Key.create("ourInlineComponentRenderPredefinedVarsKey"); private volatile JSReferenceList myImplementsList; private final boolean isFxgBackedClass; public MxmlJSClass(XmlTag tag) { super(tag); final PsiFile psiFile = tag.getContainingFile(); isFxgBackedClass = psiFile != null && isFxgFile(psiFile); } public static XmlTag[] findLanguageSubTags(final XmlTag tag, final String languageTagName) { return tag.findSubTags(languageTagName, getLanguageNamespace(tag)); } public static boolean isFxgFile(final PsiFile file) { return JavaScriptSupportLoader.isFxgFile(file.getViewProvider().getVirtualFile()); } @NotNull public static String getLanguageNamespace(final XmlTag rootTag) { assert JavaScriptSupportLoader.isFlexMxmFile(rootTag.getContainingFile()) : rootTag.getContainingFile(); return rootTag.getPrefixByNamespace(JavaScriptSupportLoader.MXML_URI3) != null ? JavaScriptSupportLoader.MXML_URI3 : JavaScriptSupportLoader.MXML_URI; } @Override protected String getSuperClassName() { if (isFxgBackedClass) { return FXG_SUPER_CLASS; } return super.getSuperClassName(); } @Override public String getName() { XmlTag parent = getParent(); if (parent.getParentTag() != null && isComponentTag(parent.getParentTag())) { String explicitName = getExplicitName(); if (explicitName != null) { return explicitName; } } return super.getName(); } @Nullable private String getExplicitName() { XmlTag parent = getParent(); if (parent.getParentTag() != null) { return parent.getParentTag().getAttributeValue(CLASS_NAME_ATTRIBUTE_NAME, parent.getParentTag().getNamespace()); } else { return null; } } @Nullable @Override public JSReferenceList getImplementsList() { if (isFxgBackedClass) { return null; } JSReferenceList refList = myImplementsList; if (refList == null) { final XmlTag rootTag = getParent(); myImplementsList = refList = createReferenceList(rootTag != null ? rootTag.getAttributeValue(IMPLEMENTS_ATTRIBUTE) : null); } return refList; } @Override public void addToImplementsList(String refText) { XmlAttribute attribute = getParent().getAttribute(IMPLEMENTS_ATTRIBUTE); if (attribute == null) { getParent().setAttribute(IMPLEMENTS_ATTRIBUTE, refText); } else { attribute.setValue(attribute.getValue() + ", " + refText); } myImplementsList = null; } @Override public void removeFromImplementsList(String refText) { String[] refs = getImplementsList().getReferenceTexts(); LOG.assertTrue(ArrayUtil.contains(refText, refs)); XmlAttribute attribute = getParent().getAttribute(IMPLEMENTS_ATTRIBUTE); if (refs.length == 1) { attribute.delete(); } else { String[] newRefs = ArrayUtil.remove(refs, refText); attribute.setValue(StringUtil.join(newRefs, ", ")); } myImplementsList = null; } public void setBaseComponent(String qName, String prefix, String namespace) { setBaseComponent(getParent(), qName, prefix, namespace); myExtendsList = null; } public static void setBaseComponent(XmlTag rootTag, String qName, String prefix, String namespace) { Map<String, String> existingPrefix2Namespace = rootTag.getLocalNamespaceDeclarations(); for (Map.Entry<String, String> entry : existingPrefix2Namespace.entrySet()) { if (entry.getValue().equals(namespace)) { rootTag.setName(entry.getKey() + ":" + StringUtil.getShortName(qName)); return; } } int postfix = 1; String uniquePrefix = prefix; while (existingPrefix2Namespace.containsKey(uniquePrefix)) { uniquePrefix = prefix + postfix++; } rootTag.setName(uniquePrefix + ":" + StringUtil.getShortName(qName)); rootTag.setAttribute("xmlns:" + uniquePrefix, namespace); } @Override protected void processImplicitMembers(PsiScopeProcessor processor) { MxmlResolveUtil.processImplicitFields(processor, getContainingFile(), skinComponentPredefinedFields, ourSkinComponentPredefinedFieldsKey); } @Override public boolean processOuterDeclarations(PsiScopeProcessor processor) { return MxmlResolveUtil.processImplicitFields(processor, getContainingFile(), inlineComponentRenderPredefinedVars, ourInlineComponentRenderPredefinedVarsKey); } public static boolean isFxLibraryTag(final XmlTag tag) { return tag != null && FlexPredefinedTagNames.LIBRARY.equals(tag.getLocalName()) && JavaScriptSupportLoader.MXML_URI3.equals(tag.getNamespace()); } @Override protected boolean canResolveTo(XmlElement element) { final XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class, false); if (tag == null) { return true; } // Component tag itself can be referenced by id if (!isComponentTag(tag) && getContainingComponent(element) != this) { return false; } return canBeReferencedById(tag); } @Override protected boolean resolveViaImplicitImports(ResolveProcessor processor) { return MxmlImplicitImports.resolveTypeNameUsingImplicitImports(processor, this); } public static boolean canBeReferencedById(XmlTag tag) { return !isInsideTagThatAllowsAnyXmlContent(tag) && !isOperationTag(tag); } public static boolean isTagOrInsideTagThatAllowsAnyXmlContent(final XmlTag tag) { return isTagThatAllowsAnyXmlContent(tag) || isInsideTagThatAllowsAnyXmlContent(tag); } public static boolean isInsideTagThatAllowsAnyXmlContent(final XmlTag tag) { return isInsideTag(tag, parentTag -> isTagThatAllowsAnyXmlContent(parentTag)); } public static boolean isTagThatAllowsAnyXmlContent(final XmlTag tag) { return tag != null && (JavaScriptSupportLoader.isLanguageNamespace(tag.getNamespace()) && ArrayUtil.contains(tag.getLocalName(), TAGS_THAT_ALLOW_ANY_XML_CONTENT) || isWebOrHttpServiceRequestTag(tag)); } private static boolean isOperationTag(final XmlTag tag) { return OPERATION_TAG_NAME.equals(tag.getLocalName()) && ArrayUtil.contains(tag.getNamespace(), REQUEST_TAG_POSSIBLE_NAMESPACES); } private static boolean isWebOrHttpServiceRequestTag(final XmlTag tag) { if (ArrayUtil.contains(tag.getNamespace(), REQUEST_TAG_POSSIBLE_NAMESPACES) && REQUEST_TAG_NAME.equals(tag.getLocalName())) { final XmlTag parentTag = tag.getParentTag(); if (parentTag != null && ArrayUtil.contains(parentTag.getNamespace(), REQUEST_TAG_POSSIBLE_NAMESPACES)) { if (HTTP_SERVICE_TAG_NAME.equals(parentTag.getLocalName())) { return true; } else if (OPERATION_TAG_NAME.equals(parentTag.getLocalName())) { final XmlTag parentParentTag = parentTag.getParentTag(); if (parentParentTag != null && ArrayUtil.contains(parentParentTag.getNamespace(), REQUEST_TAG_POSSIBLE_NAMESPACES) && WEB_SERVICE_TAG_NAME.equals(parentParentTag.getLocalName())) { return true; } } } } return false; } public JSFile createScriptTag() throws IncorrectOperationException { final XmlTag rootTag = getParent(); if (rootTag != null) { String ns = getLanguageNamespace(rootTag); final String emptyText = "\n"; for (XmlTag tag : rootTag.getSubTags()) { if (FlexPredefinedTagNames.SCRIPT.equals(tag.getLocalName()) && ns.equals(tag.getNamespace())) { tag.getValue().setText(emptyText); return findFirstScriptTag(); } } rootTag.add(rootTag.createChildTag(FlexPredefinedTagNames.SCRIPT, ns, CDATA_START + emptyText + CDATA_END, false)); return findFirstScriptTag(); } return null; } @Override protected JSLanguageDialect getClassLanguage() { return JavaScriptSupportLoader.ECMA_SCRIPT_L4; } }