com.intellij.plugins.haxe.runner.debugger.HaxeDebugRunner.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.plugins.haxe.runner.debugger.HaxeDebugRunner.java

Source

/*
 * Copyright 2000-2013 JetBrains s.r.o.
 * Copyright 2014-2014 AS3Boyan
 * Copyright 2014-2014 Elias Ku
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.plugins.haxe.runner.debugger;

import com.intellij.execution.ExecutionException;
import com.intellij.execution.ExecutionResult;
import com.intellij.execution.Executor;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.configurations.RunProfileState;
import com.intellij.execution.executors.DefaultDebugExecutor;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.runners.DefaultProgramRunner;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.ui.ExecutionConsole;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.icons.AllIcons;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VFileProperty;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.plugins.haxe.HaxeBundle;
import com.intellij.plugins.haxe.config.HaxeTarget;
import com.intellij.plugins.haxe.config.NMETarget;
import com.intellij.plugins.haxe.config.OpenFLTarget;
import com.intellij.plugins.haxe.haxelib.HaxelibClasspathUtils;
import com.intellij.plugins.haxe.ide.module.HaxeModuleSettings;
import com.intellij.plugins.haxe.runner.HaxeApplicationConfiguration;
import com.intellij.plugins.haxe.runner.NMERunningState;
import com.intellij.plugins.haxe.runner.OpenFLRunningState;
import com.intellij.plugins.haxe.util.HaxeResolveUtil;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.ui.ColoredTextContainer;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.util.concurrency.QueueProcessor;
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.evaluation.XDebuggerEvaluator;
import com.intellij.xdebugger.frame.*;
import com.intellij.xdebugger.impl.XSourcePositionImpl;
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
import gnu.trove.THashSet;
import haxe.root.JavaProtocol;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author: Fedor.Korotkov
 * @author: Bryan Ischo <bji@tivo.com>
 * <p/>
 * This is the singular Haxe debug runner, that can debug:
 * 1. Flash targets
 * 2. Hxcpp targets, run locally by the IDE
 * 3. Hxcpp targets, run by an external command
 */
public class HaxeDebugRunner extends DefaultProgramRunner {
    public static final String HAXE_DEBUG_RUNNER_ID = "HaxeDebugRunner";

    @NotNull
    @Override
    public String getRunnerId() {
        return HAXE_DEBUG_RUNNER_ID;
    }

    @Override
    public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
        return (DefaultDebugExecutor.EXECUTOR_ID.equals(executorId)
                && (profile instanceof HaxeApplicationConfiguration));
    }

    @Override
    protected RunContentDescriptor doExecute(RunProfileState state, ExecutionEnvironment env)
            throws ExecutionException {
        final HaxeApplicationConfiguration configuration = (HaxeApplicationConfiguration) (env.getRunProfile());
        final Module module = configuration.getConfigurationModule().getModule();
        final Executor executor = env.getExecutor();

        if (module == null) {
            throw new ExecutionException(
                    HaxeBundle.message("no.module.for.run.configuration", configuration.getName()));
        }

        final HaxeModuleSettings settings = HaxeModuleSettings.getInstance(module);

        boolean flashDebug = false, hxcppDebug = false;

        if (settings.isUseHxmlToBuild()) {
            if (settings.getHaxeTarget() == HaxeTarget.FLASH) {
                flashDebug = true;
            } else if (settings.getHaxeTarget() == HaxeTarget.CPP) {
                hxcppDebug = true;
            }
        } else if (settings.isUseNmmlToBuild()) {
            NMETarget target = settings.getNmeTarget();
            if (target == NMETarget.FLASH) {
                flashDebug = true;
            } else if ((target == NMETarget.IOS) || (target == NMETarget.ANDROID) || (target == NMETarget.WINDOWS)
                    || (target == NMETarget.MAC) || (target == NMETarget.LINUX) || (target == NMETarget.LINUX64)) {
                hxcppDebug = true;
            }
        } else if (settings.isUseOpenFLToBuild()) {
            OpenFLTarget target = settings.getOpenFLTarget();
            if (target == OpenFLTarget.FLASH) {
                flashDebug = true;
            } else if ((target == OpenFLTarget.IOS) || (target == OpenFLTarget.ANDROID)
                    || (target == OpenFLTarget.WINDOWS) || (target == OpenFLTarget.MAC)
                    || (target == OpenFLTarget.LINUX) || (target == OpenFLTarget.LINUX64)) {
                hxcppDebug = true;
            }
        }

        if (flashDebug) {
            return runFlash(module, settings, env, executor, configuration.getCustomFileToLaunchPath());
        } else if (hxcppDebug) {
            final Project project = env.getProject();
            return runHxcpp(project, module, settings, env, executor, configuration.getCustomDebugPort(),
                    configuration.isCustomRemoteDebugging());
        } else {
            throw new ExecutionException(HaxeBundle.message("haxe.proper.debug.targets"));
        }
    }

    private RunContentDescriptor runFlash(final Module module, final HaxeModuleSettings settings,
            final ExecutionEnvironment env, final Executor executor, final String launchPath)
            throws ExecutionException {
        final IdeaPluginDescriptor plugin = PluginManager.getPlugin(PluginId.getId("com.intellij.flex"));
        if (plugin == null) {
            throw new ExecutionException(HaxeBundle.message("install.flex.plugin"));
        }
        if (!plugin.isEnabled()) {
            throw new ExecutionException(HaxeBundle.message("enable.flex.plugin"));
        }

        String flexSdkName = settings.getFlexSdkName();
        if (StringUtil.isEmpty(flexSdkName)) {
            throw new ExecutionException(HaxeBundle.message("flex.sdk.not.specified"));
        }

        if (settings.isUseNmmlToBuild()) {
            return HaxeFlashDebuggingUtil.getNMEDescriptor(this, module, env, executor, flexSdkName);
        } else if (settings.isUseOpenFLToBuild()) {
            return HaxeFlashDebuggingUtil.getOpenFLDescriptor(this, module, env, executor, flexSdkName);
        } else {
            return HaxeFlashDebuggingUtil.getDescriptor(module, env, launchPath, flexSdkName);
        }
    }

    private RunContentDescriptor runHxcpp(final Project project, final Module module,
            final HaxeModuleSettings settings, final ExecutionEnvironment env, final Executor executor,
            final int port, final boolean remoteDebugging) throws ExecutionException {
        final XDebugSession debugSession = XDebuggerManager.getInstance(project).startSession(env,
                new XDebugProcessStarter() {
                    @NotNull
                    public XDebugProcess start(@NotNull final XDebugSession session) throws ExecutionException {
                        try {
                            // Start the debugger process, which is a class that
                            // implements the actual debugger functionality.  In this
                            // case, it does so by message passing through a socket.
                            final DebugProcess debugProcess = new DebugProcess(session, project, module, port);

                            // If using remote debugging, emit a console message
                            // indicating that the debugger is waiting for the remote
                            // process to start.
                            if (remoteDebugging) {
                                showInfoMessage(project,
                                        "Listening for debugged process " + "on port " + port
                                                + " ... Press OK after " + "remote debugged process has started.",
                                        "Haxe Debugger");
                            }
                            // Else, start the being-debugged process and make the
                            // local debug process instance aware of it.
                            else {
                                if (settings.isUseOpenFLToBuild()) {
                                    debugProcess.setExecutionResult(new OpenFLRunningState(env, module,
                                            // runInTest if android or ios
                                            ((settings.getOpenFLTarget() == OpenFLTarget.ANDROID)
                                                    || (settings.getOpenFLTarget() == OpenFLTarget.IOS)),
                                            true, port).execute(executor, HaxeDebugRunner.this));
                                } else {
                                    debugProcess.setExecutionResult(new NMERunningState(env, module,
                                            // runInTest if android or ios
                                            ((settings.getNmeTarget() == NMETarget.ANDROID)
                                                    || (settings.getNmeTarget() == NMETarget.IOS)),
                                            true, port).execute(executor, HaxeDebugRunner.this));
                                }
                            }

                            // Now accept the connection from the being-debugged
                            // process.
                            debugProcess.start();

                            return debugProcess;
                        } catch (IOException e) {
                            throw new ExecutionException(e.getMessage(), e);
                        }
                    }
                });

        return debugSession.getRunContentDescriptor();
    }

    private class DebugProcess extends XDebugProcess {
        public DebugProcess(@NotNull XDebugSession session, Project project, Module module, int port)
                throws IOException {
            super(session);
            mClassesWithStatics = new Vector<String>();
            mProject = project;
            mModule = module;
            mDeferredQueue = new LinkedList<Pair<debugger.Command, MessageListener>>();
            mListenerQueue = new LinkedList<MessageListener>();
            mServerSocket = new java.net.ServerSocket(port);
            mBreakpointHandlers = this.createBreakpointHandlers();
            mMap = new HashMap<XLineBreakpoint<XBreakpointProperties>, Integer>();

            mWriteQueue = QueueProcessor.createRunnableQueueProcessor(QueueProcessor.ThreadToUse.POOLED);
        }

        public void setExecutionResult(ExecutionResult executionResult) {
            mExecutionResult = executionResult;
        }

        public void start() {
            ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
                public void run() {
                    try {
                        DebugProcess.this.readLoop();
                    } catch (final Throwable t) {
                        SwingUtilities.invokeLater(new Runnable() {
                            public void run() {
                                DebugProcess.this.error("Debugging loop failed: " + t);
                            }
                        });
                    }
                }
            });
        }

        @Override
        protected ProcessHandler doGetProcessHandler() {
            return ((mExecutionResult == null) ? null : mExecutionResult.getProcessHandler());
        }

        @Override
        @NotNull
        public ExecutionConsole createConsole() {
            return ((mExecutionResult == null) ? super.createConsole() : mExecutionResult.getExecutionConsole());
        }

        @Override
        @NotNull
        public XBreakpointHandler<?>[] getBreakpointHandlers() {
            return mBreakpointHandlers;
        }

        @Override
        @NotNull
        public XDebuggerEditorsProvider getEditorsProvider() {
            return new HaxeDebuggerEditorsProvider();
        }

        @Override
        public void startPausing() {
            this.expectOK(debugger.Command.BreakNow);
        }

        @Override
        public void resume() {
            this.expectOK(debugger.Command.Continue(1));
        }

        @Override
        public void startStepOver() {
            this.expectOK(debugger.Command.Next(1));
        }

        @Override
        public void startStepInto() {
            this.expectOK(debugger.Command.Step(1));
        }

        @Override
        public void startStepOut() {
            this.expectOK(debugger.Command.Finish(1));
        }

        @Override
        public void stop() {
            synchronized (this) {
                if (mServerSocket != null) {
                    try {
                        mServerSocket.close();
                        mServerSocket = null;
                    } catch (IOException e) {
                    }
                }
                if (mDebugSocket != null) {
                    try {
                        mDebugSocket.close();
                        mDebugSocket = null;
                    } catch (IOException e) {
                    }
                }
                // Stop the write queue. Otherwise we get a bunch of pointless dialogs.
                mWriteQueue.dismissLastTasks(0);
            }
        }

        @Override
        public void runToPosition(@NotNull XSourcePosition position) {
            // Complicated!  Basically, just make sure that there is a single
            // enabled breakpoint at [position], and then when [position] is
            // hit, set breakpoints back to how they were before ... but ...
            // what about breaking and setting breakpoints and stuff while
            // waiting on runToPosition?  Have to be clever there ...
        }

        private void info(String message) {
            showInfoMessage(mProject, message, "Haxe Debugger");
        }

        private void warn(String message) {
            showInfoMessage(mProject, message, "Haxe Debugger Warning");
        }

        private void error(String message) {
            showInfoMessage(mProject, message, "Haxe Debugger Error");
            this.stop();
        }

        private void expectOK(debugger.Command command) {
            this.enqueueCommand(command, new MessageListener() {
                public void handleMessage(int messageId, debugger.Message message) {
                    if (messageId != JavaProtocol.IdOK) {
                        DebugProcess.this.error("Debugger protocol error: expected OK, but got: "
                                + JavaProtocol.messageToString(message));
                    }
                }
            });
        }

        private void where() {
            this.enqueueCommand(debugger.Command.WhereCurrentThread(false), new MessageListener() {
                public void handleMessage(int messageId, debugger.Message message) {
                    if (messageId != JavaProtocol.IdThreadsWhere) {
                        DebugProcess.this.error("Debugger protocol error: expected " + "IdThreadsWhere, but got: "
                                + JavaProtocol.messageToString(message));
                        return;
                    }
                    getSession().positionReached(
                            new SuspendContext(DebugProcess.this.mProject, DebugProcess.this.mModule, message));
                }
            });
        }

        private void enqueueCommand(final debugger.Command command, MessageListener listener) {
            //            System.out.println("Writing command: " +
            //                               JavaProtocol.commandToString(command));
            try {
                synchronized (this) {
                    if (mDebugSocket == null) {
                        mDeferredQueue.add(Pair.create(command, listener));
                        return;
                    }
                    mListenerQueue.add(listener);
                    final OutputStream os = mDebugSocket.getOutputStream();
                    mWriteQueue.add(new Runnable() {
                        public void run() {
                            try {
                                JavaProtocol.writeCommand(os, command);
                            } catch (RuntimeException e) {
                                DebugProcess.this.error("Debugger protocol error: exception while writing "
                                        + "command " + JavaProtocol.commandToString(command) + ": " + e);
                            }
                        }
                    });
                }
            } catch (IOException e) {
                DebugProcess.this.error("Debugger error: exception queueing write " + "command "
                        + JavaProtocol.commandToString(command) + ": " + e);
            }
        }

        private void readLoop() throws IOException {
            java.net.ServerSocket serverSocket;
            synchronized (this) {
                serverSocket = mServerSocket;
            }
            // Don't synchronize around the accept.  It locks up the rest of the debugger still
            // running on the AWT thread if the application isn't starting correctly.
            java.net.Socket debugSocket = serverSocket.accept();
            synchronized (this) {
                mDebugSocket = debugSocket;
                mServerSocket.close();
                mServerSocket = null;
                JavaProtocol.readClientIdentification(mDebugSocket.getInputStream());
                // XXX: Put this on the write thread/queue, instead of just posting it?
                JavaProtocol.writeServerIdentification(mDebugSocket.getOutputStream());
                // Enqueue a classList callback to populate the class list
                this.enqueueCommand(debugger.Command.Classes(null), new MessageListener() {
                    public void handleMessage(int messageId, debugger.Message message) {
                        if (messageId == JavaProtocol.IdClasses) {
                            DebugProcess.this.handlePartialClassList((debugger.ClassList) message.params.__a[0]);
                        }
                    }
                });
            }
            while (true) {
                synchronized (this) {
                    debugSocket = mDebugSocket;
                }
                if (debugSocket == null) {
                    break;
                }
                debugger.Message message = JavaProtocol.readMessage(debugSocket.getInputStream());
                //                System.out.println("Received message: " +
                //                                   JavaProtocol.messageToString(message));
                int messageId = JavaProtocol.getMessageId(message);
                if (messageId == JavaProtocol.IdThreadCreated) {
                    // Console it out
                } else if (messageId == JavaProtocol.IdThreadTerminated) {
                    // Console it out
                } else if (messageId == JavaProtocol.IdThreadStarted) {
                    // Console it out
                } else if (messageId == JavaProtocol.IdThreadStopped) {
                    if (mStoppedOnce) {
                        // Send a where to solicit current thread stack frame
                        this.where();
                    } else {
                        mStoppedOnce = true;
                        while (!mDeferredQueue.isEmpty()) {
                            Pair<debugger.Command, MessageListener> p = mDeferredQueue.removeFirst();
                            this.enqueueCommand(p.getFirst(), p.getSecond());
                        }
                        this.resume();
                    }
                } else {
                    MessageListener listener = null;
                    synchronized (this) {
                        if (!mListenerQueue.isEmpty()) {
                            listener = mListenerQueue.removeFirst();
                        }
                    }
                    if (listener == null) {
                        DebugProcess.this.error("Debugger protocol error: unsolicited response: "
                                + JavaProtocol.messageToString(message));
                        break;
                    } else {
                        listener.handleMessage(messageId, message);
                    }
                }
            }
        }

        private void handlePartialClassList(debugger.ClassList classList) {
            while (true) {
                if (classList.index == 0) {
                    // Terminator
                    break;
                }
                if (classList.index == 1) {
                    // Continued
                    this.enqueueCommand(debugger.Command.Classes((String) classList.params.__a[0]),
                            new MessageListener() {
                                public void handleMessage(int messageId, debugger.Message message) {
                                    if (messageId == JavaProtocol.IdClasses) {
                                        DebugProcess.this
                                                .handlePartialClassList((debugger.ClassList) message.params.__a[0]);
                                    } else {
                                        throw new RuntimeException(
                                                "Unexpected message in response " + "to class list request: "
                                                        + JavaProtocol.messageToString(message));
                                    }
                                }
                            });
                    break;
                }
                // Element
                if (((Boolean) classList.params.__a[1]).booleanValue()) {
                    mClassesWithStatics.addElement((String) classList.params.__a[0]);
                }
                classList = (debugger.ClassList) classList.params.__a[2];
            }
        }

        private void registerBreakpoint(@NotNull final XLineBreakpoint<XBreakpointProperties> breakpoint) {
            final XSourcePosition position = breakpoint.getSourcePosition();
            if (position == null) {
                return;
            }

            String path = getRelativePath(mProject, position.getFile());

            DebugProcess.this.enqueueCommand(debugger.Command.AddFileLineBreakpoint(path, position.getLine() + 1),
                    new MessageListener() {
                        public void handleMessage(int messageId, debugger.Message message) {
                            if (messageId == JavaProtocol.IdFileLineBreakpointNumber) {
                                mMap.put(breakpoint, (Integer) (message.params.__a[0]));
                            } else {
                                getSession().updateBreakpointPresentation(breakpoint,
                                        AllIcons.Debugger.Db_invalid_breakpoint, null);
                                DebugProcess.this.warn("Cannot set breakpoint");
                            }
                        }
                    });
        }

        private void unregisterBreakpoint(@NotNull final XLineBreakpoint<XBreakpointProperties> breakpoint,
                final boolean temporary) {
            if (!mMap.containsKey(breakpoint)) {
                return;
            }

            int id = mMap.remove(breakpoint);
            DebugProcess.this.enqueueCommand(debugger.Command.DeleteBreakpointRange(id, id), new MessageListener() {
                public void handleMessage(int messageId, debugger.Message message) {
                    // Could verify that the response was Deleted ...
                }
            });
        }

        private XBreakpointHandler<?>[] createBreakpointHandlers() {
            return new XBreakpointHandler<?>[] {
                    new XBreakpointHandler<XLineBreakpoint<XBreakpointProperties>>(HaxeBreakpointType.class) {
                        public void registerBreakpoint(
                                @NotNull final XLineBreakpoint<XBreakpointProperties> breakpoint) {
                            DebugProcess.this.registerBreakpoint(breakpoint);
                        }

                        public void unregisterBreakpoint(
                                @NotNull final XLineBreakpoint<XBreakpointProperties> breakpoint,
                                final boolean temporary) {
                            DebugProcess.this.unregisterBreakpoint(breakpoint, temporary);
                        }
                    } };
        }

        private abstract class MessageListener {
            public abstract void handleMessage(int messageId, debugger.Message message);
        }

        private class SuspendContext extends XSuspendContext {
            public SuspendContext(Project project, Module module, debugger.Message threadsWhereMessages) {
                mExecutionStacks = this
                        .buildWhereList(project, module,
                                (debugger.ThreadWhereList) threadsWhereMessages.params.__a[0])
                        .toArray(new XExecutionStack[0]);
            }

            public XExecutionStack getActiveExecutionStack() {
                return ((mExecutionStacks.length > 0) ? mExecutionStacks[0] : null);
            }

            public XExecutionStack[] getExecutionStacks() {
                return mExecutionStacks;
            }

            private Vector<XExecutionStack> buildWhereList(Project project, Module module,
                    debugger.ThreadWhereList whereList) {
                Vector<XExecutionStack> executionStacks = new Vector<XExecutionStack>();

                this.addWhereList(project, module, executionStacks, whereList);

                return executionStacks;
            }

            private void addWhereList(Project project, Module module, Vector<XExecutionStack> executionStacks,
                    debugger.ThreadWhereList whereList) {
                if (whereList == debugger.ThreadWhereList.Terminator) {
                    return;
                }

                int number = ((Integer) whereList.params.__a[0]).intValue();
                debugger.ThreadStatus status = (debugger.ThreadStatus) whereList.params.__a[1];
                debugger.FrameList frameList = (debugger.FrameList) whereList.params.__a[2];

                executionStacks.addElement(new ExecutionStack(project, module, number, frameList));

                this.addWhereList(project, module, executionStacks,
                        (debugger.ThreadWhereList) whereList.params.__a[3]);
            }

            private XExecutionStack[] mExecutionStacks;
        }

        private class ExecutionStack extends XExecutionStack {
            public ExecutionStack(Project project, Module module, int number, debugger.FrameList frameList) {
                super("Thread " + number);

                mStackFrames = new Vector<XStackFrame>();

                this.addFrameList(project, module, frameList);
            }

            public XStackFrame getTopFrame() {
                return ((mStackFrames.size() > 0) ? mStackFrames.elementAt(0) : null);
            }

            public void computeStackFrames(int firstFrameIndex, XStackFrameContainer container) {
                if (firstFrameIndex < mStackFrames.size()) {
                    container.addStackFrames(mStackFrames.subList(firstFrameIndex, mStackFrames.size() - 1), true);
                }
            }

            private void addFrameList(Project project, Module module, debugger.FrameList frameList) {
                if (frameList == debugger.FrameList.Terminator) {
                    return;
                }

                mStackFrames.addElement(new StackFrame(project, module, frameList));

                this.addFrameList(project, module, (debugger.FrameList) frameList.params.__a[6]);
            }

            private Vector<XStackFrame> mStackFrames;
        }

        private class StackFrame extends XStackFrame {
            public StackFrame(Project project, Module module, debugger.FrameList frameList) {
                mFrameNumber = (Integer) frameList.params.__a[1];
                mFileName = (String) frameList.params.__a[4];
                mLineNumber = (((Integer) frameList.params.__a[5]).intValue());
                mClassAndFunctionName = ((String) frameList.params.__a[2] + "." + (String) frameList.params.__a[3]);
                VirtualFile file = null;

                String fileName = VfsUtil.extractFileName(mFileName);
                if (fileName == null) {
                    fileName = mFileName;
                }
                java.util.Collection<VirtualFile> files = FilenameIndex.getVirtualFilesByName(project, fileName,
                        GlobalSearchScope.moduleScope(module));
                if (files.isEmpty()) {
                    files = FilenameIndex.getVirtualFilesByName(project, fileName,
                            GlobalSearchScope.allScope(project));
                }

                java.util.Collection<VirtualFile> matches = new THashSet<VirtualFile>();
                if (!files.isEmpty()) {
                    for (VirtualFile f : files) {
                        if (f.getPath().endsWith(mFileName)) {
                            matches.add(f);
                        }
                    }
                }
                if (matches.isEmpty()) {
                    // If we don't have a match yet, then walk the classpath looking for
                    // an appropriate file.
                    file = HaxelibClasspathUtils.findFileOnClasspath(module, mFileName);
                } else if (matches.size() == 1) {
                    // Got one.  If it's a good file, keep it.  Otherwise, try to pick it
                    // out of the classpath.
                    VirtualFile possible = matches.iterator().next();
                    file = possible.isValid() ? possible
                            : HaxelibClasspathUtils.findFileOnClasspath(module, possible.toString());
                } else {
                    // Too many matches. Get the first that occurs on the classpath.
                    file = HaxelibClasspathUtils.findFirstFileOnClasspath(module, matches);
                }

                // Now, work around the fact that IDEA treats symlinks as separate files.
                // XXX: This should be controlled via an UI option.
                if (null != file && file.is(VFileProperty.SYMLINK)) {
                    file = file.getCanonicalFile();
                }

                mSourcePosition = XSourcePositionImpl.create(file, mLineNumber - 1);
            }

            public Object getEqualityObject() {
                return (mFileName + mClassAndFunctionName).intern();
            }

            public XDebuggerEvaluator getEvaluator() {
                return new XDebuggerEvaluator() {
                    public void evaluate(@NotNull String expression, @NotNull XEvaluationCallback callback,
                            XSourcePosition expressionPosition) {
                        callback.evaluated(new Value(expression));
                    }
                };
            }

            public XSourcePosition getSourcePosition() {
                return mSourcePosition;
            }

            @Override
            public void computeChildren(@NotNull final XCompositeNode node) {
                // Move to the stack frame
                DebugProcess.this.enqueueCommand(debugger.Command.SetFrame(mFrameNumber), new MessageListener() {
                    public void handleMessage(int messageId, debugger.Message message) {
                        if (messageId == JavaProtocol.IdThreadLocation) {
                            StackFrame.this.computeChildrenCurrentFrame(node);
                        } else {
                            DebugProcess.this.warn("Failed to set stack frame to " + mFrameNumber
                                    + "; got message; " + JavaProtocol.messageToString(message));
                        }
                    }
                    // Get var names
                });
            }

            public void customizePresentation(@NotNull ColoredTextContainer component) {
                SimpleTextAttributes attr = (mSourcePosition == null) ? SimpleTextAttributes.GRAYED_ATTRIBUTES
                        : SimpleTextAttributes.REGULAR_ATTRIBUTES;

                component.append(mClassAndFunctionName + "  [" + mFileName + ":" + mLineNumber + "]", attr);
                component.setIcon(AllIcons.Debugger.StackFrame);
            }

            private void computeChildrenCurrentFrame(@NotNull final XCompositeNode node) {
                DebugProcess.this.enqueueCommand(debugger.Command.Variables(false), new MessageListener() {
                    public void handleMessage(int messageId, debugger.Message message) {
                        if (messageId == JavaProtocol.IdVariables) {
                            XValueChildrenList childrenList = new XValueChildrenList();
                            debugger.StringList stringList = (debugger.StringList) message.params.__a[0];
                            addChildren(childrenList, stringList);
                            if (true) {
                                node.addChildren(childrenList, true);
                            } else {
                                // Note: Removed because it cluttered the variable list.
                                //       It's a candidate for reinstatement, possibly with
                                //       a control variable.
                                node.addChildren(childrenList, false);
                                // Add all statics to the list of variables.
                                addStaticChildren(node);
                            }
                        } else {
                            DebugProcess.this.warn("Failed to get variables; got message "
                                    + JavaProtocol.messageToString(message));
                        }
                    }
                });
            }

            private void addChildren(XValueChildrenList childrenList, debugger.StringList stringList) {
                while (true) {
                    if (stringList == debugger.StringList.Terminator) {
                        break;
                    }

                    String string = (String) stringList.params.__a[0];
                    if (!isIntermediateVariableName(string)) {
                        childrenList.add(string, new Value(string));
                    }

                    stringList = (debugger.StringList) stringList.params.__a[1];
                }
            }

            /** Determines whether a variable name has been introduced by
             * the compiler's target backend (e.g. hxcpp).
             */
            private boolean isIntermediateVariableName(String s) {
                return s.startsWith("_g");
            }

            private void addStaticChildren(@NotNull final XCompositeNode node) {
                XValueChildrenList childrenList = new XValueChildrenList();

                for (String c : DebugProcess.this.mClassesWithStatics) {
                    childrenList.add("statics of " + c, new Value(c, true));
                }
                node.addChildren(childrenList, true);
            }

            private class Value extends XValue {
                public Value(String name) {
                    mName = name;
                    mExpression = name;
                }

                public Value(String name, boolean isClassStatics) {
                    if (!isClassStatics) {
                        mName = name;
                        mExpression = name;
                        return;
                    }

                    // Indirect so that the value is not fetched immediately
                    // by the UI - the user has to click to see the statics,
                    // otherwise, all statics of all classes would be fetched
                    // on every breakpoint which would suck
                    mName = "statics of " + name;
                    mExpression = name;
                    mIcon = AllIcons.Debugger.Value;
                    mType = "";
                    mValue = "";
                    mChildren = new LinkedList<Value>();
                    mChildren.add(new Value(name));
                }

                public void computePresentation(@NotNull XValueNode node, @NotNull XValuePlace place) {
                    // If no icon has been calculated, then the value must be
                    // fetched
                    if (mIcon == null) {
                        this.fetchValue(node, place);
                        return;
                    }

                    setPresentation(node);
                }

                private void setPresentation(@NotNull XValueNode node) {
                    node.setPresentation(mIcon, mType, mValue, (mChildren != null));
                }

                // getModifierPsi() is temporarily disabled as it does not work
                // due to PSI errors in the haxe PSI tree.
                //                public XValueModifier getModifierPsi()
                //                {
                //                    return new XValueModifier()
                //                    {
                //                        public void setValue(@NotNull String expression,
                //                                   @NotNull final XModificationCallback callback)
                //                        {
                //                            System.out.println("Setting value of " +
                //                                               Value.this.mName + " with " +
                //                                               "expression " +
                //                                               Value.this.mExpression  + 
                //                                               " to " + expression);
                //                            DebugProcess.this.enqueueCommand
                //                                (debugger.Command.SetExpression
                //                                 (false, mExpression, expression),
                //                                 new MessageListener()
                //                            {
                //                                public void handleMessage(int messageId,
                //                                                       debugger.Message message)
                //                                {
                //                                    // Just indicate that the value was
                //                                    // modified, it may end up being the same
                //                                    // value if the set expression failed
                //                                    callback.valueModified();
                //                                }
                //                            });
                //                        }
                //
                //                        public String getInitialValueEditorText()
                //                        {
                //                            System.out.println("Getting initial value of " +
                //                                               Value.this.mName + " as " +
                //                                               Value.this.mValue);
                //                            return Value.this.mValue;
                //                        }
                //                    };
                //                }

                public String getEvaluationExpression() {
                    return mExpression;
                }

                @Override
                public boolean canNavigateToSource() {
                    return false;
                }

                @Override
                public boolean canNavigateToTypeSource() {
                    // XXX todo -- implement source navigation for class types
                    return false;
                }

                @Override
                public void computeChildren(@NotNull final XCompositeNode node) {
                    internalComputeChildren(node);
                }

                private void internalComputeChildren(@NotNull Obsolescent node) {
                    // mWaitingforChildrenResults tells us wether a request for
                    // the children is already in flight.  In the case that one
                    // is, we do NOT want to call node.addChildren(list,true),
                    // because that tells the UI that we have computed all of the
                    // children for this node.
                    //
                    // mChildrenComputationRequested notifies the fetchValue
                    // callback that the UI has attempted to draw any elements
                    // and won't request them again, so the callback should
                    // notify the UI that new children are available (by calling
                    // this function again).

                    if (mChildren == null || mWaitingForChildrenResults) {
                        mChildrenComputationRequested = true;
                        return;
                    }
                    mChildrenComputationRequested = false;

                    XValueChildrenList childrenList = new XValueChildrenList(mChildren.size());
                    for (Value child : mChildren) {
                        childrenList.add(child.mName, child);
                    }

                    // Stupid hack to get around the fact that we're abusing the APIs.
                    if (node instanceof XValueNodeImpl) {
                        ((XValueNodeImpl) node).addChildren(childrenList, true);
                    } else if (node instanceof XCompositeNode) {
                        ((XCompositeNode) node).addChildren(childrenList, true);
                    } else {
                        error("Unexpected node type in debugger screen: " + node.getClass().toString());
                    }
                }

                private void fetchValue(@NotNull final XValueNode node, @NotNull final XValuePlace place) {
                    mWaitingForChildrenResults = true;
                    DebugProcess.this.enqueueCommand(debugger.Command.GetStructured(false, mExpression),
                            new MessageListener() {
                                public void handleMessage(int messageId, debugger.Message message) {
                                    if (messageId == JavaProtocol.IdStructured) {
                                        debugger.StructuredValue structuredValue = (debugger.StructuredValue) message.params.__a[0];
                                        Value.this.fromStructuredValue(structuredValue);
                                    } else {
                                        mIcon = AllIcons.General.Error;
                                        mValue = mType = "<Unavailable - " + getErrorString(message) + ">";
                                    }

                                    // If fromStructuredValue contained a list, the nodes
                                    // need to be added to the UI.  The UI usually requests
                                    // them via computeChildren().  In some cases,
                                    // computeChildren() is called before we have retrieved
                                    // the results, and we need to re-trigger the computation.
                                    mWaitingForChildrenResults = false;
                                    if (mChildrenComputationRequested) {
                                        Value.this.internalComputeChildren(node);
                                    }

                                    Value.this.setPresentation(node);
                                }
                            });
                }

                private void fromStructuredValue(debugger.StructuredValue structuredValue) {
                    if (structuredValue.index == 0) { // Elided
                        mExpression = (String) structuredValue.params.__a[1];
                    } else if (structuredValue.index == 1) { // Single
                        debugger.StructuredValueType type = (debugger.StructuredValueType) structuredValue.params.__a[0];
                        String value = (String) structuredValue.params.__a[1];
                        mIcon = AllIcons.Debugger.Value;
                        mType = getTypeString(type);
                        mValue = stripErrorAdornments(value);
                    } else if (structuredValue.index == 2) { // List
                        debugger.StructuredValueListType type = (debugger.StructuredValueListType) structuredValue.params.__a[0];
                        debugger.StructuredValueList list = (debugger.StructuredValueList) structuredValue.params.__a[1];
                        mIcon = AllIcons.Debugger.Value;
                        mType = getTypeString(type);
                        mValue = "";
                        mChildren = new LinkedList<Value>();
                        this.addChildren(list);
                    }
                    // Anything else, including Elided, is an error
                    else {
                        mIcon = AllIcons.General.Error;
                        mValue = mType = "<Unavailable>";
                    }
                }

                private void addChildren(debugger.StructuredValueList list) {
                    if (list == debugger.StructuredValueList.Terminator) {
                        return;
                    }

                    String name = (String) list.params.__a[0];
                    debugger.StructuredValue structuredValue = (debugger.StructuredValue) list.params.__a[1];
                    debugger.StructuredValueList next = (debugger.StructuredValueList) list.params.__a[2];

                    Value val = new Value(name);
                    val.fromStructuredValue(structuredValue);
                    mChildren.add(val);

                    addChildren(next);
                }

                private String getTypeString(debugger.StructuredValueType type) {
                    if (type.index == 0) {
                        return "Null";
                    } else if (type.index == 1) {
                        return "Bool";
                    } else if (type.index == 2) {
                        return "Int";
                    } else if (type.index == 3) {
                        return "Float";
                    } else if (type.index == 4) {
                        return "String";
                    } else if (type.index == 5) {
                        return (String) type.params.__a[0];
                    } else if (type.index == 6) {
                        return (String) type.params.__a[0];
                    } else if (type.index == 7) {
                        return "{ ... }";
                    } else if (type.index == 8) {
                        return (String) type.params.__a[0];
                    } else if (type.index == 9) {
                        return "Function";
                    } else {
                        return "<Unavailable>";
                    }
                }

                private String getTypeString(debugger.StructuredValueListType type) {
                    if (type.index == 0) {
                        return "{ ... }";
                    } else if (type.index == 1) {
                        return (String) type.params.__a[0];
                    } else if (type.index == 2) {
                        return "Array";
                    } else {
                        return "<Unavailable>";
                    }
                }

                private String getErrorString(debugger.Message debugMessage) {
                    return stripErrorAdornments(debugMessage.toString());
                }

                private String stripErrorAdornments(String message) {

                    // Strip the enumeration text.
                    Pattern wrapperPattern = Pattern.compile("ErrorEvaluatingExpression\\((.*)\\)", Pattern.DOTALL);
                    Matcher m = wrapperPattern.matcher(message);
                    String description = m.matches() ? m.group(1) : message;

                    // Strip the call stack.
                    final String callStackMarker = "\nCalled from ";
                    if (description.contains(callStackMarker)) {
                        description = description.substring(0, description.indexOf(callStackMarker));
                    }

                    return description;
                }

                private String mName;
                private String mExpression;
                private javax.swing.Icon mIcon;
                private String mType;
                private String mValue;
                private LinkedList<Value> mChildren;

                // These two manage how/when the child nodes are fetched.
                // See internalComputeChildren for an explanation.
                private boolean mWaitingForChildrenResults;
                private boolean mChildrenComputationRequested;
            }

            private int mFrameNumber;
            private String mFileName;
            private int mLineNumber;
            private String mClassAndFunctionName;
            private XSourcePosition mSourcePosition;
        }

        private Vector<String> mClassesWithStatics;
        private Project mProject;
        private Module mModule;
        private boolean mStoppedOnce;
        private LinkedList<Pair<debugger.Command, MessageListener>> mDeferredQueue;
        private QueueProcessor<Runnable> mWriteQueue;
        private LinkedList<MessageListener> mListenerQueue;
        private java.net.ServerSocket mServerSocket;
        private java.net.Socket mDebugSocket;
        private ExecutionResult mExecutionResult;
        private XBreakpointHandler[] mBreakpointHandlers;
        private HashMap<XLineBreakpoint<XBreakpointProperties>, Integer> mMap;
    }

    private static String getRelativePath(Project project, VirtualFile file) {
        PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
        String packageName = HaxeResolveUtil.getPackageName(psiFile);
        String fileName = VfsUtil.extractFileName(file.getPath());
        return getPath(packageName, fileName);
    }

    private static String getPath(String packageName, String fileName) {
        if (StringUtil.isEmpty(packageName)) {
            return fileName;
        }

        return packageName.replaceAll("\\.", "/") + "/" + fileName;
    }

    private static void showInfoMessage(final Project project, final String message, final String title) {
        ApplicationManager.getApplication().invokeLater(new Runnable() {
            @Override
            public void run() {
                Messages.showInfoMessage(project, message, title);
            }
        });
    }
}