package com.intellij.lang.javascript.flex; import com.intellij.flex.FlexCommonUtils; import com.intellij.flex.model.bc.CompilerOptionInfo; import com.intellij.flex.model.bc.LinkageType; import com.intellij.flex.model.bc.TargetPlatform; import com.intellij.javascript.flex.FlexPredefinedTagNames; import com.intellij.javascript.flex.mxml.MxmlJSClass; import com.intellij.javascript.flex.mxml.MxmlJSClassProvider; import com.intellij.lang.javascript.flex.projectStructure.FlexOrderEnumerationHandler; import com.intellij.lang.javascript.flex.projectStructure.FlexProjectLevelCompilerOptionsHolder; import com.intellij.lang.javascript.flex.projectStructure.model.DependencyType; import com.intellij.lang.javascript.flex.projectStructure.model.FlexBuildConfiguration; import com.intellij.lang.javascript.flex.projectStructure.model.FlexBuildConfigurationManager; import com.intellij.lang.javascript.flex.sdk.FlexSdkUtils; import com.intellij.lang.javascript.psi.JSFile; import com.intellij.lang.javascript.psi.resolve.ActionScriptResolveUtil; import com.intellij.lang.javascript.psi.resolve.JSResolveUtil; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathMacros; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.module.impl.scopes.ModuleWithDependenciesScope; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.DependencyScope; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; 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.util.PathUtil; import com.intellij.util.Processor; import com.intellij.util.SystemProperties; import com.intellij.util.xml.NanoXmlUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.model.serialization.PathMacroUtil; import javax.swing.*; import java.io.*; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author Maxim.Mossienko */ public class FlexUtils { @NonNls private static final Pattern INFO_PLIST_EXECUTABLE_PATTERN = Pattern.compile("<key>CFBundleExecutable</key>(?:(?:\\s*)(?:<!--(?:.*)-->(?:\\s*))*)<string>(.*)</string>"); private FlexUtils() { } public static FileChooserDescriptor createFileChooserDescriptor(@Nullable final String... allowedExtensions) { return allowedExtensions == null ? new FileChooserDescriptor(true, false, true, true, false, false) : new FileChooserDescriptor(true, false, true, true, false, false) { @Override public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) { return super.isFileVisible(file, showHiddenFiles) && (file.isDirectory() || isAllowedExtension(file.getExtension())); } private boolean isAllowedExtension(final String extension) { for (String allowedExtension : allowedExtensions) { if (allowedExtension.equalsIgnoreCase(extension)) return true; } return false; } }; } public static void createSampleApp(final Project project, final VirtualFile sourceRoot, final String sampleFileName, final TargetPlatform platform, final boolean isFlex4) throws IOException { final String sampleClassName = FileUtil.getNameWithoutExtension(sampleFileName); final String extension = FileUtilRt.getExtension(sampleFileName); final String sampleTechnology = platform == TargetPlatform.Mobile ? "AIRMobile" : platform == TargetPlatform.Desktop ? "AIR" : "Flex"; String suffix = ""; if ("mxml".equalsIgnoreCase(extension)) { if (platform == TargetPlatform.Mobile) { suffix = "_ViewNavigator"; } else if (isFlex4) { suffix = "_Spark"; } } final String helloWorldTemplate = "HelloWorld_" + sampleTechnology + suffix + "." + extension + ".ft"; final InputStream stream = FlexUtils.class.getResourceAsStream(helloWorldTemplate); assert stream != null : helloWorldTemplate; final String sampleFileContent = FileUtil.loadTextAndClose(new InputStreamReader(stream)).replace("${class.name}", sampleClassName); final VirtualFile sampleApplicationFile = addFileWithContent(sampleFileName, sampleFileContent, sourceRoot); if (sampleApplicationFile != null) { final Runnable runnable = () -> FileEditorManager.getInstance(project).openFile(sampleApplicationFile, true); if (project.isInitialized()) { runnable.run(); } else { StartupManager.getInstance(project).registerPostStartupActivity(runnable); } } } public static VirtualFile addFileWithContent(@NonNls final String fileName, byte[] fileContent, final VirtualFile dir) throws IOException { VirtualFile file = dir.findChild(fileName); if (file == null) { file = dir.createChildData(FlexUtils.class, fileName); } else if (SystemInfo.isWindows) { file.rename(FlexUtils.class, fileName); // ensure the right case } file.setBinaryContent(fileContent); return file; } public static VirtualFile addFileWithContent(@NonNls final String fileName, @NonNls final String fileContent, final VirtualFile dir) throws IOException { VirtualFile data = dir.findChild(fileName); if (data == null) { data = dir.createChildData(FlexUtils.class, fileName); } else if (SystemInfo.isWindows) { data.rename(FlexUtils.class, fileName); // ensure the right case } VfsUtil.saveText(data, fileContent); return data; } @Nullable public static Sdk getSdkForActiveBC(@NotNull final Module module) { return ModuleType.get(module) instanceof FlexModuleType ? FlexBuildConfigurationManager.getInstance(module).getActiveConfiguration().getSdk() : null; } /** * Looks through input stream containing XML document and finds all entries of XML elements listed in <code>xmlElements</code>. * Content of these elements is put to result map. XML namespaces are not taken into consideration. * * @param xmlInputStream input stream with xml content to parse * @param xmlElements list of XML elements to look for. * Format is: <code>"<root_element><child_element><subelement_to_look_for>"</code>. * Listed XML elements SHOULD NOT contain subelements * @return map, keys are XML elements listed in <code>xmlElements</code>, * values are all entries of respective element (may be empty list) */ public static Map<String, List<String>> findXMLElements(@NotNull final InputStream xmlInputStream, final List<String> xmlElements) { final Map<String, List<String>> resultMap = new HashMap<>(); for (final String element : xmlElements) { resultMap.put(element, new ArrayList<>()); } NanoXmlUtil.parse(xmlInputStream, new NanoXmlUtil.IXMLBuilderAdapter() { private String currentElement = ""; private final StringBuilder currentElementContent = new StringBuilder(); @Override public void startElement(final String name, final String nsPrefix, final String nsURI, final String systemID, final int lineNr) throws Exception { currentElement += "<" + name + ">"; } @Override public void endElement(final String name, final String nsPrefix, final String nsURI) throws Exception { if (xmlElements.contains(currentElement)) { resultMap.get(currentElement).add(currentElementContent.toString()); currentElementContent.delete(0, currentElementContent.length()); } assert currentElement.endsWith("<" + name + ">"); currentElement = currentElement.substring(0, currentElement.length() - (name.length() + 2)); } @Override public void addPCData(final Reader reader, final String systemID, final int lineNr) throws Exception { if (xmlElements.contains(currentElement)) { char[] chars = new char[128]; int read; while ((read = reader.read(chars)) > 0) { currentElementContent.append(chars, 0, read); } } } }); return resultMap; } /** * Looks through input stream containing XML document and finds first entry of <code>xmlElement</code>. * XML namespaces are not taken into consideration. * * @param xmlInputStream input stream with xml content to parse * @param xmlElement XML element to look for. * Format is: <code>"<root_element><child_element><subelement_to_look_for>"</code>. * XML element SHOULD NOT contain subelements * @return first found value of <code>xmlElement</code> tag, or <code>null</code> if non found or any exception occurs. */ @Nullable public static String findXMLElement(@NotNull final InputStream xmlInputStream, final String xmlElement) { final Ref<String> result = new Ref<>(); NanoXmlUtil.parse(xmlInputStream, new NanoXmlUtil.IXMLBuilderAdapter() { private String currentElement = ""; private final StringBuilder xmlElementContent = new StringBuilder(); @Override public void startElement(final String name, final String nsPrefix, final String nsURI, final String systemID, final int lineNr) throws Exception { currentElement += "<" + name + ">"; } @Override public void endElement(final String name, final String nsPrefix, final String nsURI) throws Exception { if (xmlElement.equals(currentElement)) { result.set(xmlElementContent.toString()); stop(); } assert currentElement.endsWith("<" + name + ">"); currentElement = currentElement.substring(0, currentElement.length() - (name.length() + 2)); } @Override public void addPCData(final Reader reader, final String systemID, final int lineNr) throws Exception { if (xmlElement.equals(currentElement)) { char[] chars = new char[128]; int read; while ((read = reader.read(chars)) > 0) { xmlElementContent.append(chars, 0, read); } } } }); return result.get(); } @Nullable public static String getMacExecutable(@NotNull final String appFolderPath) { try { final String text = FileUtil.loadFile(new File(appFolderPath + "/Contents/Info.plist")); Matcher m = INFO_PLIST_EXECUTABLE_PATTERN.matcher(text); if (!m.find()) return null; return appFolderPath + "/Contents/MacOS/" + m.group(1); } catch (IOException ignored) { return null; } } /** * If the first item of ComboBox model is <code>null</code> or not instance of <code>clazz</code> then it will be removed from the model. */ public static void removeIncorrectItemFromComboBoxIfPresent(final JComboBox comboBox, final Class clazz) { final int oldSize = comboBox.getModel().getSize(); final Object firstElement = comboBox.getModel().getElementAt(0); if (oldSize > 0 && (firstElement == null || !clazz.isAssignableFrom(firstElement.getClass()))) { final Object selectedItem = comboBox.getSelectedItem(); final Object[] newObjects = new Object[oldSize - 1]; for (int i = 0; i < newObjects.length; i++) { newObjects[i] = comboBox.getModel().getElementAt(i + 1); } comboBox.setModel(new DefaultComboBoxModel(newObjects)); comboBox.setSelectedItem(selectedItem); } } public static String getFlexCompilerWorkDirPath(final Project project, @Nullable final Sdk flexSdk) { final VirtualFile baseDir = project.getBaseDir(); return FlexSdkUtils.isFlex2Sdk(flexSdk) || FlexSdkUtils.isFlex3_0Sdk(flexSdk) ? FlexCommonUtils.getTempFlexConfigsDirPath() //avoid problems with spaces in temp dir path (fcsh from Flex SDK 2 is not patched) : (baseDir == null ? "" : baseDir.getPath()); } public static String getPathToMainClassFile(final String mainClassFqn, final Module module) { if (StringUtil.isEmpty(mainClassFqn)) return ""; final String s = mainClassFqn.replace('.', '/'); final String[] classFileRelPaths = {s + ".mxml", s + ".as"}; for (final VirtualFile sourceRoot : ModuleRootManager.getInstance(module).getSourceRoots()) { for (final String classFileRelPath : classFileRelPaths) { final VirtualFile mainClassFile = VfsUtilCore.findRelativeFile(classFileRelPath, sourceRoot); if (mainClassFile != null) { return mainClassFile.getPath(); } } } return ""; } public static void removeFileLater(@NotNull final VirtualFile file) { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { try { if (file.exists()) { file.delete(this); } } catch (IOException ignored) {/*ignore*/} } }); } }); } private static void processMxmlTags(final XmlTag rootTag, final JSResolveUtil.JSInjectedFilesVisitor injectedFilesVisitor, Processor<XmlTag> processor) { String namespace = findMxmlNamespace(rootTag); XmlBackedJSClassImpl.InjectedScriptsVisitor scriptsVisitor = new XmlBackedJSClassImpl.InjectedScriptsVisitor(rootTag, MxmlJSClassProvider.getInstance(), false, false, injectedFilesVisitor, processor, true); scriptsVisitor.go(); for (XmlTag s : rootTag.findSubTags(FlexPredefinedTagNames.METADATA, namespace)) { processor.process(s); } } private static String findMxmlNamespace(XmlTag rootTag) { String namespace = ""; for(String candidateNs: MxmlJSClass.MXML_URIS) { if (rootTag.getPrefixByNamespace(candidateNs) != null) { namespace = candidateNs; } if (namespace.length() != 0) break; } return namespace; } public static void processMxmlTags(final XmlTag rootTag, boolean isPhysical, final JSResolveUtil.JSInjectedFilesVisitor injectedFilesVisitor) { processMxmlTags(rootTag, injectedFilesVisitor, new XmlBackedJSClassImpl.InjectedScriptsVisitor.InjectingProcessor(injectedFilesVisitor, rootTag, isPhysical)); } public static void processMetaAttributesForClass(@NotNull PsiElement jsClass, @NotNull final ActionScriptResolveUtil.MetaDataProcessor processor) { ActionScriptResolveUtil.processMetaAttributesForClass(jsClass, processor); if (jsClass instanceof XmlBackedJSClassImpl) { PsiElement parent = jsClass.getParent(); if (parent != null) { PsiFile file = parent.getContainingFile(); if (file instanceof XmlFile) { XmlDocument document = ((XmlFile)file).getDocument(); if (document != null) { XmlTag rootTag = document.getRootTag(); if (rootTag != null) { JSResolveUtil.JSInjectedFilesVisitor visitor = new JSResolveUtil.JSInjectedFilesVisitor() { @Override protected void process(JSFile file) { if (file != null) { ActionScriptResolveUtil.processMetaAttributesForClass(file, processor); } } }; processMxmlTags(rootTag, true, visitor); } } } } } } public static String replacePathMacros(@NotNull final String text, @NotNull final Module module, final String sdkRootPath) { final StringBuilder builder = new StringBuilder(text); int startIndex; int endIndex = 0; while ((startIndex = builder.indexOf("${", endIndex)) >= 0) { endIndex = builder.indexOf("}", startIndex); if (endIndex > startIndex) { final String macroName = builder.substring(startIndex + 2, endIndex); final String macroValue; if (PathMacroUtil.MODULE_DIR_MACRO_NAME.equals(macroName)) { macroValue = ModuleUtilCore.getModuleDirPath(module); } else if (PathMacroUtil.PROJECT_DIR_MACRO_NAME.equals(macroName)) { macroValue = module.getProject().getBasePath(); } else if (PathMacroUtil.USER_HOME_NAME.equals(macroName)) { macroValue = StringUtil.trimEnd((StringUtil.trimEnd(SystemProperties.getUserHome(), "/")), "\\"); } else if (CompilerOptionInfo.FLEX_SDK_MACRO_NAME.equals(macroName)) { macroValue = sdkRootPath; } else { macroValue = PathMacros.getInstance().getValue(macroName); } if (macroValue != null && !StringUtil.isEmptyOrSpaces(macroValue)) { builder.replace(startIndex, endIndex + 1, macroValue); endIndex = endIndex + macroValue.length() - (macroName.length() + 3); } } else { break; } } return builder.toString(); } public static <T> boolean equalLists(final List<T> list1, final List<T> list2) { if (list1.size() != list2.size()) return false; final Iterator<T> iterator = list1.iterator(); for (final T element : list2) { if (!iterator.next().equals(element)) return false; } return true; } public static String getContentOrModuleFolderPath(final Module module) { final String[] contentRootUrls = ModuleRootManager.getInstance(module).getContentRootUrls(); return contentRootUrls.length > 0 ? VfsUtilCore.urlToPath(contentRootUrls[0]) : PathUtil.getParentPath(module.getModuleFilePath()); } @Nullable public static VirtualFile createDirIfMissing(final Project project, final boolean interactive, final String folderPath, final String errorMessageTitle) { VirtualFile folder = LocalFileSystem.getInstance().findFileByPath(folderPath); if (folder == null) { try { folder = VfsUtil.createDirectories(folderPath); } catch (IOException e) { if (interactive) { Messages.showErrorDialog(project, FlexBundle .message("failed.to.create.folder", FileUtil.toSystemDependentName(folderPath), e.getMessage()), errorMessageTitle); } return null; } } if (folder == null) { if (interactive) { Messages.showErrorDialog(project, FlexBundle.message("failed.to.create.folder", folderPath, "unknown error"), errorMessageTitle); } return null; } else if (!folder.isDirectory()) { Messages.showErrorDialog(project, FlexBundle.message("selected.path.not.folder", FileUtil.toSystemDependentName(folderPath)), errorMessageTitle); return null; } return folder; } public static boolean processCompilerOption(final Module module, final FlexBuildConfiguration bc, final String option, final Processor<Pair<String, String>> processor) { String rawValue = bc.getCompilerOptions().getOption(option); if (rawValue == null) rawValue = FlexBuildConfigurationManager.getInstance(module).getModuleLevelCompilerOptions().getOption(option); if (rawValue == null) { rawValue = FlexProjectLevelCompilerOptionsHolder.getInstance(module.getProject()).getProjectLevelCompilerOptions().getOption(option); } if (rawValue == null) return true; int pos = 0; while (true) { int index = rawValue.indexOf(CompilerOptionInfo.LIST_ENTRIES_SEPARATOR, pos); if (index == -1) break; String token = rawValue.substring(pos, index); final int tabIndex = token.indexOf(CompilerOptionInfo.LIST_ENTRY_PARTS_SEPARATOR); if (tabIndex > 0 && !processor.process(Pair.create(token.substring(0, tabIndex), token.substring(tabIndex + 1)))) return false; pos = index + 1; } final int tabIndex = rawValue.indexOf(CompilerOptionInfo.LIST_ENTRY_PARTS_SEPARATOR, pos); if (tabIndex > pos) { if (!processor.process(Pair.create(rawValue.substring(pos, tabIndex), rawValue.substring(tabIndex + 1)))) return false; } return true; } public static LinkageType convertLinkageType(final DependencyScope scope, final boolean isExported) { if (scope == DependencyScope.PROVIDED) { return LinkageType.External; } else if (scope == DependencyScope.TEST) { return LinkageType.Test; } else if (isExported) { return LinkageType.Include; } return DependencyType.DEFAULT_LINKAGE; } public static ModuleWithDependenciesScope getModuleWithDependenciesAndLibrariesScope(@NotNull Module module, @NotNull FlexBuildConfiguration bc, boolean includeTests) { // we cannot assert this since build configuration may be not yet persisted //if (!ArrayUtil.contains(bc, FlexBuildConfigurationManager.getInstance(module).getBuildConfigurations())) { // throw new IllegalArgumentException("Build configuration '" + bc.getName() + "' does not belong to module '" + module.getName() + "'"); //} // module.putUserData(FlexOrderEnumerationHandler.FORCE_BC, bc); try { return new ModuleWithDependenciesScope(module, ModuleWithDependenciesScope.COMPILE_ONLY | ModuleWithDependenciesScope.MODULES | ModuleWithDependenciesScope.LIBRARIES | (includeTests ? ModuleWithDependenciesScope.TESTS : 0)); } finally { module.putUserData(FlexOrderEnumerationHandler.FORCE_BC, null); } } public static String getOwnIpAddress() { try { final Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); while (networkInterfaces.hasMoreElements()) { final Enumeration<InetAddress> inetAddresses = networkInterfaces.nextElement().getInetAddresses(); while (inetAddresses.hasMoreElements()) { final InetAddress inetAddress = inetAddresses.nextElement(); if (inetAddress instanceof Inet4Address && !inetAddress.isLoopbackAddress()) { return inetAddress.getHostAddress(); } } } } catch (SocketException ignore) {/* ignore */} return "unknown"; } public static boolean isMxmlNs(final String ns) { return ArrayUtil.contains(ns, MxmlJSClass.MXML_URIS); } }