gt:     StringBuilder sb = new StringBuilder();
context: /**
   * Copyright (C) 2014  Eric Van Dewoestine
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  package org.eclim.plugin.jdt.command.debug.context;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import org.eclim.logging.Logger;
  
  import org.eclim.plugin.core.util.VimClient;
  
  import org.eclim.plugin.jdt.command.debug.event.DebugEventSetListener;
  
  import org.eclim.plugin.jdt.command.debug.ui.ThreadView;
  import org.eclim.plugin.jdt.command.debug.ui.VariableView;
  import org.eclim.plugin.jdt.command.debug.ui.ViewUtils;
  
  import org.eclim.plugin.jdt.util.JavaUtils;
  
  import org.eclim.util.CollectionUtils;
  
  import org.eclipse.core.resources.IProject;
  
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.IProgressMonitor;
  
  import org.eclipse.debug.core.DebugException;
  import org.eclipse.debug.core.DebugPlugin;
  import org.eclipse.debug.core.IDebugEventSetListener;
  import org.eclipse.debug.core.ILaunch;
  import org.eclipse.debug.core.ILaunchConfiguration;
  import org.eclipse.debug.core.ILaunchManager;
  import org.eclipse.debug.core.Launch;
  
  import org.eclipse.debug.core.model.IDebugTarget;
  import org.eclipse.debug.core.model.ISourceLocator;
  
  import org.eclipse.jdt.core.IJavaProject;
  import org.eclipse.jdt.core.JavaCore;
  
  import org.eclipse.jdt.internal.launching.SocketAttachConnector;
  
  import org.eclipse.jdt.launching.sourcelookup.JavaSourceLocator;
  
  /**
   * Maintains the state of currently active debug session and exposes methods to
   * interact with it.
   */
  public class DebuggerContext
  {
    private static final Logger logger = Logger.getLogger(DebuggerContext.class);
  
    private static final String KEY_HOSTNAME = "hostname";
    private static final String KEY_PORT = "port";
  
    private IProject project;
  
    private String host;
  
    private int port;
  
    /**
     * ID of this context obtained by concatenating user input.
     * This should be unique across all active contexts.
     */
    private String id;
  
    private volatile DebuggerState state = DebuggerState.CONNECTING;
  
    private final ThreadContext threadCtx;
    private final ThreadView threadView;
    private final VariableView varView;
  
    /**
     * Debug target for currently active session.
     */
    private IDebugTarget debugTarget;
  
    private VimClient vimClient;
  
    private IDebugEventSetListener listener = new DebugEventSetListener();
  
    /**
     * Starts the debug session by creating the debug target with given parameters.
     */
    public DebuggerContext(
        IProject project,
        String host,
        int port,
        String vimInstanceId)
      throws Exception
    {
      this.project = project;
      this.host = host;
      this.port = port;
      this.id = project.getName() + " - " + host + ":" + port;
      this.vimClient = new VimClient(vimInstanceId);
  
      this.threadCtx = new ThreadContext();
      this.threadView = new ThreadView(threadCtx);
      this.varView = new VariableView(threadCtx);
    }
  
    public void start()
      throws Exception
    {
      logger.info(
          "Starting debug session at " + host + ":" + port +
          " for vim instance: " + vimClient.getId());
  
      // Setup listener before connecting to VM so that events are not missed
      DebugPlugin.getDefault().addDebugEventListener(listener);
  
      ILaunchConfiguration config = null;
  
      ArrayList<IProject> projects = new ArrayList<IProject>();
      projects.add(project);
      CollectionUtils.addAll(projects, project.getReferencedProjects());
      CollectionUtils.addAll(projects, project.getReferencingProjects());
  
      ArrayList<IJavaProject> javaProjects = new ArrayList<IJavaProject>();
      for (IProject p : projects) {
        if (p.exists() && p.hasNature(JavaCore.NATURE_ID)){
          javaProjects.add(JavaUtils.getJavaProject(p));
        }
      }
      ISourceLocator srcLocator = new JavaSourceLocator(
          javaProjects.toArray(new IJavaProject[javaProjects.size()]), true);
  
      ILaunch launch = new Launch(config, ILaunchManager.DEBUG_MODE, srcLocator);
      IProgressMonitor monitor = null;
      Map<String, String> args = new HashMap<String, String>();
      args.put(KEY_HOSTNAME, host);
      args.put(KEY_PORT, String.valueOf(port));
  
      SocketAttachConnector connector = new SocketAttachConnector();
      try {
        connector.connect(args, monitor, launch);
      } catch (CoreException e) {
        throw new RuntimeException(
            "Debug VM not available at " + host + ":" + port + ". " +
            "Check hostname and port number.");
      }
  
      this.debugTarget = launch.getDebugTarget();
    }
  
    /**
     * Suspends the debug session.
     */
    public void suspend()
      throws DebugException
    {
      logger.info("Suspending debug session");
  
      if (debugTarget != null) {
        debugTarget.suspend();
      }
    }
  
    /**
     * Disconnects the debug session.
     */
    public void stop()
      throws DebugException
    {
      logger.info("Stopping debug session");
  
      if (debugTarget != null) {
        debugTarget.disconnect();
      }
  
      clear();
    }
  
    /**
     * Terminates the debug session.
     */
    public void terminate()
      throws DebugException
    {
      logger.info("Terminating debug session");
      if (debugTarget != null) {
        debugTarget.terminate();
      }
  
      clear();
    }
  
    /**
     * Resumes execution from the current breakpoint.
     */
    public void resume()
      throws DebugException
    {
      logger.debug("Resuming breakpoint");
      if (debugTarget != null) {
        debugTarget.resume();
      }
    }
  
    private void clear()
    {
      DebugPlugin.getDefault().removeDebugEventListener(listener);
    }
  
    public Map<String, Object> getStatus()
      throws DebugException
    {
      Map<String, Object> statusMap = new HashMap<String, Object>();
  
      statusMap.put("project", project.getName());
      statusMap.put("state", ViewUtils.EXPANDED_NODE_SYMBOL + getId() +
          " (" + state.getName() + ")");
      statusMap.put("threads", threadView.get());
      statusMap.put("variables", varView.get());
  
      return statusMap;
    }
  
    public DebuggerState getState()
    {
      return this.state;
    }
  
    public void setState(DebuggerState state)
    {
      this.state = state;
    }
  
    public String getId()
    {
      return this.id;
    }
  
    public ThreadContext getThreadContext()
    {
      return threadCtx;
    }
  
    public ThreadView getThreadView()
    {
      return threadView;
    }
  
    public VariableView getVariableView()
    {
      return varView;
    }
  
    public IDebugTarget getDebugTarget()
    {
      return debugTarget;
    }
  
    public void jumpToFilePosition(String fileName, int lineNum)
      throws Exception
    {
      vimClient.remoteFunctionCall(
          "eclim#java#debug#GoToFile",
          fileName, String.valueOf(lineNum));
    }
  
    public void refreshDebugStatus()
      throws Exception
    {
      vimClient.remoteSend(":JavaDebugStatus");
    }
  
    public void signalSessionTermination()
      throws Exception
    {
      vimClient.remoteFunctionCall("eclim#java#debug#SessionTerminated");
    }
  
    public void updateThreadView(long threadId, String kind, List<String> results)
      throws Exception
    {
      vimClient.remoteFunctionCall(
          "eclim#java#debug#ThreadViewUpdate",
          String.valueOf(threadId), kind, concatenateList(results));
    }
  
    public void updateVariableView(List<String> results)
      throws Exception
    {
      vimClient.remoteFunctionCall(
          "eclim#java#debug#VariableViewUpdate",
          concatenateList(results));
  
      // Hack to force VIM to execute the previous remote send command.
      // In some cases, VIM seems to buffer the command and not execute
      // until the next remote command is sent.
      vimClient.remoteSend(":echo ' '");
    }
  
    /**
     * Returns a string by concatenating all the given entries using <eol> as
     * delimiter.
     */
    private String concatenateList(List<String> entries)
    {
      if (entries == null || entries.isEmpty()) {
        // Return an empty string since the remote VIM command expects an arg
        return "\\ ";
      }
  
  