package com.intellij.lang.javascript.flex.debug; import com.intellij.execution.CantRunException; import com.intellij.execution.ExecutionException; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.process.ProcessOutputTypes; import com.intellij.execution.ui.ConsoleView; import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.flex.FlexCommonUtils; import com.intellij.flex.model.bc.TargetPlatform; import com.intellij.ide.IdeBundle; import com.intellij.ide.actions.ShowFilePathAction; import com.intellij.lang.javascript.flex.FlexBundle; import com.intellij.lang.javascript.flex.FlexUtils; import com.intellij.lang.javascript.flex.actions.airpackage.AirPackageUtil; import com.intellij.lang.javascript.flex.actions.airpackage.DeviceInfo; import com.intellij.lang.javascript.flex.flexunit.FlexUnitConnection; import com.intellij.lang.javascript.flex.flexunit.FlexUnitRunnerParameters; import com.intellij.lang.javascript.flex.flexunit.SwfPolicyFileConnection; import com.intellij.lang.javascript.flex.projectStructure.model.*; import com.intellij.lang.javascript.flex.projectStructure.options.BCUtils; import com.intellij.lang.javascript.flex.projectStructure.options.FlexProjectRootsUtil; import com.intellij.lang.javascript.flex.run.*; import com.intellij.lang.javascript.flex.sdk.FlexSdkComboBoxWithBrowseButton; import com.intellij.lang.javascript.flex.sdk.FlexSdkUtils; import com.intellij.lang.javascript.flex.sdk.FlexmojosSdkType; import com.intellij.lang.javascript.psi.resolve.JSResolveUtil; import com.intellij.notification.Notification; import com.intellij.notification.NotificationType; import com.intellij.notification.Notifications; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ex.ApplicationEx; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.LibraryOrderEntry; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.util.NullableComputable; 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.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindowId; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.psi.search.FilenameIndex; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.ui.HyperlinkAdapter; import com.intellij.util.Alarm; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; import com.intellij.util.PathUtil; import com.intellij.xdebugger.*; import com.intellij.xdebugger.breakpoints.XBreakpointHandler; import com.intellij.xdebugger.breakpoints.XBreakpointProperties; import com.intellij.xdebugger.breakpoints.XLineBreakpoint; import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider; import com.intellij.xdebugger.frame.XSuspendContext; import com.intellij.xdebugger.frame.XValueMarkerProvider; import com.intellij.xdebugger.stepping.XSmartStepIntoHandler; import gnu.trove.THashSet; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.io.LocalFileFinder; import javax.swing.event.HyperlinkEvent; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.*; import static com.intellij.lang.javascript.flex.run.FlashRunnerParameters.AirMobileDebugTransport; import static com.intellij.lang.javascript.flex.run.FlashRunnerParameters.AirMobileRunTarget; import static com.intellij.lang.javascript.flex.run.RemoteFlashRunnerParameters.RemoteDebugTarget; /** * @author Maxim.Mossienko * Date: Jan 22, 2008 * Time: 4:38:36 PM */ public class FlexDebugProcess extends XDebugProcess { private static final String TRACE_MARKER = "[trace] "; public static final String DEBUGGER_GROUP_ID = "Debugger"; private static final String SRC_PATH_ELEMENT = "/src/"; private boolean debugSessionInitialized; private final Process fdbProcess; private Process adlProcess; private final MyFdbOutputReader reader; private Alarm myOutputAlarm; private final Module myModule; private final FlexBuildConfiguration myBC; private final BCBasedRunnerParameters myRunnerParameters; private final String myAppSdkHome; private final String myDebuggerSdkHome; private final String myDebuggerVersion; @NonNls static final String RESOLVED_BREAKPOINT_MARKER = "Resolved breakpoint "; @NonNls static final String BREAKPOINT_MARKER = "Breakpoint "; @NonNls private static final String FDB_MARKER = "(fdb) "; @NonNls private static final String WAITING_PLAYER_MARKER_1 = "Waiting for Player to connect"; @NonNls private static final String WAITING_PLAYER_MARKER_2 = "Trying to connect to Player"; @NonNls static final String ATTEMPTING_TO_RESOLVE_BREAKPOINT_MARKER = "Attempting to resolve breakpoint "; @NonNls private static final String ADL_PREFIX = "[AIR Debug Launcher]: "; private boolean myCheckForUnexpectedStartupStop; private Thread myDebuggerManagerThread; @NonNls static final String AMBIGUOUS_MATCHING_FILE_NAMES = "Ambiguous matching file names:"; private final FlexBreakpointsHandler myBreakpointsHandler; @NonNls private static final String FAULT_MARKER = "[Fault] "; private static final Logger LOG = Logger.getInstance(FlexDebugProcess.class.getName()); private static final boolean doSimpleTracing = ((ApplicationEx)ApplicationManager.getApplication()).isInternal(); private Object myStackFrameEqualityObject; private Map<String, String> myQName2IdMap; private int myCurrentWorker = 0; private final KnownFilesInfo myKnownFilesInfo = new KnownFilesInfo(this); private String myFdbLaunchCommand; private final LinkedList<DebuggerCommand> commandsToWrite = new LinkedList<DebuggerCommand>() { @Override public synchronized DebuggerCommand removeFirst() { waitForData(); return super.removeFirst(); } @Override public synchronized void addFirst(final DebuggerCommand debuggerCommand) { super.addFirst(debuggerCommand); notify(); } @Override public synchronized void addLast(final DebuggerCommand debuggerCommand) { super.addLast(debuggerCommand); notify(); } // TODO: other methods private void waitForData() { try { while (size() == 0) { wait(); } } catch (InterruptedException ex) { throw new RuntimeException(ex); } } }; private boolean suspended; private boolean fdbWaitingForPlayerStateReached; private boolean startupDone; private ConsoleView myConsoleView; private FlexUnitConnection myFlexUnitConnection; private SwfPolicyFileConnection myPolicyFileConnection; public FlexDebugProcess(final XDebugSession session, final FlexBuildConfiguration bc, final BCBasedRunnerParameters params) throws IOException { super(session); myModule = ModuleManager.getInstance(session.getProject()).findModuleByName(params.getModuleName()); myBC = bc; myRunnerParameters = params; LOG.assertTrue(myModule != null); final Sdk sdk = bc.getSdk(); LOG.assertTrue(sdk != null); myAppSdkHome = FileUtil.toSystemIndependentName(sdk.getHomePath()); final Sdk sdkForDebugger = params instanceof FlashRunnerParameters && bc.getTargetPlatform() == TargetPlatform.Web ? getDebuggerSdk(((FlashRunnerParameters)params).getDebuggerSdkRaw(), sdk) : sdk; myDebuggerSdkHome = FileUtil.toSystemIndependentName(sdkForDebugger.getHomePath()); myDebuggerVersion = sdkForDebugger.getVersionString(); myBreakpointsHandler = new FlexBreakpointsHandler(this); final List<String> fdbLaunchCommand = FlexSdkUtils .getCommandLineForSdkTool(session.getProject(), sdkForDebugger, getFdbClasspath(), "flex.tools.debugger.cli.DebugCLI", null); if (isFlexSdk_4_12plus_IdeMode()) { fdbLaunchCommand.add("-ide"); } if (params instanceof FlashRunnerParameters && bc.getTargetPlatform() == TargetPlatform.Mobile && (((FlashRunnerParameters)params).getMobileRunTarget() == AirMobileRunTarget.AndroidDevice || ((FlashRunnerParameters)params).getMobileRunTarget() == AirMobileRunTarget.iOSDevice) && ((FlashRunnerParameters)params).getDebugTransport() == AirMobileDebugTransport.USB) { fdbLaunchCommand.add("-p"); fdbLaunchCommand.add(String.valueOf(((FlashRunnerParameters)params).getUsbDebugPort())); } if (params instanceof RemoteFlashRunnerParameters && (((RemoteFlashRunnerParameters)params).getRemoteDebugTarget() == RemoteDebugTarget.AndroidDevice || ((RemoteFlashRunnerParameters)params).getRemoteDebugTarget() == RemoteDebugTarget.iOSDevice) && ((RemoteFlashRunnerParameters)params).getDebugTransport() == AirMobileDebugTransport.USB) { fdbLaunchCommand.add("-p"); fdbLaunchCommand.add(String.valueOf(((RemoteFlashRunnerParameters)params).getUsbDebugPort())); } fdbProcess = launchFdb(fdbLaunchCommand); if (params instanceof FlashRunnerParameters) { final FlashRunnerParameters appParams = (FlashRunnerParameters)params; switch (bc.getTargetPlatform()) { case Web: final String urlOrPath = appParams.isLaunchUrl() ? appParams.getUrl() : bc.isUseHtmlWrapper() ? PathUtil.getParentPath(bc.getActualOutputFilePath()) + "/" + BCUtils.getWrapperFileName(bc) : bc.getActualOutputFilePath(); sendCommand(new LaunchBrowserCommand(urlOrPath, appParams.getLauncherParameters())); break; case Desktop: sendAdlStartingCommand(bc, appParams); break; case Mobile: switch (appParams.getMobileRunTarget()) { case Emulator: sendAdlStartingCommand(bc, appParams); break; case AndroidDevice: final String androidAppId = FlexBaseRunner.getApplicationId(FlexBaseRunner.getAirDescriptorPath(bc, bc.getAndroidPackagingOptions())); sendCommand(new StartAppOnAndroidDeviceCommand(bc.getSdk(), appParams.getDeviceInfo(), androidAppId)); break; case iOSSimulator: final String iosSimulatorAppId = FlexBaseRunner.getApplicationId(FlexBaseRunner.getAirDescriptorPath(bc, bc.getIosPackagingOptions())); sendCommand(new StartAppOnIosSimulatorCommand(bc.getSdk(), iosSimulatorAppId, ((FlashRunnerParameters)params).getIOSSimulatorSdkPath())); break; case iOSDevice: final String iosAppName = FlexBaseRunner.getApplicationName(FlexBaseRunner.getAirDescriptorPath(bc, bc.getIosPackagingOptions())); sendCommand(new StartAppOnIosDeviceCommand(iosAppName)); break; } } } else if (params instanceof FlexUnitRunnerParameters) { final FlexUnitRunnerParameters flexUnitParams = (FlexUnitRunnerParameters)params; openFlexUnitConnections(flexUnitParams.getSocketPolicyPort(), flexUnitParams.getPort()); if (bc.getTargetPlatform() == TargetPlatform.Web) { sendCommand(new LaunchBrowserCommand(bc.getActualOutputFilePath(), flexUnitParams.getLauncherParameters())); } else { sendAdlStartingCommand(bc, params); } } else { // Flash Remote Debug run configuration sendCommand(new StartDebuggingCommand()); } reader = new MyFdbOutputReader(fdbProcess.getInputStream()); startCommandProcessingThread(); } @Nullable public Module getModule() { return myModule.isDisposed() ? null : myModule; } public FlexBuildConfiguration getBC() { return myBC; } public boolean isFlexUnit() { return myRunnerParameters instanceof FlexUnitRunnerParameters; } public String getAppSdkHome() { return myAppSdkHome; } public static Sdk getDebuggerSdk(final String sdkRaw, final Sdk bcSdk) { if (sdkRaw.equals(FlexSdkComboBoxWithBrowseButton.BC_SDK_KEY)) { return bcSdk; } else { final Sdk sdk = FlexSdkUtils.findFlexOrFlexmojosSdk(sdkRaw); LOG.assertTrue(sdk != null); return sdk; } } private String getFdbClasspath() { final String legacyFdbPath = myDebuggerSdkHome + "/lib/legacy/fdb.jar"; if (new File(legacyFdbPath).isFile()) { return legacyFdbPath; } String classpath = myDebuggerSdkHome + "/lib/fdb.jar"; if (isDebuggerFromSdk3()) { classpath = FlexCommonUtils.getPathToBundledJar("idea-fdb-3-fix.jar") + File.pathSeparator + classpath; } else if (!myDebuggerVersion.startsWith(FlexCommonUtils.AIR_SDK_VERSION_PREFIX)) { if (StringUtil.compareVersionNumbers(myDebuggerVersion, "4.0") >= 0 && StringUtil.compareVersionNumbers(myDebuggerVersion, "4.1.1") < 0) { classpath = FlexCommonUtils.getPathToBundledJar("idea-fdb-4.0.0.14159-fix.jar") + File.pathSeparator + classpath; } else if (myDebuggerVersion.startsWith("4.6.b") || (StringUtil.compareVersionNumbers(myDebuggerVersion, "4.5") >= 0 && StringUtil.compareVersionNumbers(myDebuggerVersion, "4.6.1") < 0) || (StringUtil.compareVersionNumbers(myDebuggerVersion, "4.8") >= 0 && StringUtil.compareVersionNumbers(myDebuggerVersion, "4.12") < 0)) { classpath = FlexCommonUtils.getPathToBundledJar("idea-fdb-4.5.0.20967-fix.jar") + File.pathSeparator + classpath; } } return classpath; } private void openFlexUnitConnections(final int socketPolicyPort, final int port) { try { myPolicyFileConnection = new SwfPolicyFileConnection(); myPolicyFileConnection.open(socketPolicyPort); myFlexUnitConnection = new FlexUnitConnection(); myFlexUnitConnection.addListener(new FlexUnitConnection.Listener() { @Override public void statusChanged(FlexUnitConnection.ConnectionStatus status) { if (status == FlexUnitConnection.ConnectionStatus.CONNECTION_FAILED) { getSession().stop(); } } @Override public void onData(String line) { getProcessHandler().notifyTextAvailable(line + "\n", ProcessOutputTypes.STDOUT); } @Override public void onFinish() { getProcessHandler().detachProcess(); } }); myFlexUnitConnection.open(port); } catch (ExecutionException e) { Notifications.Bus.notify(new Notification( DEBUGGER_GROUP_ID, FlexBundle.message("flex.debugger.startup.error"), FlexBundle.message("flexunit.startup.error", e.getMessage()), NotificationType.ERROR ), getSession().getProject()); myFlexUnitConnection = null; myPolicyFileConnection = null; } } private Process launchFdb(final List<String> fdbLaunchCommand) throws IOException { ensureExecutable(fdbLaunchCommand.get(0)); myFdbLaunchCommand = StringUtil.join(fdbLaunchCommand, s -> s.indexOf(' ') >= 0 && !(s.startsWith("\"") && s.endsWith("\"")) ? '\"' + s + '\"' : s, " "); final Process process = Runtime.getRuntime().exec(ArrayUtil.toStringArray(fdbLaunchCommand)); sendCommand(new ReadGreetingCommand()); // just to read copyrights and wait for "(fdb)" return process; } private void startCommandProcessingThread() { ApplicationManager.getApplication().executeOnPooledThread(() -> { myDebuggerManagerThread = Thread.currentThread(); synchronized (this) { if (!debugSessionInitialized) { try { this.wait(); } catch (InterruptedException e) { // ignore } } } try { while (true) { processOneCommandLoop(); } } catch (IOException ex) { myConsoleView.print(ex.toString(), ConsoleViewContentType.ERROR_OUTPUT); getProcessHandler().detachProcess(); fdbProcess.destroy(); LOG.warn(ex); } catch (InterruptedException e) { return; } catch (RuntimeException ex) { final Throwable throwable = ex.getCause(); if (throwable instanceof InterruptedException) return; throw ex; } finally { try { fdbProcess.getInputStream().close(); } catch (IOException ex) { } } }); } private void sendAdlStartingCommand(final FlexBuildConfiguration bc, final BCBasedRunnerParameters params) throws IOException { try { final Sdk sdk = bc.getSdk(); LOG.assertTrue(sdk != null); final boolean needToRemoveAirRuntimeDir; final VirtualFile airRuntimeDirForFlexmojosSdk; if (sdk.getSdkType() instanceof FlexmojosSdkType) { final Pair<VirtualFile, Boolean> airRuntimeDirInfo; airRuntimeDirInfo = FlexSdkUtils.getAirRuntimeDirInfoForFlexmojosSdk(sdk); needToRemoveAirRuntimeDir = airRuntimeDirInfo.second; airRuntimeDirForFlexmojosSdk = airRuntimeDirInfo.first; } else { needToRemoveAirRuntimeDir = false; airRuntimeDirForFlexmojosSdk = null; } final String airRuntimePath = airRuntimeDirForFlexmojosSdk == null ? null : airRuntimeDirForFlexmojosSdk.getPath(); sendCommand( new StartAirAppDebuggingCommand(FlexBaseRunner.createAdlCommandLine(getSession().getProject(), params, bc, airRuntimePath), needToRemoveAirRuntimeDir ? airRuntimeDirForFlexmojosSdk : null)); } catch (CantRunException e) { throw new IOException(e.getMessage()); } } @Override public String getCurrentStateMessage() { return getSession().isStopped() ? XDebuggerBundle.message("debugger.state.message.disconnected") : startupDone ? XDebuggerBundle.message("debugger.state.message.connected") : fdbWaitingForPlayerStateReached ? FlexBundle.message("debugger.waiting.player") : FlexBundle.message("initializing.flex.debugger"); } @NotNull @Override public XDebuggerEditorsProvider getEditorsProvider() { return new FlexDebuggerEditorsProvider(); } private static final Set<String> ourAlreadyMadeExecutable = new THashSet<>(); private static synchronized void ensureExecutable(String path) { if (!SystemInfo.isWindows && !ourAlreadyMadeExecutable.contains(path)) { try { ourAlreadyMadeExecutable.add(path); Runtime.getRuntime().exec(new String[]{"chmod", "+x", path}); } catch (IOException ex) { log(ex); } } } private void processOneCommandLoop() throws IOException, InterruptedException { assert Thread.currentThread() == myDebuggerManagerThread; final DebuggerCommand command = postCommand(); if (command == null) return; boolean explicitlyContinueRead = false; do { final CommandOutputProcessingType outputProcessingType = command.getOutputProcessingMode(); if (outputProcessingType == CommandOutputProcessingType.NO_PROCESSING || (outputProcessingType == CommandOutputProcessingType.DEFAULT_PROCESSING && !reader.hasSomeDataPending())) { return; } if (myCheckForUnexpectedStartupStop && !(command instanceof DumpOutputCommand)) { myCheckForUnexpectedStartupStop = false; } @NonNls String commandOutput = null; try { commandOutput = command.read(this); } catch (IOException e) { if (!(command instanceof QuitCommand)) { throw e; } } if (command instanceof QuitCommand) { Thread.currentThread().interrupt(); // request to finish } if (commandOutput == null) break; if (commandOutput.contains("Player session terminated") && !(command instanceof SuspendResumeDebuggerCommand)) { handleProbablyUnexpectedStop(commandOutput); break; } commandOutput = commandOutput.trim(); log(commandOutput); if (outputProcessingType == CommandOutputProcessingType.SPECIAL_PROCESSING) { log("Processed by " + command); if (command.onTextAvailable(commandOutput) == CommandOutputProcessingMode.DONE) break; explicitlyContinueRead = true; continue; } ResponseLineIterator iterator = new ResponseLineIterator(commandOutput); boolean toInsertContinue = false; boolean encounteredNonsuspendableBreakpoint = false; int index; while (iterator.hasNext()) { final String line = iterator.next(); if (line.startsWith("Active worker has changed to worker ")) { try { final String workerText = line.substring("Active worker has changed to worker ".length()); if ("Main Thread".equals(workerText)) { myCurrentWorker = 0; } else { myCurrentWorker = Integer.parseInt(workerText); } } catch (NumberFormatException e) { log("Unexpected worker number"); } } else if (line.contains("Additional ActionScript code has been loaded")) { if (!suspended) reader.readLine(false); myKnownFilesInfo.setUpToDate(false); } else if ((index = line.indexOf(BREAKPOINT_MARKER)) != -1 && !line.contains(" created")) { // TODO: move to break point handler // Breakpoint 1, aaa() at A.mxml:14 try { final int from = index + BREAKPOINT_MARKER.length(); // Breakpoint 1, aaa() at A.mxml:14 // Breakpoint 2: file ConfigurationService.as // Breakpoint 3 at 0xFFF int endOfBreakpointIndexPosition = line.indexOf(',', from); final int colonIndex = line.indexOf(':', from); final int spaceIndex = line.indexOf(' ', from); if (endOfBreakpointIndexPosition != -1) { if (colonIndex != -1) { endOfBreakpointIndexPosition = Math.min(colonIndex, endOfBreakpointIndexPosition); } if (spaceIndex != -1) { endOfBreakpointIndexPosition = Math.min(spaceIndex, endOfBreakpointIndexPosition); } index = Integer.parseInt(line.substring(from, endOfBreakpointIndexPosition)); final XLineBreakpoint<XBreakpointProperties> breakpoint = myBreakpointsHandler.getBreakpointByIndex(index); if (breakpoint != null) { FlexStackFrame frame = new FlexStackFrame(this, breakpoint.getSourcePosition()); boolean suspend = false; if (evaluateCondition(breakpoint.getConditionExpression(), frame)) { String message = evaluateMessage(breakpoint.getLogExpressionObject(), frame); suspend = getSession().breakpointReached(breakpoint, message, new FlexSuspendContext(frame)); } if (!suspend) { encounteredNonsuspendableBreakpoint = true; toInsertContinue = true; } } else { insertCommand(myBreakpointsHandler.new RemoveBreakpointCommand(index, breakpoint)); // run to cursor break point } } } catch (NumberFormatException ex) { log(ex); } } else if (line.length() > 0 && Character.isDigit(line.charAt(0))) { // we are on new location: e.g. " 119 trace('\x30 \123')" if (!encounteredNonsuspendableBreakpoint) insertCommand(new DumpSourceLocationCommand(this)); } else if (handleStdResponse(line, iterator)) { } else if (line.startsWith(RESOLVED_BREAKPOINT_MARKER)) { // TODO: move to break point handler // Resolved breakpoint 1 to aaa() at A.mxml:14 final String breakPointNumber = line.substring(RESOLVED_BREAKPOINT_MARKER.length(), line.indexOf(' ', RESOLVED_BREAKPOINT_MARKER.length())); myBreakpointsHandler.updateBreakpointStatusToVerified(breakPointNumber); } else if (line.startsWith(ATTEMPTING_TO_RESOLVE_BREAKPOINT_MARKER)) { // TODO: move to break point handler int breakpointId = Integer.parseInt(line.substring(ATTEMPTING_TO_RESOLVE_BREAKPOINT_MARKER.length(), line.indexOf(','))); final XLineBreakpoint<XBreakpointProperties> breakpoint = myBreakpointsHandler.getBreakpointByIndex(breakpointId); if (iterator.hasNext() && iterator.getNext().contains("no executable code")) { iterator.next(); myBreakpointsHandler.updateBreakpointStatusToInvalid(breakpoint); toInsertContinue = true; } else if (iterator.hasNext() && iterator.getNext().contains(AMBIGUOUS_MATCHING_FILE_NAMES)) { iterator.next(); iterator.next(); while (iterator.hasNext() && iterator.getNext().contains("#")) { iterator.next(); } if (getFileId(breakpoint.getSourcePosition().getFile().getPath()) != null) { final XBreakpointHandler handler = getBreakpointHandlers()[0]; handler.unregisterBreakpoint(breakpoint, false); handler.registerBreakpoint(breakpoint); } toInsertContinue = true; } } else if (line.startsWith("Set additional breakpoints")) { //Set additional breakpoints as desired, and then type 'continue'. toInsertContinue = true; // TODO: move to break point handler } else if (line.contains("Execution halted")) { if (!getSession().isPaused()) { getSession().pause(); } } } if (toInsertContinue) insertCommand(new ContinueCommand()); } while (explicitlyContinueRead || reader.hasSomeDataPending()); } private boolean evaluateCondition(@Nullable XExpression expression, FlexStackFrame frame) { if (expression == null || StringUtil.isEmptyOrSpaces(expression.getExpression())) { return true; } final String result = frame.eval(expression.getExpression(), this); if (result != null && (result.equalsIgnoreCase("true") || result.equalsIgnoreCase("false"))) { return Boolean.valueOf(result); } else { final String message = result == null || result.startsWith(FlexStackFrame.CANNOT_EVALUATE_EXPRESSION) ? FlexBundle.message("failed.to.evaluate.breakpoint.condition", expression) : FlexBundle.message("not.boolean.breakpoint.condition", expression, result); final Ref<Boolean> stopRef = new Ref<>(false); ApplicationManager.getApplication().invokeAndWait(() -> { final Project project = getSession().getProject(); final int answer = Messages.showYesNoDialog(project, message, FlexBundle.message("breakpoint.condition.error"), Messages.getQuestionIcon()); stopRef.set(answer == Messages.YES); }); return stopRef.get(); } } @Nullable private String evaluateMessage(@Nullable XExpression expression, FlexStackFrame frame) { if (expression == null || StringUtil.isEmptyOrSpaces(expression.getExpression())) { return null; } return frame.eval(expression.getExpression(), this); } String defaultReadCommand(DebuggerCommand command) throws IOException { return reader.readLine(command.getEndVMState() == VMState.RUNNING); } private boolean handleStdResponse(String line, ResponseLineIterator iterator) { if (line.startsWith(TRACE_MARKER)) { myConsoleView.print(line + "\n", ConsoleViewContentType.NORMAL_OUTPUT); return true; } else if (line.startsWith(FAULT_MARKER)) { myConsoleView.print(line + "\n", ConsoleViewContentType.SYSTEM_OUTPUT); while (iterator.hasNext() && iterator.getNext().startsWith("at ")) { myConsoleView.print(iterator.next() + "\n", ConsoleViewContentType.SYSTEM_OUTPUT); } return true; } else if (line.startsWith("[SWF]") || line.startsWith("[UnloadSWF]")) { if (!FilterSwfLoadUnloadMessagesAction.isFilterEnabled(getSession().getProject())) { myConsoleView.print(line + "\n", ConsoleViewContentType.SYSTEM_OUTPUT); } return true; } return false; } protected String resolveFileReference(VirtualFile file) { String marker; String id = myKnownFilesInfo.getIdByFilePathNoUpdate(file.getPath()); if (id != null) { marker = "#" + id; } else { marker = file.getName(); String expectedPackageNameFromFile = JSResolveUtil.getExpectedPackageNameFromFile(file, getSession().getProject()); if (!StringUtil.isEmpty(expectedPackageNameFromFile)) marker = expectedPackageNameFromFile + "." + marker; } return marker; } protected String getFileId(final String filePath) { return myKnownFilesInfo.getIdByFilePath(filePath); } /** * Looks for file with specified <code>fileName</code> in caches or anywhere in the project in following order: * <ul> * <li>[1] matching <code>id</code>. If the file is not within the project - try to find if its copy exists within the project</li> * <li>[2] in <code>myFileNameToPathsMap</code> matching <code>packageName</code>. If the file is not within the project - try to find if its copy exists within the project</li> * <li>[3] in the whole project with libraries matching <code>packageName</code> (prefer in BC scope)</li> * <li>[4] in the whole project with libraries (prefer BC scope)</li> * </ul> * * @param fileName * @param packageName used as auxiliary information if there are more than one file with the same name. * <code>null</code> means that we don't have information about package * @param id */ @Nullable VirtualFile findFileByNameOrId(final @NotNull String fileName, @Nullable String packageName, final @Nullable String id) { // [1] if (id != null) { final String path = myKnownFilesInfo.getFilePathById(myCurrentWorker, id); if (path != null) { final VirtualFile fileById = LocalFileFinder.findFile(path); if (packageName == null) { // try to guess package name final String mavenStyleSrc = "/src/main/flex/"; int srcIndex = path.indexOf(mavenStyleSrc); if (srcIndex > 0) { packageName = StringUtil.getPackageName(path.substring(srcIndex + mavenStyleSrc.length()), '/').replace('/', '.'); } else { srcIndex = path.indexOf(SRC_PATH_ELEMENT); if (srcIndex > 0) { packageName = StringUtil.getPackageName(path.substring(srcIndex + SRC_PATH_ELEMENT.length()), '/').replace('/', '.'); } } } if (fileById != null) { return getThisOrSimilarFileInProject(fileById, packageName); } } } if ("<null>".equals(fileName)) return null; return packageName == null ? findFile(fileName) : findFile(fileName, packageName); } /** * @see #findFileByNameOrId(String, String, String) */ @Nullable private VirtualFile findFile(final String fileName, @NotNull String packageName) { final String packagePath = packageName.replace('.', '/'); // [2] final Collection<String> paths = myKnownFilesInfo.getPathsByName(myCurrentWorker, fileName); if (paths != null) { for (final String path : paths) { final String folderPath = PathUtil.getParentPath(path); if (folderPath.endsWith(packagePath)) { final VirtualFile file = LocalFileFinder.findFile(path); if (file != null) { return getThisOrSimilarFileInProject(file, packageName); } } } } // [3] final GlobalSearchScope bcScopeBase = FlexUtils.getModuleWithDependenciesAndLibrariesScope(myModule, myBC, isFlexUnit()); final GlobalSearchScope bcScope = uniteWithLibrarySourcesOfBC(bcScopeBase, myModule, myBC, new THashSet<>()); Collection<VirtualFile> files = getFilesByName(getSession().getProject(), bcScope, fileName); VirtualFile file = getFileMatchingPackageName(getSession().getProject(), files, packageName); if (file == null) { files = getFilesByName(getSession().getProject(), GlobalSearchScope.allScope(getSession().getProject()), fileName); file = getFileMatchingPackageName(getSession().getProject(), files, packageName); } return file != null ? file : findFile(fileName); } /** * @see #findFileByNameOrId(String, String, String) */ @Nullable private VirtualFile findFile(final String fileName) { // [4] final GlobalSearchScope bcScope = FlexUtils.getModuleWithDependenciesAndLibrariesScope(myModule, myBC, isFlexUnit()); Collection<VirtualFile> files = getFilesByName(getSession().getProject(), bcScope, fileName); if (files.isEmpty()) { files = getFilesByName(getSession().getProject(), GlobalSearchScope.allScope(getSession().getProject()), fileName); } if (!files.isEmpty()) { return files.iterator().next(); } // last chance to find file out of project final Collection<String> paths = myKnownFilesInfo.getPathsByName(myCurrentWorker, fileName); if (paths != null) { for (final String path : paths) { final VirtualFile file = LocalFileFinder.findFile(path); if (file != null) { return file; } } } return null; } @NotNull private VirtualFile getThisOrSimilarFileInProject(final @NotNull VirtualFile file, final String packageName) { final Project project = getSession().getProject(); final GlobalSearchScope allScope = GlobalSearchScope.allScope(project); if (allScope.contains(file)) { return file; } // File is found on the computer but it doesn't belong to the project. That means that the library is compiled on this computer, // but in this project it is configured as if it were a 3rd party library. So we'll try to find if sources of this library are also configured. final Collection<VirtualFile> files = getFilesByName(project, allScope, file.getName()); if (packageName == null) { return !files.isEmpty() ? files.iterator().next() : file; } else { final VirtualFile fileMatchingPackage = getFileMatchingPackageName(project, files, packageName); return fileMatchingPackage != null ? fileMatchingPackage : file; } } private static Collection<VirtualFile> getFilesByName(final Project project, final GlobalSearchScope scope, final String fileName) { return ApplicationManager.getApplication().runReadAction( (NullableComputable<Collection<VirtualFile>>)() -> FilenameIndex.getVirtualFilesByName(project, fileName, scope)); } @Nullable private static VirtualFile getFileMatchingPackageName(final Project project, final Collection<VirtualFile> files, final String packageName) { for (VirtualFile file : files) { final VirtualFile sourceRoot = ProjectRootManager.getInstance(project).getFileIndex().getSourceRootForFile(file); final String relPath = sourceRoot == null ? null : VfsUtilCore.getRelativePath(file, sourceRoot, '/'); final String packagePath = relPath == null ? null : PathUtil.getParentPath(relPath).replace('/', '.'); if (packagePath != null && packagePath.equals(packageName)) { return file; } } return null; } private static GlobalSearchScope uniteWithLibrarySourcesOfBC(GlobalSearchScope scope, final Module module, final FlexBuildConfiguration bc, final Collection<FlexBuildConfiguration> processedConfigurations) { if (!processedConfigurations.add(bc)) return scope; final Collection<VirtualFile> libSourceRoots = new THashSet<>(); final Sdk sdk = bc.getSdk(); if (sdk != null) { Collections.addAll(libSourceRoots, sdk.getRootProvider().getFiles(OrderRootType.SOURCES)); } for (final DependencyEntry entry : bc.getDependencies().getEntries()) { if (entry instanceof BuildConfigurationEntry) { final Module otherModule = ((BuildConfigurationEntry)entry).findModule(); final FlexBuildConfiguration otherBC = ((BuildConfigurationEntry)entry).findBuildConfiguration(); if (otherModule != null && otherBC != null) { scope = uniteWithLibrarySourcesOfBC(scope, otherModule, otherBC, processedConfigurations); } } else if (entry instanceof ModuleLibraryEntry) { final LibraryOrderEntry orderEntry = FlexProjectRootsUtil.findOrderEntry((ModuleLibraryEntry)entry, ModuleRootManager.getInstance(module)); if (orderEntry != null) { Collections.addAll(libSourceRoots, orderEntry.getFiles(OrderRootType.SOURCES)); } } else if (entry instanceof SharedLibraryEntry) { final Library library = FlexProjectRootsUtil.findOrderEntry(module.getProject(), (SharedLibraryEntry)entry); if (library != null) { Collections.addAll(libSourceRoots, library.getFiles(OrderRootType.SOURCES)); } } } return libSourceRoots.isEmpty() ? scope : scope.uniteWith(new LibrarySourcesSearchScope(module.getProject(), libSourceRoots)); } private void handleProbablyUnexpectedStop(final String s) { if (!getSession().isStopped()) { log(s); if (myCheckForUnexpectedStartupStop) { myConsoleView.print(s + "\n", ConsoleViewContentType.SYSTEM_OUTPUT); } getProcessHandler().detachProcess(); } } private DebuggerCommand postCommand() throws IOException { DebuggerCommand command = commandsToWrite.removeFirst(); final boolean currentlyExecuting = !suspended && startupDone; if (command.getStartVMState() == VMState.RUNNING) { if (!currentlyExecuting) { if (command instanceof SuspendDebuggerCommand) ((SuspendDebuggerCommand)command).doCommandAfterSuspend(); if (command.getOutputProcessingMode() != CommandOutputProcessingType.DEFAULT_PROCESSING) return null; } command.post(this); return command; } else if (!currentlyExecuting) { if (command.getEndVMState() == VMState.RUNNING) { final DebuggerCommand nextCommand = commandsToWrite.peek(); if (nextCommand != null && nextCommand.getStartVMState() == VMState.SUSPENDED) { command = commandsToWrite.removeFirst(); if (nextCommand.getEndVMState() == VMState.SUSPENDED && !(nextCommand instanceof QuitCommand)) { insertCommand(new ContinueCommand()); } } } } if (currentlyExecuting) { command = new SuspendResumeDebuggerCommand(command); } command.post(this); return command; } boolean isDebuggerFromSdk3() { return myDebuggerVersion != null && myDebuggerVersion.startsWith("3."); } boolean isFlexSdk_4_12plus_IdeMode() { return !myDebuggerVersion.startsWith(FlexCommonUtils.AIR_SDK_VERSION_PREFIX) && StringUtil.compareVersionNumbers(myDebuggerVersion, "4.12") > 0; } void doSendCommandText(final DebuggerCommand command) throws IOException { final String text = command.getText(); setSuspended( command.getOutputProcessingMode() == CommandOutputProcessingType.NO_PROCESSING && command.getEndVMState() == VMState.SUSPENDED); log("Sent:" + text); fdbProcess.getOutputStream().write((text + "\n").getBytes()); try { fdbProcess.getOutputStream().flush(); } catch (IOException ex) { handleProbablyUnexpectedStop(FlexBundle.message("flex.debugger.unexpected.communication.error")); } } protected static void log(@NonNls String s) { s = System.currentTimeMillis() + " " + s; if (doSimpleTracing) System.out.println(s); if (LOG.isDebugEnabled()) LOG.debug(s); } static void log(final Throwable ex) { if (doSimpleTracing) ex.printStackTrace(); LOG.error(ex); } @Override public void startStepOver(@Nullable XSuspendContext context) { sendCommand(new DebuggerCommand("next")); } @Override public void startStepInto(@Nullable XSuspendContext context) { sendCommand(new DebuggerCommand("step")); } @Override @NotNull public XBreakpointHandler<?>[] getBreakpointHandlers() { return myBreakpointsHandler.getBreakpointHandlers(); } @Override public void sessionInitialized() { super.sessionInitialized(); sendCommand(new ContinueCommand() { @Override public CommandOutputProcessingType getOutputProcessingMode() { myCheckForUnexpectedStartupStop = true; return super.getOutputProcessingMode(); } }); myConsoleView = (ConsoleView)getSession().getRunContentDescriptor().getExecutionConsole(); if (myFdbLaunchCommand != null) { myConsoleView.print(myFdbLaunchCommand + "\n", ConsoleViewContentType.SYSTEM_OUTPUT); } myOutputAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, myConsoleView); scheduleOutputReading(); scheduleFdbErrorStreamReading(); getSession().setPauseActionSupported(true); synchronized (this) { debugSessionInitialized = true; notifyAll(); } } private void scheduleOutputReading() { if (myOutputAlarm.isDisposed()) return; Runnable action = new Runnable() { @Override public void run() { try { if (reader.hasSomeDataPending()) { sendCommand(new DumpOutputCommand()); } else if (!myOutputAlarm.isDisposed()) { myOutputAlarm.addRequest(this, 100); } } catch (IOException ex) { myOutputAlarm.cancelAllRequests(); } } }; myOutputAlarm.addRequest(action, 0); } void addPendingCommand(final DebuggerCommand command, int delay) { myOutputAlarm.addRequest(() -> sendCommand(command), delay); } private void scheduleFdbErrorStreamReading() { ApplicationManager.getApplication().executeOnPooledThread(() -> { InputStreamReader myErrorStreamReader = new InputStreamReader(fdbProcess.getErrorStream()); try { char[] buf = new char[1024]; int read; while ((read = myErrorStreamReader.read(buf, 0, buf.length)) >= 0) { String message = new String(buf, 0, read); LOG.debug("[fdb error stream]: " + message); myConsoleView.print(message, ConsoleViewContentType.ERROR_OUTPUT); } } catch (IOException e) { LOG.debug("fdb error stream reading error", e); } finally { try { myErrorStreamReader.close(); } catch (IOException e) {/*ignore*/} } }); } @Override public void startPausing() { sendCommand(new SuspendDebuggerCommand(new DumpSourceLocationCommand(this))); } @Override public void startStepOut(@Nullable XSuspendContext context) { sendCommand(new DebuggerCommand("finish")); } @Override public void stop() { if (myFlexUnitConnection != null) { myFlexUnitConnection.close(); } if (myPolicyFileConnection != null) { myPolicyFileConnection.close(); } sendCommand(new QuitCommand()); if (adlProcess != null) { adlProcess.destroy(); } if (!startupDone) { fdbProcess.destroy(); // connect will block input and quit command will not do the thing, but it needed for graceful cleanup } } private void reportProblem(final String s) { Notifications.Bus .notify(new Notification(DEBUGGER_GROUP_ID, FlexBundle.message("flex.debugger.startup.error"), s.replace("\n", "<br>"), NotificationType.ERROR), getSession().getProject()); } void insertCommand(DebuggerCommand command) { commandsToWrite.addFirst(command); } void sendCommand(DebuggerCommand command) { commandsToWrite.addLast(command); } @Override public void resume(@Nullable XSuspendContext context) { sendCommand(new ContinueCommand()); } @Override public void runToPosition(@NotNull final XSourcePosition position, @Nullable XSuspendContext context) { myBreakpointsHandler.handleRunToPosition(position, this); } public void sendAndProcessOneCommand(final DebuggerCommand command, final @Nullable Function<Exception, Void> onException) { insertCommand(command); try { processOneCommandLoop(); } catch (Exception e) { log(e); if (onException != null) { onException.fun(e); } else { throw new RuntimeException(e); } } } private void setSuspended(final boolean suspended) { this.suspended = suspended; } boolean filterStdResponse(String line) { ResponseLineIterator iterator = new ResponseLineIterator(line); boolean stdcontent = true; while (iterator.hasNext()) { String s = iterator.next(); if (s.startsWith("$")) { // $1 = 111 stdcontent = false; break; } if (s.length() > 0 && Character.isDigit(s.charAt(0))) { sendCommand(new DumpSourceLocationCommand(this)); } else if (!handleStdResponse(line, iterator)) { stdcontent = false; } } if (stdcontent) return true; return false; } void setQName2Id(Map<String, String> qName2IdMap, Object equalityObject) { myStackFrameEqualityObject = equalityObject; myQName2IdMap = qName2IdMap; } @Nullable Map<String, String> getQName2IdIfSameEqualityObject(Object equalityObject) { if (equalityObject != null && equalityObject.equals(myStackFrameEqualityObject)) return myQName2IdMap; return null; } class MyFdbOutputReader { private final InputStreamReader myReader; private final char[] buf = new char[8192]; private final StringBuilder lastText = new StringBuilder(); private int lastTextMarkerScanningStart; private final InputStream myInputStream; public MyFdbOutputReader(final InputStream _inputStream) { myReader = FlexCommonUtils.createInputStreamReader(_inputStream); myInputStream = _inputStream; } boolean hasSomeDataPending() throws IOException { return myInputStream.available() > 0; } String readLine(boolean nonblock) throws IOException { if (lastText != null) { final String lastText = getNextLine(nonblock); if (lastText != null) return lastText; } while (true) { int read = myReader.read(buf, 0, buf.length); if (read == -1) return null; lastText.append(buf, 0, read); if (read < buf.length) { final String lastText = getNextLine(nonblock); if (lastText != null) return lastText; } } } private String getNextLine(boolean allowEmptyMarker) { String result; String marker = FDB_MARKER; int i = lastText.indexOf(marker, lastTextMarkerScanningStart); if (i == -1) { marker = "(y or n)"; i = lastText.indexOf(marker, lastTextMarkerScanningStart); } if (i == -1 && (allowEmptyMarker || lastText.indexOf(WAITING_PLAYER_MARKER_1, lastTextMarkerScanningStart) >= 0 || lastText.indexOf(WAITING_PLAYER_MARKER_2, lastTextMarkerScanningStart) >= 0) && lastText.length() > 0) { i = lastText.length(); marker = ""; } if (i != -1) { result = lastText.substring(0, i); lastText.delete(0, i + marker.length()); lastTextMarkerScanningStart = 0; if (isBlank(lastText)) lastText.setLength(0); setSuspended(marker.length() != 0); return result; } else { lastTextMarkerScanningStart = lastText.length(); result = null; } return result; } private boolean isBlank(StringBuilder lastText) { for (int i = 0; i < lastText.length(); ++i) { if (lastText.charAt(i) != ' ') return false; } return true; } } @Override public XValueMarkerProvider<FlexValue, String> createValueMarkerProvider() { return new XValueMarkerProvider<FlexValue, String>(FlexValue.class) { @Override public boolean canMark(@NotNull final FlexValue value) { return getObjectId(value) != null; } @Override public String getMarker(@NotNull final FlexValue value) { return getObjectId(value); } private String getObjectId(final FlexValue value) { final String text = value.getResult(); final String prefix = "[Object "; final String suffix = FlexStackFrame.CLASS_MARKER; int suffixIndex; if (text.startsWith(prefix) && (suffixIndex = text.indexOf(suffix, prefix.length())) > 0) { try { return FlexStackFrame.validObjectId(text.substring(prefix.length(), suffixIndex)); } catch (NumberFormatException e) {/*ignore*/} } return null; } }; } static class QuitCommand extends DebuggerCommand { QuitCommand() { super("quit\ny", CommandOutputProcessingType.SPECIAL_PROCESSING); } } static class ContinueCommand extends DebuggerCommand { ContinueCommand() { super("continue", CommandOutputProcessingType.NO_PROCESSING, VMState.SUSPENDED, VMState.RUNNING); } } class ReadGreetingCommand extends DebuggerCommand { ReadGreetingCommand() { super("does not matter because post() is empty", CommandOutputProcessingType.SPECIAL_PROCESSING); } @Override public String read(final FlexDebugProcess flexDebugProcess) throws IOException { return reader.readLine(true); } @Override public void post(FlexDebugProcess flexDebugProcess) throws IOException { } @Override CommandOutputProcessingMode onTextAvailable(@NonNls String s) { myConsoleView.print(s + "\n", ConsoleViewContentType.SYSTEM_OUTPUT); return CommandOutputProcessingMode.DONE; } } class StartAirAppDebuggingCommand extends StartDebuggingCommand { private final GeneralCommandLine myAdlCommandLine; private final @Nullable VirtualFile myTempDirToDeleteWhenProcessFinished; public StartAirAppDebuggingCommand(final GeneralCommandLine adlCommandLine, final @Nullable VirtualFile tempDirToDeleteWhenProcessFinished) { myAdlCommandLine = adlCommandLine; myTempDirToDeleteWhenProcessFinished = tempDirToDeleteWhenProcessFinished; } @Override void launchDebuggedApplication() throws IOException { launchAdl(); } private void launchAdl() throws IOException { try { myConsoleView.print(myAdlCommandLine.getCommandLineString() + "\n", ConsoleViewContentType.SYSTEM_OUTPUT); adlProcess = myAdlCommandLine.createProcess(); } catch (ExecutionException e) { throw new IOException(e.getMessage()); } ApplicationManager.getApplication().executeOnPooledThread(() -> { InputStreamReader reader12 = new InputStreamReader(adlProcess.getInputStream()); try { char[] buf = new char[1024]; int read; while ((read = reader12.read(buf, 0, buf.length)) >= 0) { String message = new String(buf, 0, read); LOG.debug("[adl input stream]: " + message); if (!startupDone) { myConsoleView.print(ADL_PREFIX + message + (message.endsWith("\n") ? "" : "\n"), ConsoleViewContentType.ERROR_OUTPUT); } } // the process is likely already destroyed because input stream is finished, though double check makes no harm adlProcess.destroy(); try { int exitCode = adlProcess.exitValue(); myConsoleView.print(ADL_PREFIX + IdeBundle.message("finished.with.exit.code.text.message", exitCode) + "\n", exitCode == 0 ? ConsoleViewContentType.SYSTEM_OUTPUT : ConsoleViewContentType.ERROR_OUTPUT); } catch (IllegalThreadStateException ignore) { } // need to destroy fdb process that is waiting for player to start getProcessHandler().detachProcess(); } catch (IOException e) { LOG.debug("adl input stream reading error", e); myConsoleView.print(ADL_PREFIX + e.getMessage() + "\n", ConsoleViewContentType.ERROR_OUTPUT); } finally { if (myTempDirToDeleteWhenProcessFinished != null) { FlexUtils.removeFileLater(myTempDirToDeleteWhenProcessFinished); } try { reader12.close(); } catch (IOException e) {/*ignore*/} } }); ApplicationManager.getApplication().executeOnPooledThread(() -> { InputStreamReader reader1 = new InputStreamReader(adlProcess.getErrorStream()); try { char[] buf = new char[1024]; int read; while ((read = reader1.read(buf, 0, buf.length)) >= 0) { String message = new String(buf, 0, read); LOG.debug("[adl error stream]: " + message); } } catch (IOException e) { LOG.debug("adl error stream reading error", e); } finally { try { reader1.close(); } catch (IOException e) {/*ignore*/} } }); } } class StartAppOnAndroidDeviceCommand extends StartDebuggingCommand { private final Sdk myFlexSdk; private final @Nullable DeviceInfo myDevice; private final String myAppId; StartAppOnAndroidDeviceCommand(final Sdk flexSdk, final @Nullable DeviceInfo device, final String appId) { myFlexSdk = flexSdk; myDevice = device; myAppId = appId; } @Override void launchDebuggedApplication() throws IOException { ApplicationManager.getApplication().invokeLater( () -> FlexBaseRunner.launchOnAndroidDevice(getSession().getProject(), myFlexSdk, myDevice, myAppId, true)); } } class StartAppOnIosSimulatorCommand extends StartDebuggingCommand { private final Sdk myFlexSdk; private final String myAppId; private final String myIOSSdkPath; StartAppOnIosSimulatorCommand(final Sdk flexSdk, final String appId, final String iOSSdkPath) { myFlexSdk = flexSdk; myAppId = appId; myIOSSdkPath = iOSSdkPath; } @Override void launchDebuggedApplication() throws IOException { ApplicationManager.getApplication().invokeLater( () -> FlexBaseRunner.launchOnIosSimulator(getSession().getProject(), myFlexSdk, myAppId, myIOSSdkPath, true)); } } class StartAppOnIosDeviceCommand extends StartDebuggingCommand { private final String myAppName; public StartAppOnIosDeviceCommand(final String appName) { myAppName = appName; } @Override void launchDebuggedApplication() throws IOException { ApplicationManager.getApplication().invokeLater(() -> { final String adtVersion = AirPackageUtil.getAdtVersion(myModule.getProject(), myBC.getSdk()); if (StringUtil.compareVersionNumbers(adtVersion, "3.4") >= 0) { final String message = FlexBundle.message("ios.application.installed.to.debug", myAppName); ToolWindowManager.getInstance(myModule.getProject()).notifyByBalloon(ToolWindowId.DEBUG, MessageType.INFO, message); } else { final String ipaName = myBC.getIosPackagingOptions().getPackageFileName() + ".ipa"; final String outputFolder = PathUtil.getParentPath(myBC.getActualOutputFilePath()); final String message = FlexBundle.message("ios.application.packaged.to.debug", ipaName); ToolWindowManager.getInstance(myModule.getProject()) .notifyByBalloon(ToolWindowId.DEBUG, MessageType.INFO, message, null, new HyperlinkAdapter() { @Override protected void hyperlinkActivated(final HyperlinkEvent e) { ShowFilePathAction.openFile(new File(outputFolder + "/" + ipaName)); } }); } }); } } class LaunchBrowserCommand extends StartDebuggingCommand { private final @NotNull String myUrl; private final LauncherParameters myLauncherParameters; LaunchBrowserCommand(final @NotNull String url, final LauncherParameters launcherParameters) { myUrl = url; myLauncherParameters = launcherParameters; } @Override void launchDebuggedApplication() { FlexBaseRunner.launchWithSelectedApplication(myUrl, myLauncherParameters); } } protected void notifyFdbWaitingForPlayerStateReached() { if (myRunnerParameters instanceof RemoteFlashRunnerParameters) { final RemoteDebugTarget remoteDebugTarget = ((RemoteFlashRunnerParameters)myRunnerParameters).getRemoteDebugTarget(); final AirMobileDebugTransport mobileDebugTransport = ((RemoteFlashRunnerParameters)myRunnerParameters).getDebugTransport(); final int usbDebugPort = ((RemoteFlashRunnerParameters)myRunnerParameters).getUsbDebugPort(); final String message; if (remoteDebugTarget == RemoteDebugTarget.Computer) { message = FlexBundle.message("remote.flash.debug.computer", FlexUtils.getOwnIpAddress()); } else { final String device = remoteDebugTarget == RemoteDebugTarget.AndroidDevice ? "Android" : "iOS"; message = mobileDebugTransport == AirMobileDebugTransport.Network ? FlexBundle.message("remote.flash.debug.mobile.network", device, FlexUtils.getOwnIpAddress()) : FlexBundle.message("remote.flash.debug.mobile.usb", device, String.valueOf(usbDebugPort)); } ApplicationManager.getApplication().invokeLater( () -> ToolWindowManager.getInstance(getSession().getProject()).notifyByBalloon(ToolWindowId.DEBUG, MessageType.INFO, message)); } } class StartDebuggingCommand extends DebuggerCommand { StartDebuggingCommand() { super("run", CommandOutputProcessingType.SPECIAL_PROCESSING); setSuspended(true); } @Override CommandOutputProcessingMode onTextAvailable(String s) { StringBuilder builder = new StringBuilder(s.length()); StringTokenizer tokenizer = new StringTokenizer(s, "\r\n"); while (tokenizer.hasMoreTokens()) { String next = tokenizer.nextToken(); if (!next.contains("type 'continue'")) { builder.append(next).append("\n"); } } s = builder.toString(); final int unexpectedVersionIndex = s.indexOf("Unexpected version of the Flash Player"); if (unexpectedVersionIndex >= 0) { s = s.substring(0, unexpectedVersionIndex) + "Session timed out or u" + s.substring(unexpectedVersionIndex + 1); } final ResponseLineIterator iterator = new ResponseLineIterator(s); while (iterator.hasNext()) { final String line = iterator.next(); if (line.startsWith("[SWF]") || line.startsWith("[UnloadSWF]")) { if (!FilterSwfLoadUnloadMessagesAction.isFilterEnabled(getSession().getProject())) { myConsoleView.print(line + "\n", ConsoleViewContentType.SYSTEM_OUTPUT); } } else { myConsoleView.print(line + "\n", ConsoleViewContentType.SYSTEM_OUTPUT); } } if (s.contains("Another Flash debugger is probably running")) { reportProblem(s); getProcessHandler().detachProcess(); return CommandOutputProcessingMode.DONE; } if (s.contains("Failed to connect") || s.contains("unexpected version of the Flash Player") || s.contains("Connection refused")) { reportProblem(s); handleProbablyUnexpectedStop(s); return CommandOutputProcessingMode.DONE; } if (s.contains(WAITING_PLAYER_MARKER_1) || s.contains(WAITING_PLAYER_MARKER_2)) { fdbWaitingForPlayerStateReached = true; getSession().rebuildViews(); notifyFdbWaitingForPlayerStateReached(); try { launchDebuggedApplication(); } catch (IOException e) { reportProblem(s); handleProbablyUnexpectedStop(s); return CommandOutputProcessingMode.DONE; } } else { startupDone = (s.contains("Player connected; session starting.")); if (startupDone) { final Balloon balloon = ToolWindowManager.getInstance(getSession().getProject()).getToolWindowBalloon(ToolWindowId.DEBUG); if (balloon != null) { ApplicationManager.getApplication().invokeLater(() -> balloon.hide()); } getSession().rebuildViews(); return CommandOutputProcessingMode.DONE; } } return CommandOutputProcessingMode.PROCEEDING; } void launchDebuggedApplication() throws IOException { } } private class SuspendResumeDebuggerCommand extends SuspendDebuggerCommand { public SuspendResumeDebuggerCommand(final DebuggerCommand command1) { super(command1); } @Override protected void doCommandAfterSuspend() { if (!(myCommand1 instanceof QuitCommand)) insertCommand(new ContinueCommand()); super.doCommandAfterSuspend(); } } private class SuspendDebuggerCommand extends DebuggerCommand { protected final DebuggerCommand myCommand1; public SuspendDebuggerCommand(final DebuggerCommand command1) { super("suspend", CommandOutputProcessingType.SPECIAL_PROCESSING, VMState.RUNNING, VMState.SUSPENDED); myCommand1 = command1; } @Override CommandOutputProcessingMode onTextAvailable(@NonNls final String s) { insertCommand(new DebuggerCommand("y", CommandOutputProcessingType.SPECIAL_PROCESSING) { @Override CommandOutputProcessingMode onTextAvailable(@NonNls final String s) { doCommandAfterSuspend(); return CommandOutputProcessingMode.DONE; } }); return CommandOutputProcessingMode.DONE; } protected void doCommandAfterSuspend() { insertCommand(myCommand1); } } private class DumpOutputCommand extends DebuggerCommand { DumpOutputCommand() { super("dump", CommandOutputProcessingType.DEFAULT_PROCESSING, VMState.RUNNING, VMState.RUNNING); } @Override public void post(final FlexDebugProcess flexDebugProcess) throws IOException { scheduleOutputReading(); } } @Override public XSmartStepIntoHandler<?> getSmartStepIntoHandler() { return new FlexSmartStepIntoHandler(this); } @Override public void registerAdditionalActions(@NotNull final DefaultActionGroup leftToolbar, @NotNull final DefaultActionGroup topToolbar, @NotNull DefaultActionGroup settings) { topToolbar.addAction(ActionManager.getInstance().getAction("Flex.Debugger.FilterSwfLoadUnloadMessages")); } }