package com.intellij.javascript.flex.mxml.schema; import com.intellij.codeInsight.completion.CompletionInitializationContext; import com.intellij.codeInsight.daemon.Validator; import com.intellij.javascript.flex.mxml.MxmlJSClass; import com.intellij.lang.javascript.JavaScriptSupportLoader; import com.intellij.lang.javascript.psi.ecmal4.JSAttributeList; import com.intellij.lang.javascript.psi.ecmal4.JSClass; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.util.Key; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.xml.XmlDocument; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.util.ArrayUtil; import com.intellij.xml.XmlElementDescriptor; import com.intellij.xml.XmlNSDescriptor; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; /** * @author Maxim.Mossienko */ public class FlexMxmlNSDescriptor implements XmlNSDescriptor, Validator<XmlDocument> { private XmlFile myFile; private String namespace; private Module module; public static final Key<String> NS_KEY = Key.create("ns.key"); public static final Key<Module> MODULE_KEY = Key.create("module.key"); private static boolean reportedAboutStackOverflow; // TODO seems that need to fix ECMAScript.js2 and E4X.js2 // classes Boolean, Date, Number, String and XML are final in playerglobal.swc, but not final in our predefined files. // if changed to final there then we can remove these classes from this list. private static final String[] ILLEGAL_LANGUAGE_ROOT_TAGS = {"Array", "Boolean", "Component", "Class", "Date", "DesignLayer", "Function", "Number", "String", "XML", "int", "uint"}; @Nullable public XmlElementDescriptor getElementDescriptor(@NotNull final XmlTag tag) { if (MxmlJSClass.isInsideTagThatAllowsAnyXmlContent(tag)) { return new AnyXmlElementWithAnyChildrenDescriptor(); } final String namespace = tag.getNamespace(); final CodeContext context = CodeContext.getContext(namespace, module); final String localName = tag.getLocalName(); XmlElementDescriptor descriptor = context.getElementDescriptor(localName, tag); if (descriptor == null) { final XmlTag parentTag = tag.getParentTag(); if (parentTag != null && namespace.equals(parentTag.getNamespace())) { final XmlElementDescriptor parentDescriptor = parentTag.getDescriptor(); if (parentDescriptor != null && // FIXME: prevent stackoverflow due to parent delegation due to different context namespace (!(parentDescriptor instanceof ClassBackedElementDescriptor) || ClassBackedElementDescriptor.sameNs(namespace,(((ClassBackedElementDescriptor)parentDescriptor).context.namespace)) )) { descriptor = parentDescriptor.getElementDescriptor(tag, parentTag); } else if (parentDescriptor != null && !reportedAboutStackOverflow) { // TODO: remove diagnostic Logger.getInstance(getClass().getName()).error( new AssertionError("avoided SOE:\n"+tag.getContainingFile().getText()) ); reportedAboutStackOverflow = true; } } } else if (tag.getParent() instanceof XmlDocument && !isLegalRootElementDescriptor(descriptor)) { return null; } if (descriptor == null && JavaScriptSupportLoader.MXML_URI3.equals(tag.getNamespace())) { return FxDefinitionBackedDescriptor.getFxDefinitionBackedDescriptor(module, tag); } return descriptor; } private static boolean isLegalRootElementDescriptor(final @NotNull XmlElementDescriptor _descriptor) { if (_descriptor instanceof ClassBackedElementDescriptor) { final ClassBackedElementDescriptor descriptor = (ClassBackedElementDescriptor)_descriptor; final PsiElement element = descriptor.getDeclaration(); if (element instanceof JSClass) { final JSAttributeList attributeList = ((JSClass)element).getAttributeList(); if (attributeList != null && attributeList.hasModifier(JSAttributeList.ModifierType.FINAL)){ return false; } } if (JavaScriptSupportLoader.isLanguageNamespace(descriptor.context.namespace)){ final String tagName = _descriptor.getName(); return !descriptor.isPredefined() && !ArrayUtil.contains(tagName, ILLEGAL_LANGUAGE_ROOT_TAGS); } } return true; } @NotNull public XmlElementDescriptor[] getRootElementsDescriptors(@Nullable final XmlDocument document) { XmlElementDescriptor[] elementDescriptors = CodeContext.getContext(namespace, module).getDescriptorsWithAllowedDeclaration(); ArrayList<XmlElementDescriptor> results = new ArrayList<>(elementDescriptors.length); final XmlTag rootTag = document == null ? null : document.getRootTag(); // sorry for this hacky way to determine if this is root tag name completion or not final boolean isRootTagCompletion = rootTag != null && rootTag.getName().endsWith(CompletionInitializationContext.DUMMY_IDENTIFIER_TRIMMED); for (XmlElementDescriptor elementDescriptor : elementDescriptors) { if (isRootTagCompletion) { if (!isLegalRootElementDescriptor(elementDescriptor)) { continue; } if (elementDescriptor instanceof MxmlBackedElementDescriptor) { final PsiElement declaration = elementDescriptor.getDeclaration(); final PsiFile containingFile = document.getContainingFile(); if (declaration != null && containingFile != null && declaration.equals(containingFile.getOriginalFile())) { // do not suggest root tag referencing to this mxml file itself continue; } } } String name = elementDescriptor.getName(); if (name.length() > 0 /*&& Character.isUpperCase(name.charAt(0))*/) results.add(elementDescriptor); } return results.toArray(new XmlElementDescriptor[results.size()]); } @Nullable public XmlFile getDescriptorFile() { return myFile; } public PsiElement getDeclaration() { return myFile; } @NonNls public String getName(final PsiElement context) { return null; } @NonNls public String getName() { return null; } public void init(final PsiElement element) { XmlDocument document = (XmlDocument) element; myFile = ((XmlFile)document.getContainingFile()); namespace = myFile.getUserData(NS_KEY); module = myFile.getUserData(MODULE_KEY); assert namespace != null; CodeContextHolder.getInstance(module.getProject()).clearCodeContext(namespace, module); } @NotNull public Object[] getDependences() { return CodeContext.getContext(namespace, module).getDependencies(); } public void validate(@NotNull final XmlDocument context, @NotNull final ValidationHost host) {} public boolean hasElementDescriptorWithName(String name, String className) { final CodeContext context = CodeContext.getContext(namespace, module); XmlElementDescriptor descriptor = context.getElementDescriptor(name, (XmlTag)null); if (descriptor instanceof ClassBackedElementDescriptor) { return ((ClassBackedElementDescriptor)descriptor).className.equals(className); } return false; } }