package com.intellij.flex.uiDesigner.libraries; import com.intellij.ProjectTopics; import com.intellij.diagnostic.AttachmentFactory; import com.intellij.flex.uiDesigner.*; import com.intellij.flex.uiDesigner.io.StringRegistry; import com.intellij.flex.uiDesigner.libraries.FlexLibrarySet.ContainsCondition; import com.intellij.flex.uiDesigner.libraries.LibrarySorter.SortResult; import com.intellij.flex.uiDesigner.mxml.ProjectComponentReferenceCounter; import com.intellij.lang.javascript.flex.FlexUtils; import com.intellij.lang.properties.psi.PropertiesFile; import com.intellij.notification.Notification; import com.intellij.notification.NotificationType; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.diagnostic.Attachment; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ModuleRootEvent; import com.intellij.openapi.roots.ModuleRootListener; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiManager; import com.intellij.util.ArrayUtil; import com.intellij.util.ExceptionUtil; import com.intellij.util.StringBuilderSpinAllocator; import gnu.trove.THashMap; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.io.InfoMap; import org.jetbrains.io.RetainCondition; import java.io.File; import java.io.IOException; import java.nio.channels.ClosedByInterruptException; import java.util.*; @SuppressWarnings("MethodMayBeStatic") public class LibraryManager implements Disposable { private static final String SWF_EXTENSION = ".swf"; static final String PROPERTIES_EXTENSION = ".properties"; private final File appDir; private final InfoMap<VirtualFile, Library> libraries = new InfoMap<>(); private final THashMap<String, LibrarySet> librarySets = new THashMap<>(); private final Map<VirtualFile, Set<CharSequence>> globalDefinitionsMap = new THashMap<>(); private LibrariesData data; public LibraryManager() { appDir = DesignerApplicationManager.APP_DIR; } @Override public void dispose() { if (data != null) { data.close(); } } public void unregister(final int[] ids) { librarySets.retainEntries(new RetainCondition<>(ids)); } public static LibraryManager getInstance() { return DesignerApplicationManager.getService(LibraryManager.class); } public boolean isRegistered(@NotNull Library library) { return libraries.contains(library); } public int add(@NotNull Library library) { return libraries.add(library); } public void init() throws IOException { LogMessageUtil.LOG.assertTrue(data == null); data = new LibrariesData(appDir); } @NotNull public ProjectComponentReferenceCounter registerModule(@NotNull final Module module, ProblemsHolder problemsHolder) throws InitException { return registerModule(module, problemsHolder, true); } @NotNull public ProjectComponentReferenceCounter registerModule(@NotNull final Module module, ProblemsHolder problemsHolder, boolean collectLocalStyleHolders) throws InitException { final Project project = module.getProject(); final StringRegistry.StringWriter stringWriter = new StringRegistry.StringWriter(16384); stringWriter.startChange(); final AssetCounter assetCounter = new AssetCounter(); final LibraryCollector libraryCollector = new LibraryCollector(this, new LibraryStyleInfoCollector(assetCounter, problemsHolder, module, stringWriter), module); final Client client; try { final AccessToken token = ReadAction.start(); try { libraryCollector.collect(module); } finally { token.finish(); } client = Client.getInstance(); if (stringWriter.hasChanges()) { client.updateStringRegistry(stringWriter); } else { stringWriter.commit(); } } catch (Throwable e) { stringWriter.rollback(); throw new InitException(e, "error.collect.libraries"); } assert !libraryCollector.sdkLibraries.isEmpty(); final FlexLibrarySet flexLibrarySet = getOrCreateFlexLibrarySet(libraryCollector, assetCounter); final InfoMap<Project, ProjectInfo> registeredProjects = client.getRegisteredProjects(); ProjectInfo info = registeredProjects.getNullableInfo(project); if (info == null) { info = new ProjectInfo(project); registeredProjects.add(info); client.openProject(project); DesignerApplicationManager.getInstance().projectRegistered(project); } LibrarySet librarySet; if (libraryCollector.externalLibraries.isEmpty()) { librarySet = null; } else { final String key = createKey(libraryCollector.externalLibraries, false); librarySet = librarySets.get(key); if (librarySet == null) { final SortResult sortResult = sortLibraries(new LibrarySorter(), libraryCollector, flexLibrarySet.contains, key, false); librarySet = new LibrarySet(sortResult.id, flexLibrarySet, sortResult.libraries); registerLibrarySet(key, librarySet); } } final ModuleInfo moduleInfo = new ModuleInfo(module, librarySet == null ? flexLibrarySet : librarySet, ModuleInfoUtil.isApp(module)); final ProjectComponentReferenceCounter projectComponentReferenceCounter = new ProjectComponentReferenceCounter(); if (collectLocalStyleHolders) { // client.registerModule finalize it stringWriter.startChange(); try { moduleInfo.setLocalStyleHolders(ModuleInfoUtil.collectLocalStyle(moduleInfo, libraryCollector.getFlexSdkVersion(), stringWriter, problemsHolder, projectComponentReferenceCounter, assetCounter)); } catch (Throwable e) { stringWriter.rollback(); throw new InitException(e, "error.collect.local.style.holders"); } } client.registerModule(project, moduleInfo, stringWriter); client.fillAssetClassPoolIfNeed(flexLibrarySet); module.getMessageBus().connect(moduleInfo).subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { @Override public void rootsChanged(ModuleRootEvent event) { new Notification(FlashUIDesignerBundle.message("plugin.name"), FlashUIDesignerBundle.message("plugin.name"), "Please reopen your project to update on library changes.", NotificationType.WARNING).notify(project); } }); return projectComponentReferenceCounter; } private FlexLibrarySet getOrCreateFlexLibrarySet(LibraryCollector libraryCollector, AssetCounter assetCounter) throws InitException { final String key = createKey(libraryCollector.sdkLibraries, true); FlexLibrarySet flexLibrarySet = (FlexLibrarySet)librarySets.get(key); if (flexLibrarySet == null) { final Set<CharSequence> globalDefinitions = getGlobalDefinitions(libraryCollector.getGlobalLibrary()); final Condition<String> globalContains = name -> globalDefinitions.contains(name); final SortResult sortResult = sortLibraries(new LibrarySorter(new FlexDefinitionProcessor(libraryCollector.getFlexSdkVersion()), new FlexDefinitionMapProcessor(libraryCollector.getFlexSdkVersion(), globalContains)), libraryCollector, globalContains, key, true); flexLibrarySet = new FlexLibrarySet(sortResult, null, new ContainsCondition(globalDefinitions, sortResult.definitionMap), assetCounter, libraryCollector.getFlexSdkVersion()); registerLibrarySet(key, flexLibrarySet); } return flexLibrarySet; } private void registerLibrarySet(String key, LibrarySet librarySet) { Client.getInstance().registerLibrarySet(librarySet); librarySets.put(key, librarySet); } private Set<CharSequence> getGlobalDefinitions(VirtualFile file) throws InitException { Set<CharSequence> globalDefinitions = globalDefinitionsMap.get(file); if (globalDefinitions == null) { try { globalDefinitions = LibraryUtil.getDefinitions(file); } catch (IOException e) { throw new InitException(e, "error.sort.libraries"); } } globalDefinitionsMap.put(file, globalDefinitions); return globalDefinitions; } private String createKey(List<Library> libraries, boolean isSdk) { // we don't depend on library order final VirtualFile[] files = new VirtualFile[libraries.size()]; for (int i = 0, librariesSize = libraries.size(); i < librariesSize; i++) { files[i] = libraries.get(i).getFile(); } Arrays.sort(files, (o1, o2) -> StringUtil.compare(o1.getPath(), o2.getPath(), false)); final StringBuilder stringBuilder = StringBuilderSpinAllocator.alloc(); try { if (isSdk) { stringBuilder.append('_'); } for (VirtualFile file : files) { stringBuilder.append(file.getTimeStamp()).append(file.getPath()).append(':'); } return stringBuilder.toString(); } finally { StringBuilderSpinAllocator.dispose(stringBuilder); } } @NotNull private SortResult sortLibraries(LibrarySorter sorter, LibraryCollector collector, Condition<String> isExternal, String key, boolean isSdk) throws InitException { final List<Library> libraries = isSdk ? collector.sdkLibraries : collector.externalLibraries; try { final int id = data.librarySets.enumerate(key); SortResult result = data.librarySets.get(key); if (result == null) { result = sorter.sort(libraries, new File(appDir, LibrariesData.NAME_PREFIX + Integer.toString(id) + SWF_EXTENSION), isExternal, isSdk); data.librarySets.put(key, result); } else { final String[] libraryPaths = result.libraryPaths; final List<Library> filteredLibraries = new ArrayList<>(libraryPaths.length); for (Library library : libraries) { if (ArrayUtil.indexOf(libraryPaths, library.getFile().getPath()) != -1) { filteredLibraries.add(library); } } result = new SortResult(result.definitionMap, filteredLibraries); } result.id = id; return result; } catch (ClosedByInterruptException e) { throw new InitException(e); } catch (Throwable e) { String technicalMessage = "Flex SDK " + collector.getFlexSdkVersion(); final Attachment[] attachments = new Attachment[libraries.size()]; try { for (int i = 0, librariesSize = libraries.size(); i < librariesSize; i++) { Library library = libraries.get(i); technicalMessage += " " + library.getFile().getPath(); attachments[i] = AttachmentFactory.createAttachment(library.getFile()); } } catch (Throwable innerE) { technicalMessage += " Cannot collect library catalog files due to " + ExceptionUtil.getThrowableText(innerE); } throw new InitException(e, "error.sort.libraries", attachments, technicalMessage); } } // created library will be register later, in Client.registerLibrarySet, so, we expect that createOriginalLibrary never called with duplicated virtualFile, i.e. // sdkLibraries doesn't contain duplicated virtualFiles and externalLibraries too (http://youtrack.jetbrains.net/issue/AS-200) Library createOriginalLibrary(@NotNull final VirtualFile jarFile, @NotNull final LibraryStyleInfoCollector processor) { Library info = libraries.getNullableInfo(jarFile); final boolean isNew = info == null; if (isNew) { info = new Library(jarFile); } processor.process(info, isNew); return info; } @Nullable public Pair<PropertiesFile, Integer> getResourceBundleFile(String locale, String bundleName, ModuleInfo moduleInfo) { final Project project = moduleInfo.getModule().getProject(); LibrarySet librarySet = moduleInfo.getLibrarySet(); do { PropertiesFile propertiesFile; for (Library library : librarySet.getLibraries()) { if (library.hasResourceBundles() && (propertiesFile = getResourceBundleFile(locale, bundleName, library, project)) != null) { return new Pair<>(propertiesFile, librarySet.getId()); } } } while ((librarySet = librarySet.getParent()) != null); // AS-273 final Sdk sdk = FlexUtils.getSdkForActiveBC(moduleInfo.getModule()); VirtualFile dir = sdk == null ? null : sdk.getHomeDirectory(); if (dir != null) { dir = dir.findFileByRelativePath("frameworks/projects"); } if (dir != null) { for (String libName : new String[]{"framework", "spark", "mx", "airframework", "rpc", "advancedgrids", "charts", "textLayout"}) { VirtualFile file = dir.findFileByRelativePath(libName + "/bundles/" + locale + "/" + bundleName + PROPERTIES_EXTENSION); if (file != null) { return new Pair<>(virtualFileToProperties(project, file), moduleInfo.getFlexLibrarySet().getId()); } } } return null; } @Nullable private static PropertiesFile getResourceBundleFile(String locale, String bundleName, Library library, Project project) { final THashSet<String> bundles = library.resourceBundles.get(locale); if (!bundles.contains(bundleName)) { return null; } //noinspection ConstantConditions VirtualFile file = library.getFile().findChild("locale").findChild(locale).findChild(bundleName + PROPERTIES_EXTENSION); //noinspection ConstantConditions return virtualFileToProperties(project, file); } private static PropertiesFile virtualFileToProperties(Project project, VirtualFile file) { final AccessToken token = ReadAction.start(); try { return (PropertiesFile)PsiManager.getInstance(project).findFile(file); } finally { token.finish(); } } }