package com.intellij.lang.javascript.flex.sdk;
import com.intellij.flex.FlexCommonUtils;
import com.intellij.flex.model.bc.ComponentSet;
import com.intellij.flex.model.bc.TargetPlatform;
import com.intellij.lang.javascript.flex.FlexUtils;
import com.intellij.lang.javascript.flex.build.FlexCompilerProjectConfiguration;
import com.intellij.lang.javascript.flex.projectStructure.FlexBuildConfigurationsExtension;
import com.intellij.lang.javascript.flex.projectStructure.model.FlexBuildConfiguration;
import com.intellij.lang.javascript.flex.projectStructure.model.impl.FlexProjectConfigurationEditor;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.ProjectJdkTable;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkAdditionalData;
import com.intellij.openapi.projectRoots.SdkType;
import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl;
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
import com.intellij.openapi.roots.ui.configuration.ClasspathEditor;
import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtil;
import com.intellij.util.PairConsumer;
import com.intellij.util.Processor;
import com.intellij.util.SystemProperties;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.ZipUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FlexSdkUtils {
public static final String ADL_RELATIVE_PATH =
File.separatorChar + "bin" + File.separatorChar + "adl" + (SystemInfo.isWindows ? ".exe" : "");
static final String AIR_RUNTIME_RELATIVE_PATH =
File.separatorChar + "runtimes" + File.separatorChar + "air" + File.separatorChar +
(SystemInfo.isWindows ? "win" : (SystemInfo.isLinux ? "linux" : "mac"));
private static final Pattern PLAYER_FOLDER_PATTERN = Pattern.compile("\\d{1,2}(\\.\\d{1,2})?");
private FlexSdkUtils() {
}
public static void processPlayerglobalSwcFiles(final VirtualFile playerDir, final Processor<VirtualFile> processor) {
VirtualFile playerglobalSwcFile;
VirtualFile[] children = playerDir.getChildren();
VirtualFile[] sorted = Arrays.copyOf(children, children.length);
Arrays.sort(sorted, (o1, o2) -> o1.getName().compareTo(o2.getName()));
for (final VirtualFile subDir : sorted) {
if (subDir.isDirectory() &&
(playerglobalSwcFile = subDir.findChild("playerglobal.swc")) != null &&
PLAYER_FOLDER_PATTERN.matcher(subDir.getName()).matches()) {
if (!processor.process(playerglobalSwcFile)) {
break;
}
}
}
}
@Nullable
public static String doReadFlexSdkVersion(final VirtualFile sdkRoot) {
return doReadSdkVersion(sdkRoot, false);
}
@Nullable
public static String doReadAirSdkVersion(final VirtualFile sdkRoot) {
return doReadSdkVersion(sdkRoot, true);
}
@Nullable
private static String doReadSdkVersion(final VirtualFile sdkRoot, final boolean airSdk) {
if (sdkRoot == null) {
return null;
}
final VirtualFile flexSdkDescriptionFile = sdkRoot.findChild(airSdk ? "air-sdk-description.xml" : "flex-sdk-description.xml");
if (flexSdkDescriptionFile == null) {
return null;
}
try {
final String versionElement = airSdk ? "<air-sdk-description><version>" : "<flex-sdk-description><version>";
final String buildElement = airSdk ? "<air-sdk-description><build>" : "<flex-sdk-description><build>";
final Map<String, List<String>> versionInfo =
FlexUtils.findXMLElements(flexSdkDescriptionFile.getInputStream(), Arrays.asList(versionElement, buildElement));
final List<String> majorMinor = versionInfo.get(versionElement);
final List<String> revision = versionInfo.get(buildElement);
return majorMinor.isEmpty() ? null
: majorMinor.get(0) + (revision.isEmpty() ? "" : ("." + revision.get(0)));
}
catch (IOException e) {
return null;
}
}
@Nullable
public static Sdk createOrGetSdk(final SdkType sdkType, final String path) {
// todo work with sdk modifiable model if Project Structure is open!
final VirtualFile sdkHome = path == null ? null : LocalFileSystem.getInstance().findFileByPath(path);
if (sdkHome == null) return null;
final ProjectJdkTable projectJdkTable = ProjectJdkTable.getInstance();
for (final Sdk flexSdk : projectJdkTable.getSdksOfType(sdkType)) {
final String existingHome = flexSdk.getHomePath();
if (existingHome != null && sdkHome.getPath().equals(FileUtil.toSystemIndependentName(existingHome))) {
if (sdkType instanceof FlexmojosSdkType) {
final SdkAdditionalData data = flexSdk.getSdkAdditionalData();
if (data == null || ((FlexmojosSdkAdditionalData)data).getFlexCompilerClasspath().isEmpty()) {
sdkType.setupSdkPaths(flexSdk);
}
}
return flexSdk;
}
}
return createSdk(sdkType, sdkHome.getPath());
}
private static Sdk createSdk(final SdkType sdkType, final @NotNull String sdkHomePath) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
return doCreateSdk(sdkType, sdkHomePath);
}
else {
final Ref<Sdk> sdkRef = new Ref<>();
ApplicationManager.getApplication().invokeAndWait(() -> sdkRef.set(doCreateSdk(sdkType, sdkHomePath)));
return sdkRef.get();
}
}
private static Sdk doCreateSdk(final SdkType sdkType, final @NotNull String sdkHomePath) {
return WriteAction.compute(() -> {
final ProjectJdkTable projectJdkTable = ProjectJdkTable.getInstance();
final String sdkName = SdkConfigurationUtil.createUniqueSdkName(sdkType, sdkHomePath, projectJdkTable.getSdksOfType(sdkType));
final Sdk sdk = new ProjectJdkImpl(sdkName, sdkType, sdkHomePath, "");
sdkType.setupSdkPaths(sdk);
projectJdkTable.addJdk(sdk);
return sdk;
});
}
public static int getFlexSdkRevision(final String sdkVersion) {
// "4.5.0.17689"
// "4.5.0 build 17689"
try {
final int index = Math.max(sdkVersion.lastIndexOf('.'), sdkVersion.lastIndexOf(' '));
return Integer.parseInt(sdkVersion.substring(index + 1));
}
catch (NumberFormatException ignore) {/*ignore*/}
return 0;
}
public static String getAdlPath(final @NotNull Sdk sdk) {
if (sdk.getSdkType() instanceof FlexmojosSdkType) {
final SdkAdditionalData data = sdk.getSdkAdditionalData();
if (data instanceof FlexmojosSdkAdditionalData) {
return ((FlexmojosSdkAdditionalData)data).getAdlPath();
}
}
return sdk.getHomePath() + ADL_RELATIVE_PATH;
}
/**
* @return either path to a directory or to the zip file. It can't be used to construct command line. Use {@link #getAirRuntimeDirInfoForFlexmojosSdk(com.intellij.openapi.projectRoots.Sdk) instead}
*/
public static String getAirRuntimePathForFlexmojosSdk(final @NotNull Sdk sdk) {
assert sdk.getSdkType() instanceof FlexmojosSdkType;
if (sdk.getSdkType() instanceof FlexmojosSdkType) {
final SdkAdditionalData data = sdk.getSdkAdditionalData();
if (data instanceof FlexmojosSdkAdditionalData) {
return ((FlexmojosSdkAdditionalData)data).getAirRuntimePath();
}
}
return sdk.getHomePath() + AIR_RUNTIME_RELATIVE_PATH;
}
@Nullable
public static String getAirRuntimePath(final @NotNull Sdk sdk) {
if (sdk.getSdkType() instanceof FlexmojosSdkType) {
final SdkAdditionalData data = sdk.getSdkAdditionalData();
if (data instanceof FlexmojosSdkAdditionalData) {
return ((FlexmojosSdkAdditionalData)data).getAirRuntimePath();
}
}
else {
return sdk.getHomePath() + AIR_RUNTIME_RELATIVE_PATH;
}
return null;
}
/**
* This method unzips AIR Runtime to temporary directory if AIR Runtime is set as a zip file.<br>
* <b>Caller is responsible to delete temporary files</b> when AIR application terminates.
*
* @return the first object is a directory containing AIR Runtime.<br>
* The second object is <code>Boolean.TRUE</code> if this directory is temporary (contains zip file content) and must be deleted when AIR application terminates.
*/
public static Pair<VirtualFile, Boolean> getAirRuntimeDirInfoForFlexmojosSdk(final @NotNull Sdk sdk) throws IOException {
assert sdk.getSdkType() instanceof FlexmojosSdkType;
final String airRuntimePath = getAirRuntimePathForFlexmojosSdk(sdk);
final VirtualFile airRuntime = LocalFileSystem.getInstance().findFileByPath(airRuntimePath);
if (airRuntime == null) {
throw new IOException("Can't find AIR Runtime at " + airRuntimePath);
}
if (airRuntime.isDirectory()) {
return Pair.create(airRuntime, Boolean.FALSE);
}
else {
final String systemTempDirPath = FileUtil.getTempDirectory();
final File systemTempDir = new File(systemTempDirPath);
if (!systemTempDir.exists() || !systemTempDir.isDirectory()) {
throw new IOException("Temp directory doesn't exist: " + systemTempDirPath);
}
final String tempDirPath = findUniqueTempDirName(systemTempDir);
try {
final VirtualFile tempDir = unzip(airRuntime.getPath(), tempDirPath);
return Pair.create(tempDir, Boolean.TRUE);
}
catch (IOException e) {
throw new IOException(
MessageFormat.format("Can''t unzip file ''{0}'' to ''{1}'': {2}", FileUtil.toSystemDependentName(airRuntime.getPath()),
tempDirPath, e.getMessage()));
}
}
}
private static String findUniqueTempDirName(final File systemTempDir) {
String tempDirName;
final String unzipDirNameBase = "intellij_air_runtime_";
final String[] children = systemTempDir.list();
for (int i = 1; ; i++) {
tempDirName = unzipDirNameBase + i;
if (!ArrayUtil.contains(tempDirName, children)) break;
}
return systemTempDir.getPath() + File.separatorChar + tempDirName;
}
@NotNull
private static VirtualFile unzip(final String zipFilePath, final String outputDirPath) throws IOException {
final Ref<IOException> ioExceptionRef = new Ref<>();
final VirtualFile dir = ApplicationManager.getApplication().runWriteAction((NullableComputable<VirtualFile>)() -> {
try {
ZipUtil.extract(new File(zipFilePath), new File(outputDirPath), null);
final VirtualFile tempDir = LocalFileSystem.getInstance().refreshAndFindFileByPath(outputDirPath);
assert tempDir != null;
return tempDir;
}
catch (IOException e) {
ioExceptionRef.set(e);
}
return null;
});
if (!ioExceptionRef.isNull()) {
throw ioExceptionRef.get();
}
else {
assert dir != null;
return dir;
}
}
public static boolean isFlex2Sdk(final @Nullable Sdk flexSdk) {
final String version = flexSdk == null ? null : flexSdk.getVersionString();
return version != null && (version.startsWith("2.") || version.startsWith("3.0 Moxie"));
}
public static boolean isFlex3_0Sdk(final @Nullable Sdk flexSdk) {
final String version = flexSdk == null ? null : flexSdk.getVersionString();
return version != null && version.startsWith("3.0");
}
public static boolean isFlex4Sdk(final @Nullable Sdk flexSdk) {
final String version = flexSdk == null ? null : flexSdk.getVersionString();
return version != null && version.startsWith("4.");
}
/**
* @param mainClass used in case of Flexmojos SDK, also used for ordinary Flex SDK if <code>jarName</code> is <code>null</code>
* @param jarName if not <code>null</code> - this parameter used in case of Flex SDK; always ignored in case of Flexmojos SDK
*/
public static List<String> getCommandLineForSdkTool(final @NotNull Project project,
final @NotNull Sdk sdk,
final @Nullable String additionalClasspath,
final @NotNull String mainClass,
final @Nullable String jarName) {
String javaHome = SystemProperties.getJavaHome();
boolean customJavaHomeSet = false;
String additionalJavaArgs = null;
int heapSizeMbFromJvmConfig = 0;
String classpath = additionalClasspath;
final boolean isFlexmojos = sdk.getSdkType() == FlexmojosSdkType.getInstance();
final FlexmojosSdkAdditionalData flexmojosSdkData = isFlexmojos ? (FlexmojosSdkAdditionalData)sdk.getSdkAdditionalData() : null;
if (isFlexmojos) {
classpath = (StringUtil.isEmpty(classpath) ? "" : (classpath + File.pathSeparator)) +
FileUtil.toSystemDependentName(StringUtil.join(flexmojosSdkData.getFlexCompilerClasspath(), File.pathSeparator));
}
else {
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(FileUtil.toSystemDependentName(sdk.getHomePath() + "/bin/jvm.config"));
final Properties properties = new Properties();
properties.load(inputStream);
final String configuredJavaHome = properties.getProperty("java.home");
if (configuredJavaHome != null && configuredJavaHome.trim().length() > 0) {
javaHome = configuredJavaHome;
customJavaHomeSet = true;
}
final String javaArgs = properties.getProperty("java.args");
if (javaArgs != null && javaArgs.trim().length() > 0) {
additionalJavaArgs = javaArgs;
final Matcher matcher = FlexCommonUtils.XMX_PATTERN.matcher(javaArgs);
if (matcher.matches()) {
try {
heapSizeMbFromJvmConfig = Integer.parseInt(matcher.group(2));
}
catch (NumberFormatException e) {/*ignore*/}
}
}
final String classpathFromJvmConfig = properties.getProperty("java.class.path");
if (classpathFromJvmConfig != null && classpathFromJvmConfig.trim().length() > 0) {
classpath = (StringUtil.isEmpty(classpath) ? "" : (classpath + File.pathSeparator)) + classpathFromJvmConfig;
}
//jvm.config also has properties which are not handled here: 'env' and 'java.library.path'; though not sure that there's any sense in them
}
catch (IOException e) {
// not a big problem, will use default settings
if (inputStream != null) {
try {
inputStream.close();
}
catch (IOException e1) {/*ignore*/}
}
}
}
final String javaExecutable = FileUtil.toSystemDependentName((javaHome + "/bin/java" + (SystemInfo.isWindows ? ".exe" : "")));
final String applicationHomeParam =
isFlexmojos ? null : ("-Dapplication.home=" + FileUtil.toSystemDependentName(sdk.getHomePath()));
final String d32 = FlexCommonUtils.getD32IfNeed(customJavaHomeSet, javaHome);
final List<String> result = new ArrayList<>();
result.add(javaExecutable);
if (StringUtil.isNotEmpty(d32)) result.add(d32);
if (StringUtil.isNotEmpty(applicationHomeParam)) result.add(applicationHomeParam);
if (StringUtil.isNotEmpty(additionalJavaArgs)) result.addAll(StringUtil.split(additionalJavaArgs, " "));
final String vmOptions = FlexCompilerProjectConfiguration.getInstance(project).VM_OPTIONS;
if (StringUtil.isNotEmpty(vmOptions)) result.addAll(StringUtil.split(vmOptions, " "));
if (additionalJavaArgs == null || !additionalJavaArgs.contains("file.encoding")) {
result.add("-Dfile.encoding=" + FlexCommonUtils.SDK_TOOLS_ENCODING);
}
result.add("-Djava.awt.headless=true");
result.add("-Duser.language=en");
result.add("-Duser.region=en");
final int heapSizeMb = FlexCompilerProjectConfiguration.getInstance(project).HEAP_SIZE_MB;
if (heapSizeMb > heapSizeMbFromJvmConfig) {
result.add("-Xmx" + heapSizeMb + "m");
}
if (StringUtil.isNotEmpty(classpath)) {
result.add("-classpath");
result.add(classpath);
}
if (isFlexmojos || jarName == null) {
result.add(mainClass);
}
else {
result.add("-jar");
result.add(FileUtil.toSystemDependentName(sdk.getHomePath() + "/lib/" + jarName));
}
return result;
}
public static void openModuleConfigurable(final Module module) {
final ProjectStructureConfigurable projectStructureConfigurable = ProjectStructureConfigurable.getInstance(module.getProject());
ShowSettingsUtil.getInstance().editConfigurable(module.getProject(), projectStructureConfigurable, () -> projectStructureConfigurable.select(module.getName(), ClasspathEditor.NAME, true));
}
/**
* @param processor (namespace, relative path with no leading slash)
*/
public static void processStandardNamespaces(FlexBuildConfiguration bc, PairConsumer<String, String> processor) {
final Sdk sdk = bc.getSdk();
if (bc.isPureAs() || sdk == null || sdk.getSdkType() == FlexmojosSdkType.getInstance()) return;
if (StringUtil.compareVersionNumbers(sdk.getVersionString(), "4") < 0) {
processor.consume("http://www.adobe.com/2006/mxml", "frameworks/mxml-manifest.xml");
}
else {
processor.consume("http://ns.adobe.com/mxml/2009", "frameworks/mxml-2009-manifest.xml");
if (bc.getTargetPlatform() == TargetPlatform.Mobile ||
bc.getDependencies().getComponentSet() == ComponentSet.SparkAndMx ||
bc.getDependencies().getComponentSet() == ComponentSet.SparkOnly) {
processor.consume("library://ns.adobe.com/flex/spark", "frameworks/spark-manifest.xml");
}
if (bc.getTargetPlatform() != TargetPlatform.Mobile) {
if (bc.getDependencies().getComponentSet() == ComponentSet.SparkAndMx ||
bc.getDependencies().getComponentSet() == ComponentSet.MxOnly) {
processor.consume("library://ns.adobe.com/flex/mx", "frameworks/mx-manifest.xml");
}
processor.consume("http://www.adobe.com/2006/mxml", "frameworks/mxml-manifest.xml");
}
}
}
public static Sdk[] getAllSdks() {
final FlexProjectConfigurationEditor currentEditor = FlexBuildConfigurationsExtension.getInstance().getConfigurator().getConfigEditor();
if (currentEditor == null) {
return ProjectJdkTable.getInstance().getAllJdks();
}
else {
final Collection<Sdk> sdks =
ProjectStructureConfigurable.getInstance(currentEditor.getProject()).getProjectJdksModel().getProjectSdks().values();
return sdks.toArray(new Sdk[sdks.size()]);
}
}
public static List<Sdk> getFlexAndFlexmojosSdks() {
return ContainerUtil.filter(getAllSdks(),
sdk -> sdk.getSdkType() instanceof FlexSdkType2 || sdk.getSdkType() instanceof FlexmojosSdkType);
}
@Nullable
public static Sdk findFlexOrFlexmojosSdk(final String name) {
return ContainerUtil.find(getFlexAndFlexmojosSdks(), sdk -> name.equals(sdk.getName()));
}
public static List<Sdk> getFlexSdks() {
return ContainerUtil.filter(getAllSdks(), sdk -> sdk.getSdkType() instanceof FlexSdkType2);
}
public static boolean isAirSdkWithoutFlex(final @Nullable Sdk sdk) {
final String version = sdk == null ? null : sdk.getVersionString();
return version != null && version.startsWith(FlexCommonUtils.AIR_SDK_VERSION_PREFIX);
}
/**
* Returned string is equal to folder name in which playerglobal.swc resides
*/
public static String getTargetPlayer(final @Nullable String playerVersionInAnyForm, final String sdkHome) {
String targetPlayer = null;
final String[] targetPlayers = FlexCommonUtils.getTargetPlayers(sdkHome);
if (playerVersionInAnyForm != null) {
final Trinity<String,String,String> majorMinorRevision = FlexCommonUtils.getMajorMinorRevisionVersion(playerVersionInAnyForm);
if (ArrayUtil.contains(majorMinorRevision.first, targetPlayers)) {
targetPlayer = majorMinorRevision.first;
}
else if (ArrayUtil.contains(majorMinorRevision.first + "." + majorMinorRevision.second, targetPlayers)) {
targetPlayer = majorMinorRevision.first + "." + majorMinorRevision.second;
}
}
return targetPlayer != null ? targetPlayer : FlexCommonUtils.getMaximumVersion(targetPlayers);
}
}