package com.intellij.flex.uiDesigner.mxml;
import com.intellij.flex.uiDesigner.DocumentFactoryManager;
import com.intellij.flex.uiDesigner.InvalidPropertyException;
import com.intellij.javascript.flex.mxml.schema.ClassBackedElementDescriptor;
import com.intellij.lang.javascript.JavaScriptSupportLoader;
import com.intellij.lang.javascript.flex.AnnotationBackedDescriptor;
import com.intellij.lang.javascript.psi.JSCommonTypeNames;
import com.intellij.lang.javascript.psi.ecmal4.JSClass;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Trinity;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.xml.*;
import com.intellij.util.concurrency.Semaphore;
import gnu.trove.THashSet;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
import java.util.regex.Pattern;
import static com.intellij.flex.uiDesigner.mxml.MxmlWriter.LOG;
public final class MxmlUtil {
private static final Pattern FLEX_SDK_ABSTRACT_CLASSES = Pattern.compile("^(mx|spark)\\.(.*)?Base$");
private static final Trinity<Integer, String, Condition<AnnotationBackedDescriptor>> NON_PROJECT_CLASS = new Trinity<>(-1, null, null);
static final String UNKNOWN_COMPONENT_CLASS_NAME = "com.intellij.flex.uiDesigner.flex.UnknownComponent";
static final String UNKNOWN_ITEM_RENDERER_CLASS_NAME = "com.intellij.flex.uiDesigner.flex.UnknownItemRenderer";
public static Document getDocumentAndWaitIfNotCommitted(PsiFile psiFile) {
Application application = ApplicationManager.getApplication();
LOG.assertTrue(application.isUnitTestMode() || !application.isReadAccessAllowed());
PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(psiFile.getProject());
Document document = psiDocumentManager.getDocument(psiFile);
LOG.assertTrue(document != null);
if (!psiDocumentManager.isCommitted(document)) {
final Semaphore semaphore = new Semaphore();
semaphore.down();
psiDocumentManager.performForCommittedDocument(document, () -> semaphore.up());
semaphore.waitFor();
}
return document;
}
// about id http://opensource.adobe.com/wiki/display/flexsdk/id+property+in+MXML+2009
public static boolean isIdLanguageAttribute(XmlAttribute attribute, AnnotationBackedDescriptor descriptor) {
if (!descriptor.hasIdType()) {
return false;
}
String ns = attribute.getNamespace();
return ns.isEmpty() || ns.equals(JavaScriptSupportLoader.MXML_URI3);
}
static boolean isComponentLanguageTag(XmlTag tag) {
return tag.getNamespace().equals(JavaScriptSupportLoader.MXML_URI3) && tag.getLocalName().equals("Component");
}
static boolean containsOnlyWhitespace(XmlTagChild child) {
PsiElement firstChild = child.getFirstChild();
return firstChild == child.getLastChild() && (firstChild == null || firstChild instanceof PsiWhiteSpace);
}
@Nullable
public static PsiLanguageInjectionHost getInjectedHost(XmlTag tag) {
// support <tag>{v}...</tag> or <tag>__PsiWhiteSpace__{v}...</tag>
// <tag><span>text</span> {v}...</tag> is not supported
for (XmlTagChild child : tag.getValue().getChildren()) {
if (child instanceof XmlText) {
//noinspection CastConflictsWithInstanceof
return (PsiLanguageInjectionHost)child;
}
else if (!(child instanceof PsiWhiteSpace)) {
return null;
}
}
return null;
}
static boolean isAbstract(ClassBackedElementDescriptor classBackedDescriptor) {
return FLEX_SDK_ABSTRACT_CLASSES.matcher(classBackedDescriptor.getQualifiedName()).matches();
}
static Trinity<Integer, String, Condition<AnnotationBackedDescriptor>> computeEffectiveClass(final PsiElement element,
final PsiElement declaration,
final ProjectComponentReferenceCounter projectComponentReferenceCounter,
final boolean computePropertyFilter)
throws InvalidPropertyException {
PsiFile psiFile = declaration.getContainingFile();
VirtualFile virtualFile = psiFile.getVirtualFile();
ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(psiFile.getProject()).getFileIndex();
LOG.assertTrue(virtualFile != null);
if (!projectFileIndex.isInSourceContent(virtualFile)) {
return NON_PROJECT_CLASS;
}
if (psiFile instanceof XmlFile) {
return new Trinity<>(
DocumentFactoryManager.getInstance().getId(virtualFile, (XmlFile)psiFile, projectComponentReferenceCounter), null, null);
}
final Set<PsiFile> filteredFiles;
if (computePropertyFilter) {
filteredFiles = new THashSet<>();
filteredFiles.add(psiFile);
}
else {
filteredFiles = null;
}
final JSClass aClass = (JSClass)declaration;
JSClass[] classes;
while ((classes = aClass.getSuperClasses()).length > 0) {
JSClass parentClass = classes[0];
PsiFile containingFile = parentClass.getContainingFile();
//noinspection ConstantConditions
if (!projectFileIndex.isInSourceContent(containingFile.getVirtualFile())) {
return new Trinity<>(-1, parentClass.getQualifiedName(),
computePropertyFilter
? new CustomComponentPropertyFilter(filteredFiles)
: null);
}
else if (computePropertyFilter) {
filteredFiles.add(containingFile);
}
}
// well, it must be at least mx.core.UIComponent or spark.primitives.supportClasses.GraphicElement
throw new InvalidPropertyException(element, "unresolved.class", aClass.getQualifiedName());
}
public static boolean isObjectLanguageTag(XmlTag tag) {
return tag.getNamespace().equals(JavaScriptSupportLoader.MXML_URI3) &&
tag.getLocalName().equals(JSCommonTypeNames.OBJECT_CLASS_NAME);
}
private static class CustomComponentPropertyFilter implements Condition<AnnotationBackedDescriptor> {
private final Set<PsiFile> filteredFiles;
public CustomComponentPropertyFilter(Set<PsiFile> filteredFiles) {
this.filteredFiles = filteredFiles;
}
@Override
public boolean value(AnnotationBackedDescriptor descriptor) {
return !filteredFiles.contains(descriptor.getDeclaration().getContainingFile());
}
}
static boolean isPropertyOfSparkDataGroup(AnnotationBackedDescriptor descriptor) {
PsiElement parent = descriptor.getDeclaration().getParent();
if (parent instanceof JSClass) {
String name = ((JSClass)parent).getQualifiedName();
return name.equals("spark.components.DataGroup") || name.equals("spark.components.SkinnableDataContainer");
}
return false;
}
}