package com.intellij.lang.javascript.flex.flashbuilder;
import com.intellij.lang.javascript.flex.FlexBundle;
import com.intellij.lang.javascript.flex.FlexUtils;
import com.intellij.lang.javascript.flex.projectStructure.ui.SelectFlexSdkDialog;
import com.intellij.lang.javascript.flex.sdk.FlexSdkType2;
import com.intellij.lang.javascript.flex.sdk.FlexSdkUtils;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
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 gnu.trove.THashSet;
import org.jdom.Attribute;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.*;
public class FlashBuilderSdkFinder {
static final String DEFAULT_SDK_NAME = ""; // used as default name in FB project files
private boolean myInitialized = false;
private final Project myProject;
private final String myInitiallySelectedPath;
private final List<FlashBuilderProject> myAllProjects;
private String myWorkspacePath;
private Map<String, String> mySdkNameToRootPath = new HashMap<>();
private String myAirSdkHome;
private Sdk mySdk;
private boolean myDialogWasShown = false;
private static final String FLEX_SDK_PROPERTY = "com.adobe.flexbuilder.project.flex_sdks";
private static final String SDKS_ELEMENT = "sdks";
public static final String SDKS_FOLDER = "sdks";
private static final String SDK_ELEMENT = "sdk";
private static final String DEFAULT_SDK_ATTR = "defaultSDK";
private static final String SDK_NAME_ATTR = "name";
private static final String SDK_LOCATION_ATTR = "location";
public FlashBuilderSdkFinder(final Project project,
final String initiallySelectedPath,
final List<FlashBuilderProject> allProjects) {
myProject = project;
myInitiallySelectedPath = initiallySelectedPath;
myAllProjects = allProjects;
}
@Nullable
public String getWorkspacePath() {
return myInitialized ? myWorkspacePath : findWorkspacePath();
}
@Nullable
private String findWorkspacePath() {
final Collection<VirtualFile> checked = new THashSet<>();
String wsPath = guessWorkspacePath(myInitiallySelectedPath, checked);
if (wsPath == null) {
for (FlashBuilderProject fbProject : myAllProjects) {
wsPath = guessWorkspacePath(fbProject.getProjectRootPath(), checked);
if (wsPath != null) return wsPath;
}
}
return wsPath;
}
@Nullable
private static String guessWorkspacePath(String path, final Collection<VirtualFile> checked) {
VirtualFile dir = LocalFileSystem.getInstance().findFileByPath(path);
if (dir != null && !dir.isDirectory()) {
dir = dir.getParent();
}
while (dir != null) {
if (checked.contains(dir)) return null;
if (FlashBuilderProjectFinder.isFlashBuilderWorkspace(dir)) return dir.getPath();
dir = dir.getParent();
}
return null;
}
@Nullable
public Sdk findSdk(final FlashBuilderProject fbProject) {
if (!myInitialized) {
initialize();
myInitialized = true;
}
if (fbProject.isAirSdk() && myAirSdkHome != null) {
return FlexSdkUtils.createOrGetSdk(FlexSdkType2.getInstance(), myAirSdkHome);
}
final String sdkHome = mySdkNameToRootPath.get(fbProject.getSdkName());
if (sdkHome != null) return FlexSdkUtils.createOrGetSdk(FlexSdkType2.getInstance(), sdkHome);
if (myDialogWasShown) return mySdk;
final SelectFlexSdkDialog dialog = new SelectFlexSdkDialog(myProject,
FlexBundle.message("flash.builder.project.import.title"),
FlexBundle.message("sdk.for.imported.projects", myAllProjects.size()));
if (!ApplicationManager.getApplication().isUnitTestMode()) {
dialog.show();
}
else {
dialog.close(DialogWrapper.CANCEL_EXIT_CODE);
}
myDialogWasShown = true;
mySdk = dialog.isOK() ? dialog.getSdk() : null;
return mySdk;
}
private void initialize() {
final String fbInstallationPath = findFBInstallationPath();
if (fbInstallationPath != null && !ApplicationManager.getApplication().isUnitTestMode()) {
initializeAirSdk(fbInstallationPath);
// first look for SDKs in installation, then in workspace. Some can be overwritten by workspace-specific ones.
initializeSdksFromFBInstallation(fbInstallationPath);
}
myWorkspacePath = findWorkspacePath();
if (myWorkspacePath != null) {
initSdksConfiguredInWorkspace(myWorkspacePath);
}
}
private void initializeAirSdk(final @NotNull String fbInstallationPath) {
final File pluginsDir = new File(fbInstallationPath + "/eclipse/plugins");
if (!pluginsDir.isDirectory()) return;
final File[] airSdkParents = pluginsDir.listFiles((dir, name) -> name.startsWith("com.adobe.flash.compiler_"));
for (File airSdkParent : airSdkParents) {
final String airSdkHome = airSdkParent.getPath() + "/AIRSDK";
if (FlexSdkType2.getInstance().isValidSdkHome(airSdkHome)) {
myAirSdkHome = airSdkHome;
return;
}
}
}
private void initializeSdksFromFBInstallation(final @NotNull String fbInstallationPath) {
final File sdksDir = new File(fbInstallationPath, SDKS_FOLDER);
if (!sdksDir.isDirectory()) return;
String maxVersion = "0";
for (File sdkDir : sdksDir.listFiles()) {
if (!sdkDir.isDirectory()) continue;
final File descriptionFile = new File(sdkDir, "flex-sdk-description.xml");
if (!descriptionFile.isFile()) return;
final String nameElement = "<flex-sdk-description><name>";
final String versionElement = "<flex-sdk-description><version>";
FileInputStream is = null;
try {
//noinspection IOResourceOpenedButNotSafelyClosed
is = new FileInputStream(descriptionFile);
final Map<String, List<String>> info = FlexUtils.findXMLElements(is, Arrays.asList(nameElement, versionElement));
final List<String> nameInfo = info.get(nameElement);
if (nameInfo.isEmpty()) continue;
mySdkNameToRootPath.put(nameInfo.get(0), FileUtil.toSystemIndependentName(sdkDir.getPath()));
final List<String> versionInfo = info.get(versionElement);
if (versionInfo.isEmpty()) continue;
final String version = versionInfo.get(0);
if (StringUtil.compareVersionNumbers(version, maxVersion) > 0) {
maxVersion = version;
mySdkNameToRootPath.put(DEFAULT_SDK_NAME, FileUtil.toSystemIndependentName(sdkDir.getPath()));
}
}
catch (IOException ignore) {/**/}
finally {
if (is != null) {
try {
is.close();
}
catch (IOException ignore) {/**/}
}
}
}
}
@Nullable
public static String findFBInstallationPath() {
final List<File> fbDirs = new ArrayList<>();
final FileFilter filter = dir -> {
final String name = dir.getName();
return dir.isDirectory() && (name.contains("Flash") || name.contains("Flex")) && name.contains("Builder")
&& new File(dir, SDKS_FOLDER).isDirectory();
};
final String programsPath = SystemInfo.isMac ? "/Applications" : SystemInfo.isWindows ? System.getenv("ProgramFiles") : null;
final File programsDir = programsPath == null ? null : new File(programsPath);
if (programsDir != null && programsDir.isDirectory()) {
Collections.addAll(fbDirs, programsDir.listFiles(filter));
final File adobeDir = new File(programsDir, "Adobe");
if (adobeDir.isDirectory()) {
Collections.addAll(fbDirs, adobeDir.listFiles(filter));
}
}
if (SystemInfo.isWindows) {
final String programs64Path = System.getenv("ProgramW6432");
final File programs64Dir = programs64Path == null ? null : new File(programs64Path);
if (programs64Dir != null && programs64Dir.isDirectory()) {
Collections.addAll(fbDirs, programs64Dir.listFiles(filter));
final File adobeDir = new File(programs64Dir, "Adobe");
if (adobeDir.isDirectory()) {
Collections.addAll(fbDirs, adobeDir.listFiles(filter));
}
}
}
if (fbDirs.size() == 0) return null;
if (fbDirs.size() == 1) return fbDirs.get(0).getPath();
// check the most recent
Pair<String, String> pathAndVersion = null;
for (File fbDir : fbDirs) {
final String version = guessFBVersion(fbDir.getName());
if (pathAndVersion == null || StringUtil.compareVersionNumbers(version, pathAndVersion.second) > 0) {
pathAndVersion = Pair.create(fbDir.getPath(), version);
}
}
assert pathAndVersion != null;
return pathAndVersion.first;
}
private static String guessFBVersion(final String fbInstallFolderName) {
final String suffix = " (64 Bit)";
final String folderName = fbInstallFolderName.endsWith(suffix)
? fbInstallFolderName.substring(0, fbInstallFolderName.length() - suffix.length())
: fbInstallFolderName;
final StringBuilder b = new StringBuilder();
for (int i = folderName.length() - 1; i >= 0; i--) {
final char ch = folderName.charAt(i);
if ('.' == ch || Character.isDigit(ch)) {
b.insert(0, ch);
}
else {
break;
}
}
return b.toString();
}
private boolean initSdksConfiguredInWorkspace(final String fbWorkspacePath) {
final Element sdkInfoDocument = loadSdkInfoDocument(fbWorkspacePath);
if (sdkInfoDocument == null) {
return false;
}
final Element sdksElement = sdkInfoDocument;
if (!sdksElement.getName().equals(SDKS_ELEMENT)) return false;
//noinspection unchecked
for (final Element sdkElement : sdksElement.getChildren(SDK_ELEMENT)) {
final Attribute defaultSdkAttr = sdkElement.getAttribute(DEFAULT_SDK_ATTR);
final Attribute sdkNameAttr = sdkElement.getAttribute(SDK_NAME_ATTR);
final Attribute sdkLocationAttr = sdkElement.getAttribute(SDK_LOCATION_ATTR);
if (sdkLocationAttr != null) {
if (defaultSdkAttr != null && defaultSdkAttr.getValue().equalsIgnoreCase("true")) {
mySdkNameToRootPath.put(DEFAULT_SDK_NAME, FileUtil.toSystemIndependentName(sdkLocationAttr.getValue()));
}
if (sdkNameAttr != null) {
mySdkNameToRootPath.put(sdkNameAttr.getValue(), FileUtil.toSystemIndependentName(sdkLocationAttr.getValue()));
}
}
}
return true;
}
@Nullable
private static Element loadSdkInfoDocument(final String flashBuilderWorkspacePath) {
try {
final VirtualFile projectPrefsFile =
LocalFileSystem.getInstance().findFileByPath(flashBuilderWorkspacePath + FlashBuilderProjectFinder.PROJECT_PREFS_RELATIVE_PATH);
if (projectPrefsFile == null) return null;
final Properties projectPrefsProperties = new Properties();
projectPrefsProperties.load(projectPrefsFile.getInputStream());
final String xmlString = projectPrefsProperties.getProperty(FLEX_SDK_PROPERTY);
if (xmlString == null) return null;
return JDOMUtil.load(xmlString);
}
catch (IOException e) {/*ignore*/}
catch (JDOMException e) {/*ignore*/}
return null;
}
}