package com.intellij.flex.uiDesigner; import com.intellij.execution.ExecutionException; import com.intellij.flex.uiDesigner.io.IOUtil; import com.intellij.flex.uiDesigner.io.MessageSocketManager; import com.intellij.flex.uiDesigner.io.StringRegistry; import com.intellij.flex.uiDesigner.libraries.InitException; import com.intellij.flex.uiDesigner.libraries.LibraryManager; import com.intellij.flex.uiDesigner.mxml.ProjectComponentReferenceCounter; import com.intellij.lang.javascript.flex.sdk.FlexSdkUtils; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.ui.ProjectJdksEditor; import com.intellij.openapi.util.Disposer; 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.text.StringUtil; import com.intellij.openapi.wm.WindowManager; import com.intellij.util.Consumer; import com.intellij.util.concurrency.Semaphore; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import static com.intellij.flex.uiDesigner.AdlUtil.*; public class DesignerApplicationLauncher extends DocumentTask { private boolean debug; private Future<ProjectComponentReferenceCounter> initializeTask; private final Semaphore semaphore = new Semaphore(); DesignerApplicationLauncher(final @NotNull Module module, final @NotNull PostTask postTask, final boolean debug) { super(module, debug, postTask); this.debug = debug; } DesignerApplicationLauncher(final @NotNull Module module, final @NotNull PostTask postTask) { this(module, postTask, false); } public void clientOpened(@NotNull OutputStream outputStream) { Client.getInstance().setOut(outputStream); LOG.info("Client opened"); semaphore.up(); } public void clientSocketNotAccepted() { indicator.cancel(); } @Override protected void beforeRun() { File startupErrorFile = new File(DesignerApplicationManager.APP_DIR, "startup-error.txt"); if (startupErrorFile.exists()) { //noinspection ResultOfMethodCallIgnored startupErrorFile.delete(); } } @Override protected void processErrorOrCancel() { if (initializeTask != null) { initializeTask.cancel(true); initializeTask = null; } if (!DesignerApplicationManager.getInstance().isApplicationClosed()) { DesignerApplicationManager.getInstance().disposeApplication(); } semaphore.up(); } @Override protected boolean doRun(@NotNull final ProgressIndicator indicator) throws IOException, java.util.concurrent.ExecutionException, InterruptedException, TimeoutException { indicator.setText(FlashUIDesignerBundle.message("copying.app.files")); copyAppFiles(); indicator.setText(FlashUIDesignerBundle.message("finding.suitable.air.runtime")); List<AdlRunConfiguration> adlRunConfigurations = getSuitableAdlRunConfigurations(); if (adlRunConfigurations.isEmpty()) { notifyNoSuitableSdkToLaunch(); return false; } indicator.checkCanceled(); DesignerApplicationManager.getInstance().setApplication(new DesignerApplication()); runInitializeLibrariesAndModuleThread(); if (debug && !runAndWaitDebugger()) { return false; } indicator.checkCanceled(); MessageSocketManager messageSocketManager = new MessageSocketManager(this, DesignerApplicationManager.APP_DIR); Disposer.register(DesignerApplicationManager.getApplication(), messageSocketManager); final List<String> arguments = new ArrayList<>(); arguments.add(Integer.toString(messageSocketManager.listen())); if (ApplicationManager.getApplication().isUnitTestMode()) { arguments.add("-p"); arguments.add(DebugPathManager.resolveTestArtifactPath("test-1.0-SNAPSHOT.swf")); } AdlProcessHandler adlProcessHandler = null; final Ref<Boolean> found = new Ref<>(true); for (AdlRunConfiguration adlRunConfiguration : adlRunConfigurations) { found.set(true); adlRunConfiguration.arguments = arguments; try { final String appClassifierVersion; if (StringUtil.compareVersionNumbers(adlRunConfiguration.getRuntimeVersion(), "3.0") < 0 || !(SystemInfo.isMac || SystemInfo.isWindows)) { appClassifierVersion = "2.6"; } else { appClassifierVersion = "3.0"; } adlProcessHandler = runAdl(adlRunConfiguration, DesignerApplicationManager.APP_DIR.getPath() + File.separatorChar + "descriptor-air" + appClassifierVersion + ".xml", exitCode -> { found.set(false); if (!indicator.isCanceled()) { LOG.info(describeAdlExit(exitCode)); semaphore.up(); } }); } catch (ExecutionException e) { adlProcessHandler = null; LOG.error(e); continue; } semaphore.down(); try { if (!semaphore.waitForUnsafe(60 * 1000)) { found.set(false); LOG.warn("Client not opened in 60 seconds"); if (checkStartupError()) { return false; } } } catch (InterruptedException e) { if (indicator.isCanceled()) { return false; } LOG.warn(e); continue; } indicator.checkCanceled(); if (found.get()) { break; } } if (!found.get()) { if (!checkStartupError()) { notifyNoSuitableSdkToLaunch(); } return false; } ProjectComponentReferenceCounter projectComponentReferenceCounter = initializeTask.get(DebugPathManager.IS_DEV ? 999 : 60, TimeUnit.SECONDS); indicator.checkCanceled(); final DesignerApplication application = DesignerApplicationManager.getApplication(); LOG.assertTrue(adlProcessHandler != null && application != null); application.setProcessHandler(adlProcessHandler); DesignerApplicationManager.getInstance().attachProjectAndModuleListeners(application); return postTask.run(module, projectComponentReferenceCounter, indicator, problemsHolder); } private static boolean checkStartupError() throws IOException { final File startupErrorFile = new File(DesignerApplicationManager.APP_DIR, "startup-error.txt"); if (!startupErrorFile.exists()) { return false; } LOG.error(FileUtil.loadFile(startupErrorFile)); //noinspection ResultOfMethodCallIgnored startupErrorFile.delete(); return true; } private boolean runAndWaitDebugger() { final AtomicBoolean result = new AtomicBoolean(); final Semaphore debuggerRunSemaphore = new Semaphore(); debuggerRunSemaphore.down(); ApplicationManager.getApplication().invokeLater(() -> { try { runDebugger(module, () -> { result.set(true); debuggerRunSemaphore.up(); }); } catch (ExecutionException e) { LOG.error(e); debuggerRunSemaphore.up(); } }); debuggerRunSemaphore.waitFor(); return result.get(); } private void notifyNoSuitableSdkToLaunch() { String message = FlashUIDesignerBundle.message(SystemInfo.isLinux ? "no.sdk.to.launch.designer.linux" : "no.sdk.to.launch.designer"); DesignerApplicationManager.notifyUser(debug, message, module.getProject(), id -> { if ("edit".equals(id)) { new ProjectJdksEditor(null, module.getProject(), WindowManager.getInstance().suggestParentWindow(myProject)).show(); } else { LOG.error("unexpected id: " + id); } }); } private static List<AdlRunConfiguration> getSuitableAdlRunConfigurations() throws IOException { if (ApplicationManager.getApplication().isUnitTestMode()) { return Collections.singletonList(createTestAdlRunConfiguration()); } final List<Sdk> sdks = new ArrayList<>(); for (Sdk sdk : FlexSdkUtils.getFlexAndFlexmojosSdks()) { if (StringUtil.compareVersionNumbers(sdk.getVersionString(), "4.5") >= 0) { sdks.add(sdk); } } Collections.sort(sdks, (o1, o2) -> StringUtil.compareVersionNumbers(o2.getVersionString(), o1.getVersionString())); final String installedRuntime = findInstalledRuntime(); final List<AdlRunConfiguration> result = new ArrayList<>(sdks.size()); for (Sdk sdk : sdks) { final String adlPath = FlexSdkUtils.getAdlPath(sdk); if (!checkAdl(adlPath)) { continue; } String runtime = FlexSdkUtils.getAirRuntimePath(sdk); if (checkRuntime(runtime) || checkRuntime((runtime = installedRuntime))) { result.add(new AdlRunConfiguration(adlPath, runtime, StringUtil.compareVersionNumbers(sdk.getVersionString(), "4.6") < 0 ? "2.6" : "3.0")); } } return result; } private static AdlRunConfiguration createTestAdlRunConfiguration() { String adlExecutable = System.getProperty("adl.executable"); if (adlExecutable == null) { if (SystemInfo.isMac) { adlExecutable = System.getProperty("user.home") + "/sdks/flex4.6.0/bin/adl"; } else { throw new IllegalStateException("Please define 'adl.executable' to point to ADL executable"); } } String adlRuntime = System.getProperty("adl.runtime"); if (adlRuntime == null) { if (SystemInfo.isMac) { adlRuntime = "/Library/Frameworks"; } else { throw new IllegalStateException("Please define 'adl.runtime' to point to ADL runtime"); } } return new AdlRunConfiguration(adlExecutable, adlRuntime, "3.0"); } private static void copyAppFiles() throws IOException { @SuppressWarnings("unchecked") Pair<String, String>[] files = new Pair[]{ new Pair("designer-air2.6.swf", "main-loader/target/main-loader-1.0-SNAPSHOT-air2.6.swf"), new Pair("designer-air3.0.swf", "main-loader/target/main-loader-1.0-SNAPSHOT.swf"), new Pair("descriptor-air2.6.xml", "main/resources/descriptor-air2.6.xml"), new Pair("descriptor-air3.0.xml", "main/resources/descriptor-air3.0.xml") }; if (DebugPathManager.IS_DEV) { for (Pair<String, String> file : files) { IOUtil.saveStream(new URL("file://" + DebugPathManager.resolveTestArtifactPath(file.first, file.second)), new File(DesignerApplicationManager.APP_DIR, file.first)); } } else { ClassLoader classLoader = DesignerApplicationLauncher.class.getClassLoader(); //noinspection unchecked for (Pair<String, String> file : files) { IOUtil.saveStream(classLoader.getResource(file.first), new File(DesignerApplicationManager.APP_DIR, file.first)); } } } private void runInitializeLibrariesAndModuleThread() { initializeTask = ApplicationManager.getApplication().executeOnPooledThread(() -> { try { LibraryManager.getInstance().init(); indicator.checkCanceled(); if (!StringRegistry.getInstance().isEmpty()) { Client.getInstance().initStringRegistry(); } indicator.setText(FlashUIDesignerBundle.message("collect.libraries")); assert myProject != null; DumbService dumbService = DumbService.getInstance(myProject); if (dumbService.isDumb()) { dumbService.waitForSmartMode(); } return LibraryManager.getInstance().registerModule(module, problemsHolder); } catch (Throwable e) { if (initializeTask == null || initializeTask.isCancelled()) { return null; } //noinspection InstanceofCatchParameter if (e instanceof InitException) { processInitException((InitException)e, module, debug); } else { LOG.error(e); } indicator.cancel(); return null; } }); } }