package com.intellij.flex.uiDesigner; import com.intellij.flex.uiDesigner.css.LocalCssWriter; import com.intellij.flex.uiDesigner.io.StringRegistry.StringWriter; import com.intellij.flex.uiDesigner.libraries.Library; import com.intellij.flex.uiDesigner.mxml.MxmlUtil; import com.intellij.flex.uiDesigner.mxml.ProjectComponentReferenceCounter; import com.intellij.javascript.flex.FlexPredefinedTagNames; import com.intellij.javascript.flex.mxml.FlexCommonTypeNames; import com.intellij.javascript.flex.resolve.ActionScriptClassResolver; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.lang.javascript.JavaScriptSupportLoader; import com.intellij.lang.javascript.flex.projectStructure.model.FlexBuildConfigurationManager; import com.intellij.lang.javascript.psi.ecmal4.JSClass; import com.intellij.lang.javascript.search.JSClassSearch; import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.css.StylesheetFile; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlAttributeValue; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.util.Processor; import com.intellij.util.concurrency.Semaphore; import gnu.trove.THashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; public final class ModuleInfoUtil { public static boolean isApp(Module module) { return FlexBuildConfigurationManager.getInstance(module).getActiveConfiguration().getNature().isApp(); } public static List<LocalStyleHolder> collectLocalStyle(final ModuleInfo moduleInfo, final String flexSdkVersion, final StringWriter stringWriter, final ProblemsHolder problemsHolder, ProjectComponentReferenceCounter projectComponentReferenceCounter, AssetCounter assetCounter) { Project project = moduleInfo.getModule().getProject(); DumbService dumbService = DumbService.getInstance(project); if (dumbService.isDumb()) { dumbService.waitForSmartMode(); } final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project); if (psiDocumentManager.hasUncommitedDocuments()) { final Semaphore semaphore = new Semaphore(); semaphore.down(); Application application = ApplicationManager.getApplication(); LogMessageUtil.LOG.assertTrue(!application.isReadAccessAllowed()); application.invokeLater(() -> psiDocumentManager.performWhenAllCommitted(() -> semaphore.up())); semaphore.waitFor(); } final AccessToken token = ReadAction.start(); try { if (moduleInfo.isApp()) { return collectApplicationLocalStyle(moduleInfo.getModule(), flexSdkVersion, problemsHolder, stringWriter, projectComponentReferenceCounter, assetCounter); } else { return collectLibraryLocalStyle(moduleInfo.getModule(), stringWriter, problemsHolder, projectComponentReferenceCounter, assetCounter); } } finally { token.finish(); } } @Nullable private static List<LocalStyleHolder> collectLibraryLocalStyle(Module module, StringWriter stringWriter, ProblemsHolder problemsHolder, ProjectComponentReferenceCounter unregisteredComponentReferences, AssetCounter assetCounter) { VirtualFile defaultsCss = null; for (VirtualFile sourceRoot : ModuleRootManager.getInstance(module).getSourceRoots(false)) { if ((defaultsCss = sourceRoot.findChild(Library.DEFAULTS_CSS)) != null) { break; } } if (defaultsCss != null) { byte[] data = new LocalCssWriter(stringWriter, problemsHolder, unregisteredComponentReferences, assetCounter).write(defaultsCss, module); if (data != null) { return Collections.singletonList(new LocalStyleHolder(defaultsCss, data)); } } return null; } @Nullable private static List<LocalStyleHolder> collectApplicationLocalStyle(final Module module, String flexSdkVersion, final ProblemsHolder problemsHolder, StringWriter stringWriter, ProjectComponentReferenceCounter projectComponentReferenceCounter, final AssetCounter assetCounter) { GlobalSearchScope moduleWithDependenciesAndLibrariesScope = module.getModuleWithDependenciesAndLibrariesScope(false); final List<JSClass> holders = new ArrayList<>(2); if (flexSdkVersion.charAt(0) > '3') { JSClass clazz = ((JSClass)ActionScriptClassResolver .findClassByQNameStatic(FlexCommonTypeNames.SPARK_APPLICATION, moduleWithDependenciesAndLibrariesScope)); // it is not legal case, but user can use patched/modified Flex SDK if (clazz != null) { holders.add(clazz); } } JSClass mxApplicationClass = ((JSClass)ActionScriptClassResolver .findClassByQNameStatic(FlexCommonTypeNames.MX_APPLICATION, moduleWithDependenciesAndLibrariesScope)); // if null, mx.swc is not added to module dependencies if (mxApplicationClass != null) { holders.add(mxApplicationClass); } if (holders.isEmpty()) { return null; } final StyleTagWriter styleTagWriter = new StyleTagWriter(new LocalCssWriter(stringWriter, problemsHolder, projectComponentReferenceCounter, assetCounter)); final List<LocalStyleHolder> result = new ArrayList<>(); final Processor<JSClass> processor = jsClass -> { PsiFile psiFile = jsClass.getNavigationElement().getContainingFile(); if (!(psiFile instanceof XmlFile)) { return true; } XmlTag rootTag = ((XmlFile)psiFile).getRootTag(); if (rootTag == null) { return true; } final VirtualFile virtualFile = psiFile.getVirtualFile(); problemsHolder.setCurrentFile(virtualFile); try { // IDEA-73558 for (final XmlTag subTag : rootTag.getSubTags()) { if (subTag.getNamespace().equals(JavaScriptSupportLoader.MXML_URI3) && subTag.getLocalName().equals(FlexPredefinedTagNames.STYLE)) { try { LocalStyleHolder localStyleHolder = styleTagWriter.write(subTag, module, virtualFile); if (localStyleHolder != null) { result.add(localStyleHolder); } } catch (InvalidPropertyException e) { problemsHolder.add(e); } } } } finally { problemsHolder.setCurrentFile(null); } return true; }; final GlobalSearchScope moduleScope = module.getModuleScope(false); for (JSClass holder : holders) { JSClassSearch.searchClassInheritors(holder, true, moduleScope).forEach(processor); } return result; } private static class StyleTagWriter { private final LocalCssWriter cssWriter; private final THashMap<VirtualFile, ExternalLocalStyleHolder> externalLocalStyleHolders = new THashMap<>(); StyleTagWriter(LocalCssWriter localCssWriter) { cssWriter = localCssWriter; } @Nullable public LocalStyleHolder write(@NotNull XmlTag tag, @NotNull Module module, @NotNull VirtualFile userVirtualFile) throws InvalidPropertyException { XmlAttribute source = tag.getAttribute("source"); if (source == null) { PsiElement host = MxmlUtil.getInjectedHost(tag); if (host == null) { return null; } MyInjectedPsiVisitor visitor = new MyInjectedPsiVisitor(host); InjectedLanguageUtil.enumerate(host, visitor); StylesheetFile stylesheetFile = visitor.getStylesheetFile(); byte[] data = stylesheetFile == null ? null : cssWriter.write(stylesheetFile, module); return data == null ? null : new LocalStyleHolder(InjectedLanguageManager.getInstance(stylesheetFile.getProject()).getTopLevelFile(stylesheetFile).getVirtualFile(), data); } else { XmlAttributeValue valueElement = source.getValueElement(); if (valueElement == null) { return null; } PsiFileSystemItem psiFile = InjectionUtil.getReferencedPsiFile(valueElement); if (!(psiFile instanceof StylesheetFile)) { throw new InvalidPropertyException(valueElement, "embed.source.is.not.css.file", psiFile.getName()); } StylesheetFile stylesheetFile = (StylesheetFile)psiFile; VirtualFile virtualFile = stylesheetFile.getVirtualFile(); ExternalLocalStyleHolder existingLocalStyleHolder = externalLocalStyleHolders.get(virtualFile); if (existingLocalStyleHolder == null) { byte[] data = cssWriter.write(stylesheetFile, module); if (data == null) { return null; } ExternalLocalStyleHolder localStyleHolder = new ExternalLocalStyleHolder(virtualFile, data, userVirtualFile); externalLocalStyleHolders.put(virtualFile, localStyleHolder); return localStyleHolder; } else { existingLocalStyleHolder.addUser(userVirtualFile); return null; } } } private static class MyInjectedPsiVisitor implements PsiLanguageInjectionHost.InjectedPsiVisitor { private final PsiElement host; private boolean visited; private StylesheetFile stylesheetFile; public MyInjectedPsiVisitor(PsiElement host) { this.host = host; } @Nullable public StylesheetFile getStylesheetFile() { return stylesheetFile; } @Override public void visit(@NotNull PsiFile injectedPsi, @NotNull List<PsiLanguageInjectionHost.Shred> places) { assert !visited; visited = true; assert places.size() == 1; assert places.get(0).getHost() == host; stylesheetFile = (StylesheetFile)injectedPsi; } } } }