/**
 * Main Corona debugger class
 *
 * @author Jan Jancura
 * @version 0.47, May 26, 1998
 */
public class JPDADebugger extends AbstractDebugger implements Executor {

  // static ........................................................................

  static final long serialVersionUID = 2797853329739651906L;

  /** bundle to obtain text information from */
  static ResourceBundle bundle = org.openide.util.NbBundle.getBundle(JPDADebugger.class);

  private static CoreBreakpoint.Event[] breakpointEvents;
  private static CoreBreakpoint.Action[] breakpointActions;
  private static Random random = new Random();

  static {
    breakpointEvents =
        new CoreBreakpoint.Event[] {
          //      new InstanceCounter (),
          new LineBreakpoint(),
          new MethodBreakpoint(),
          new ExceptionBreakpoint(),
          new VariableBreakpoint(),
          new ThreadBreakpoint(),
          new ClassBreakpoint()
        };
    breakpointActions = new CoreBreakpoint.Action[] {};
  }

  // variables .................................................................

  transient VirtualMachine virtualMachine = null;
  transient EventRequestManager requestManager = null;
  protected transient ThreadManager threadManager;
  protected transient Operator operator;
  private transient Process process;
  transient StepRequest stepRequest;
  private transient MethodEntryRequest findSourceMER;
  private transient StepRequest findSourceSR;
  private transient int findSourceCounter = 0;
  private transient Thread debuggerThread;

  private transient AttachingConnector connector;
  private transient Map args;
  private transient String mainClassName;
  private transient String stopClassName;

  // threads
  private transient JPDAThread currentThread = null;
  protected transient JPDAThreadGroup threadGroup = new JPDAThreadGroup(null);

  private transient boolean stopOnMain = false;
  private transient DebuggerInfo debuggerInfo;

  private static transient String[] stopMethodNames = {"main", "start", "init", "<init>"}; // NOI18N
  private transient CoreBreakpoint[] breakpointMain = null;

  // init .......................................................................

  public JPDADebugger() {
    this(false, null);
  }

  public JPDADebugger(boolean multisession, Validator validator) {
    super(multisession, validator);
  }

  /** Deserializes debugger. */
  protected void setDebugger(AbstractDebugger debugger) {
    super.setDebugger(debugger);
  }

  // Debugger implementation .................................................................

  /**
   * Starts the debugger. The method stops the current debugging (if any) and takes information from
   * the provided info (containing the class to start and arguments to pass it and name of class to
   * stop debugging in) and starts new debugging session.
   *
   * @param info debugger info about class to start
   * @exception DebuggerException if an error occures during the start of the debugger
   */
  public void startDebugger(DebuggerInfo info) throws DebuggerException {
    debuggerInfo = info;
    if (virtualMachine != null) finishDebugger();

    stopOnMain = info.getStopClassName() != null;
    mainClassName =
        info
            .getClassName(); // S ystem.out.println ("JPDADebugger stop on " + info.getStopClassName
                             // ()); // NOI18N

    // open output window ...
    super.startDebugger(info);

    // stop on main
    if (stopOnMain) {
      try {
        String stopClassName = debuggerInfo.getStopClassName();
        AbstractDebugger d = (AbstractDebugger) TopManager.getDefault().getDebugger();
        breakpointMain = new CoreBreakpoint[stopMethodNames.length];
        for (int x = 0; x < breakpointMain.length; x++) {
          breakpointMain[x] = (CoreBreakpoint) d.createBreakpoint(true);
          breakpointMain[x].setClassName(""); // NOI18N
          breakpointMain[x].setMethodName(stopMethodNames[x]); // NOI18N
          CoreBreakpoint.Action[] a = breakpointMain[x].getActions();
          int i, ii = a.length;
          for (i = 0; i < ii; i++)
            if (a[i] instanceof PrintAction) {
              ((PrintAction) a[i]).setPrintText(bundle.getString("CTL_Stop_On_Main_print_text"));
            }
          breakpointMain[x].setClassName(stopClassName);
        }

        addPropertyChangeListener(
            new PropertyChangeListener() {
              public void propertyChange(PropertyChangeEvent ev) {
                if (ev.getPropertyName().equals(PROP_STATE)) {
                  if ((((Integer) ev.getNewValue()).intValue() == DEBUGGER_STOPPED)
                      || (((Integer) ev.getNewValue()).intValue() == DEBUGGER_NOT_RUNNING)) {
                    if (breakpointMain != null) {
                      for (int x = 0; x < breakpointMain.length; x++) breakpointMain[x].remove();
                      breakpointMain = null;
                    }
                    removePropertyChangeListener(this);
                  }
                }
              }
            });

      } catch (DebuggerException e) {
        e.printStackTrace();
      }
    }

    // start & init remote debugger ............................................
    boolean launch = false;
    if (info instanceof ReconnectDebuggerInfo) {
      virtualMachine = reconnect((ReconnectDebuggerInfo) info);
    } else if (info instanceof RemoteDebuggerInfo) {
      virtualMachine = connect((RemoteDebuggerInfo) info);
    } else {
      virtualMachine = launch(info);
      process = virtualMachine.process();
      showOutput(process, STD_OUT, STD_OUT);
      connectInput(process);
      launch = true;
    }
    requestManager = virtualMachine.eventRequestManager();
    operator =
        new Operator(
            virtualMachine,
            launch
                ? new Runnable() {
                  public void run() {
                    startDebugger();
                  }
                }
                : null,
            new Runnable() {
              public void run() {
                try {
                  finishDebugger();
                } catch (DebuggerException e) {
                }
              }
            });
    operator.start();
    if (!launch) startDebugger();
  }

  /** Finishes debugger. */
  public void finishDebugger() throws DebuggerException {
    if (breakpointMain != null) {
      for (int x = 0; x < breakpointMain.length; x++) breakpointMain[x].remove();
      breakpointMain = null;
    }
    try {
      if (virtualMachine != null) virtualMachine.exit(0);
    } catch (VMDisconnectedException e) {
    }
    if (threadManager != null) threadManager.finish();
    if (debuggerThread != null) {
      debuggerThread.interrupt();
      debuggerThread.stop();
    }
    super.finishDebugger();
  }

  /** Trace into. */
  public synchronized void traceInto() throws DebuggerException {
    if (virtualMachine == null) return;
    removeStepRequest();
    try {
      setLastAction(ACTION_TRACE_INTO);
      stepRequest =
          requestManager.createStepRequest(
              currentThread.getThreadReference(), StepRequest.STEP_LINE, StepRequest.STEP_INTO);
      stepRequest.addCountFilter(1);
      stepRequest.putProperty("traceInto", "traceInto"); // NOI18N
      stepRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL);
      operator.register(stepRequest, this);
      stepRequest.enable();
      virtualMachine.resume();
      super.traceInto();
    } catch (DuplicateRequestException e) {
      e.printStackTrace();
    }
  }

  /** Trace over. */
  public synchronized void traceOver() throws DebuggerException {
    if (virtualMachine == null) return;
    removeStepRequest();
    try {
      setLastAction(ACTION_TRACE_OVER);
      stepRequest =
          requestManager.createStepRequest(
              currentThread.getThreadReference(), StepRequest.STEP_LINE, StepRequest.STEP_OVER);
      stepRequest.addCountFilter(1);
      stepRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL);
      operator.register(stepRequest, this);
      stepRequest.enable();
      virtualMachine.resume();
      super.traceOver();
    } catch (DuplicateRequestException e) {
      e.printStackTrace();
    }
  }

  /** Go. */
  public synchronized void go() throws DebuggerException {
    if (virtualMachine == null) return;
    setLastAction(ACTION_GO);
    removeStepRequest();
    virtualMachine.resume();
    threadGroup.refresh();
    super.go();
  }

  /** Step out. */
  public synchronized void stepOut() throws DebuggerException {
    if (virtualMachine == null) return;
    removeStepRequest();
    try {
      setLastAction(ACTION_STEP_OUT);
      stepRequest =
          requestManager.createStepRequest(
              currentThread.getThreadReference(), StepRequest.STEP_LINE, StepRequest.STEP_OUT);
      stepRequest.addCountFilter(1);
      stepRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL);
      operator.register(stepRequest, this);
      stepRequest.enable();
      virtualMachine.resume();
      super.stepOut();
    } catch (DuplicateRequestException e) {
      e.printStackTrace();
    }
  }

  // WATCHES ..............................................................

  /**
   * Creates new uninitialized watch. The watch is visible (not hidden).
   *
   * @return new uninitialized watch
   */
  public Watch createWatch() {
    JPDAWatch w = new JPDAWatch(this);
    watch.addElement(w);
    fireWatchCreated(w);
    return w;
  }

  /**
   * Creates a watch its expression is set to initial value. Also allows to create a watch not
   * presented to the user, for example for internal usage in the editor to obtain values of
   * variables under the mouse pointer.
   *
   * @param expr expresion to watch for
   * @param hidden allows to specify whether the watch should be presented to the user or not (be
   *     only of internal usage of the IDE).
   * @return new watch
   */
  public Watch createWatch(String expr, boolean hidden) {
    JPDAWatch w = new JPDAWatch(this);
    if (!hidden) watch.addElement(w);
    w.setVariableName(expr);
    if (!hidden) fireWatchCreated(w);
    return w;
  }

  // AbstractDebugger implementation ..................................................

  // properties ........................

  /** Returns version of this debugger. */
  public String getVersion() {
    return bundle.getString("CTL_Debugger_version");
    /*    if (virtualMachine != null)
      return virtualMachine.versionDescription () + " (" +
             virtualMachine.majorVersion () + "/" +
             virtualMachine.minorVersion () + ")";
    else
      return bundle.getString ("CTL_Debugger_version");*/
  }

  /** Returns size of memory. */
  public int getTotalMemory() throws DebuggerException {
    if (virtualMachine == null) return 0;
    return 0;
  }

  /** Returns size of free memory. */
  public int getFreeMemory() throws DebuggerException {
    if (virtualMachine == null) return 0;
    return 0;
  }

  /** @return newly constructed string containing classpath obtained from filesystems */
  public String getClasspath() {
    return ""; // NOI18N
  }

  /** @return Connect Panel for this version of debugger. */
  public JComponent getConnectPanel() {
    return new Connector();
  }

  /** @return name of proces for given DebuggerInfo. */
  public String getProcessName(DebuggerInfo info) {
    if (info instanceof RemoteDebuggerInfo) {
      if (((RemoteDebuggerInfo) info).getConnector().transport().name().equals("dt_shmem")) {
        Argument a = (Argument) ((RemoteDebuggerInfo) info).getArgs().get("name");
        if (a == null) return "localhost:???";
        else return "localhost:" + a.value();
      } else if (((RemoteDebuggerInfo) info)
          .getConnector()
          .transport()
          .name()
          .equals("dt_socket")) {
        Argument name = (Argument) ((RemoteDebuggerInfo) info).getArgs().get("hostname");
        Argument port = (Argument) ((RemoteDebuggerInfo) info).getArgs().get("port");
        return ((name == null) ? "???:" : (name.value() + ":"))
            + ((port == null) ? "???" : (port.value()));
      } else return "???";
    } else return (info.getStopClassName() != null) ? info.getStopClassName() : info.getClassName();
  }

  /** @return name of location for given DebuggerInfo. */
  public String getLocationName(DebuggerInfo info) {
    if (info instanceof RemoteDebuggerInfo) {
      if (((RemoteDebuggerInfo) info).getConnector().transport().name().equals("dt_shmem")) {
        return "localhost";
      } else if (((RemoteDebuggerInfo) info)
          .getConnector()
          .transport()
          .name()
          .equals("dt_socket")) {
        Argument name = (Argument) ((RemoteDebuggerInfo) info).getArgs().get("hostname");
        return name == null ? "localhost" : name.value();
      } else return "localhost";
    } else return "localhost";
  }

  /** Returns true - JPDADebugger supports evaluation of expressions. */
  public boolean supportsExpressions() {
    return true;
  }

  // breakpoints ........................

  /** Returns events available for this version of debugger. */
  public CoreBreakpoint.Event[] getBreakpointEvents() {
    return breakpointEvents;
  }

  /** Returns actions available for this version of debugger. */
  public CoreBreakpoint.Action[] getBreakpointActions() {
    return breakpointActions;
  }

  // threads ........................

  /** Returns root of all threads. */
  public AbstractThreadGroup getThreadGroupRoot() {
    return threadGroup;
  }

  /** Returns current thread or null. */
  public AbstractThread getCurrentThread() {
    return currentThread;
  }

  /** Sets current thread. If thread is null, unsets curent thread. */
  public void setCurrentThread(AbstractThread thread) {
    if (currentThread == thread) return;
    Object old = currentThread;
    currentThread = (JPDAThread) thread;
    firePropertyChange(PROP_CURRENT_THREAD, old, thread);
  }

  // interface Executor .....................................................................

  /** Executes breakpoint hit event. */
  public void exec(com.sun.jdi.event.Event ev) {
    // S ystem.out.println ("exec "); // NOI18N
    removeStepRequest();
    StepEvent event = (StepEvent) ev;
    ThreadReference tr = event.thread();
    Location loc = event.location();
    int ln = -1;
    String methodName = "?"; // NOI18N
    String className = "?"; // NOI18N
    String lineNumber = "?"; // NOI18N
    String threadName = tr.name();
    Line l = null;

    if (loc != null) {
      if (loc.method() != null) methodName = loc.method().name();
      className = loc.declaringType().name();
      ln = loc.lineNumber();
      if (ln >= 0) lineNumber = "" + loc.lineNumber();
    }

    if (ln != -1)
      try {
        l = Utils.getLineForSource(className, loc.sourceName(), ln);
      } catch (AbsentInformationException e) {
        l = Utils.getLine(className, ln);
      }

    if (resolveCanBeCurrent(tr, l))
      // if this line can not be current resolveCanBeCurrent () calls stepOver
      return;
    // line can be current

    if ((l == null) && (getLastAction() == ACTION_TRACE_INTO))
      // try to find another "intelligent" line of code
      traceToSourceCode(tr);
    // you know - intelligent means that one with source code
    else {
      makeCurrent(threadName, className, methodName, lineNumber, l != null, tr);
      operator.stopRequest();
    }
  }

  // support for multisession debugging
  // ................................................................

  /** Disconnects from running debugged process. */
  public void disconnect() throws DebuggerException {
    if (breakpointMain != null) {
      for (int x = 0; x < breakpointMain.length; x++) breakpointMain[x].remove();
      breakpointMain = null;
    }
    try {
      if (virtualMachine != null) virtualMachine.dispose();
    } catch (VMDisconnectedException e) {
    }
    if (threadManager != null) threadManager.finish();
    if (debuggerThread != null) {
      debuggerThread.interrupt();
      debuggerThread.stop();
    }
    super.finishDebugger();
  }

  /** Reconnects to disconnected Virtual Machine. */
  public void reconnect() throws DebuggerException {
    startDebugger(new ReconnectDebuggerInfo(connector, args));
  }

  // helper private methods
  // .........................................................................

  /** Finds the first executed line with source code. */
  public void traceToSourceCode(ThreadReference thread) {
    // S ystem.out.println ("Start finding!!! "); // NOI18N

    // create Step Request for searching a source code
    try {
      findSourceSR =
          requestManager.createStepRequest(thread, StepRequest.STEP_LINE, StepRequest.STEP_OUT);
      findSourceSR.addCountFilter(1);
      findSourceSR.setSuspendPolicy(EventRequest.SUSPEND_ALL);
      operator.register(findSourceSR, this);
      findSourceSR.enable();
    } catch (DuplicateRequestException e) {
      e.printStackTrace();
    }

    // create Method Entry Request for searching a source code
    findSourceMER = requestManager.createMethodEntryRequest();
    findSourceMER.setSuspendPolicy(EventRequest.SUSPEND_ALL);
    findSourceMER.addThreadFilter(thread);
    findSourceCounter = 0;
    operator.register(
        findSourceMER,
        new Executor() {
          public void exec(com.sun.jdi.event.Event event) {
            if (findSourceCounter == 500) {
              // finding source takes a long time
              operator.resume();
              if (findSourceMER != null) {
                requestManager.deleteEventRequest(findSourceMER);
                findSourceMER = null;
              }
              return;
            }
            findSourceCounter++;

            Location loc = ((MethodEntryEvent) event).location();
            if (loc == null) {
              // no line => continue finding
              operator.resume();
              return;
            }
            String className = loc.declaringType().name();
            int ln = loc.lineNumber();
            // S ystem.out.println ("FIND " + className + " : " + ln); // NOI18N
            try {
              Line l = null;
              if ((l = Utils.getLineForSource(className, loc.sourceName(), ln)) == null) {
                // no line => continue finding
                operator.resume();
                return;
              }

              // WOW I have a nice line!
              ThreadReference tr = ((MethodEntryEvent) event).thread();
              if (resolveCanBeCurrent(tr, l))
                // if can not be current => steps to some line
                return;

              // line can be current!
              String threadName = tr.name();
              String methodName = loc.method() != null ? loc.method().name() : ""; // NOI18N
              String lineNumber = ln == -1 ? "?" : "" + ln; // NOI18N
              makeCurrent(threadName, className, methodName, lineNumber, true, tr);
              operator.stopRequest();
            } catch (AbsentInformationException e) {
            }
          }
        });
    findSourceMER.enable();

    operator.resume();
    return;
  }

  /** if this line can not be current => stepOver & return true. return false on the other hand. */
  boolean resolveCanBeCurrent(ThreadReference tr) {
    try {
      Location l = tr.frame(0).location();
      if (l == null) return false;
      return resolveCanBeCurrent(
          tr, Utils.getLineForSource(l.declaringType().name(), l.sourceName(), l.lineNumber()));
    } catch (Exception e) {
    }
    return false;
  }

  /**
   * If this line can not be current => stepOver & return true. {support for non java languages}
   *
   * <p>return false on the other hand.
   */
  boolean resolveCanBeCurrent(ThreadReference tr, Line l) {
    if ((l != null) && (!canBeCurrent(l, false))) {
      try {
        removeStepRequest();
        findSourceSR =
            requestManager.createStepRequest(tr, StepRequest.STEP_LINE, StepRequest.STEP_OVER);
        findSourceSR.addCountFilter(1);
        findSourceSR.setSuspendPolicy(EventRequest.SUSPEND_ALL);
        operator.register(findSourceSR, this);
        findSourceSR.enable();
        operator.resume();
      } catch (DuplicateRequestException e) {
        e.printStackTrace();
      }
      return true;
    }
    return false;
  }

  /**
   * Sets curent line. It means: change debugger state to stopped, shows message, sets current
   * thread and updates watches.
   */
  private void makeCurrent(
      final String threadName,
      final String className,
      final String methodName,
      final String lineNumber,
      final boolean hasSource,
      final ThreadReference tr) {
    setDebuggerState(DEBUGGER_STOPPED);

    SwingUtilities.invokeLater(
        new Runnable() {
          public void run() {
            // show message
            if (isFollowedByEditor()) {
              if (hasSource) {
                println(
                    new MessageFormat(bundle.getString("CTL_Thread_stopped"))
                        .format(new Object[] {threadName, className, methodName, lineNumber}),
                    ERR_OUT + STL_OUT);
              } else {
                println(
                    new MessageFormat(bundle.getString("CTL_Thread_stopped_no_source"))
                        .format(new Object[] {threadName, className, methodName, lineNumber}),
                    ERR_OUT + STL_OUT);
              }
            } else
              println(
                  new MessageFormat(bundle.getString("CTL_Thread_stopped"))
                      .format(new Object[] {threadName, className, methodName, lineNumber}),
                  ERR_OUT + STL_OUT);

            // refresh all
            JPDAThread tt = threadManager.getThread(tr);
            tt.setCurrent(true);
            updateWatches();
          }
        });
  }

  /** Second part of debugger start procedure. */
  private void startDebugger() {
    threadManager = new ThreadManager(this);

    setBreakpoints();
    updateWatches();
    println(bundle.getString("CTL_Debugger_running"), STL_OUT);
    setDebuggerState(DEBUGGER_RUNNING);

    virtualMachine.resume();

    // start refresh thread .................................................
    if (debuggerThread != null) debuggerThread.stop();
    debuggerThread =
        new Thread(
            new Runnable() {
              public void run() {
                for (; ; ) {
                  try {
                    Thread.sleep(5000);
                  } catch (InterruptedException ex) {
                  }
                  if (getState() == DEBUGGER_RUNNING) threadGroup.refresh();
                }
              }
            },
            "Debugger refresh thread"); // NOI18N
    debuggerThread.setPriority(Thread.MIN_PRIORITY);
    debuggerThread.start();
  }

  /** Removes last step request. */
  void removeStepRequest() {
    if (stepRequest != null) {
      requestManager.deleteEventRequest(stepRequest);
      stepRequest = null;
    }
    if (findSourceMER != null) {
      requestManager.deleteEventRequest(findSourceMER);
      findSourceMER = null;
    }
    if (findSourceSR != null) {
      requestManager.deleteEventRequest(findSourceSR);
      findSourceSR = null;
    }
  }

  private static String generatePassword() {
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < 4; i++) sb.append((char) (random.nextInt(26) + 'a'));
    return new String(sb);
  }

  private VirtualMachine launch(DebuggerInfo info) throws DebuggerException {
    // create process & read password for local debugging

    // create main class & arguments ...............................................
    StringBuffer sb = new StringBuffer();
    sb.append(mainClassName);
    String[] infoArgs = info.getArguments();
    int i, k = infoArgs.length;
    for (i = 0; i < k; i++) sb.append(" \"").append(infoArgs[i]).append('"'); // NOI18N
    String main = new String(sb);

    // create connector ..............................................................
    VirtualMachineManager vmm = Bootstrap.virtualMachineManager();
    java.util.List lcs = vmm.launchingConnectors();
    k = lcs.size();
    for (i = 0; i < k; i++)
      if (((LaunchingConnector) lcs.get(i)).name().indexOf("RawCommandLineLaunch") >= 0) // NOI18N
      break;
    if (i == k) {
      finishDebugger();
      throw new DebuggerException(
          new MessageFormat(bundle.getString("EXC_Cannot_find_launcher"))
              .format(
                  new Object[] {
                    "RawCommandLineLaunch" // NOI18N
                  }));
    }
    LaunchingConnector lc = (LaunchingConnector) lcs.get(i);
    String transport = lc.transport().name();

    // create commandLine & NbProcessDescriptor ..............................
    NbProcessDescriptor debugerProcess;
    if (info instanceof ProcessDebuggerInfo)
      debugerProcess = ((ProcessDebuggerInfo) info).getDebuggerProcess();
    else debugerProcess = ProcessDebuggerType.DEFAULT_DEBUGGER_PROCESS;

    // generate password
    String password;
    if (transport.equals("dt_shmem")) { // NOI18N
      connector = getAttachingConnectorFor("dt_shmem");
      password = generatePassword();
      args = connector.defaultArguments();
      ((Argument) args.get("name")).setValue(password);
    } else {
      try {
        java.net.ServerSocket ss = new java.net.ServerSocket(0);
        password = "" + ss.getLocalPort(); // NOI18N
        ss.close();
      } catch (java.io.IOException e) {
        finishDebugger();
        throw new DebuggerException(
            new MessageFormat(bundle.getString("EXC_Cannot_find_empty_local_port"))
                .format(new Object[] {e.toString()}));
      }
      connector = getAttachingConnectorFor("dt_socket");
      args = connector.defaultArguments();
      ((Argument) args.get("port")).setValue(password);
    }
    HashMap map =
        Utils.processDebuggerInfo(
            info,
            "-Xdebug -Xnoagent -Xrunjdwp:transport="
                + // NOI18N
                transport
                + ",address="
                + // NOI18N
                password
                + ",suspend=y ", // NOI18N
            main);
    MapFormat format = new MapFormat(map);
    String commandLine =
        format.format(
            debugerProcess.getProcessName()
                + " "
                + // NOI18N
                debugerProcess.getArguments());
    println(commandLine, ERR_OUT);
    /*
    We mus wait on process start to connect...
    try {
      process = debugerProcess.exec (format);
    } catch (java.io.IOException exc) {
      finishDebugger ();
      throw new DebuggerException (
        new MessageFormat (bundle.getString ("EXC_While_create_debuggee")).
          format (new Object[] {
            debugerProcess.getProcessName (),
            exc.toString ()
          }),
        exc
      );
    }
    return connect (
      null,
      connector,
      args
    );*/

    /*      S ystem.out.println ("attaching: ");
    Utils.showConnectors (vmm.attachingConnectors ());

    S ystem.out.println ("launching: ");
    Utils.showConnectors (vmm.launchingConnectors ());

    S ystem.out.println ("listening: ");
    Utils.showConnectors (vmm.listeningConnectors ());*/

    // set debugger-start arguments
    Map params = lc.defaultArguments();
    ((Argument) params.get("command"))
        .setValue( // NOI18N
            commandLine);
    ((Argument) params.get("address"))
        .setValue( // NOI18N
            password);

    // launch VM
    try {
      return lc.launch(params);
    } catch (VMStartException exc) {
      showOutput(process = exc.process(), ERR_OUT, ERR_OUT);
      finishDebugger();
      throw new DebuggerException(
          new MessageFormat(bundle.getString("EXC_While_create_debuggee"))
              .format(
                  new Object[] {format.format(debugerProcess.getProcessName()), exc.toString()}),
          exc);
    } catch (Exception exc) {
      finishDebugger();
      throw new DebuggerException(
          new MessageFormat(bundle.getString("EXC_While_create_debuggee"))
              .format(
                  new Object[] {format.format(debugerProcess.getProcessName()), exc.toString()}),
          exc);
    }
  }

  private VirtualMachine reconnect(ReconnectDebuggerInfo info) throws DebuggerException {
    return connect("CTL_Reconnecting_to", info.getConnector(), info.getArgs());
  }

  private VirtualMachine connect(RemoteDebuggerInfo info) throws DebuggerException {
    return connect("CTL_Connecting_to", connector = info.getConnector(), args = info.getArgs());
  }

  private VirtualMachine connect(String bndlPrefix, AttachingConnector connector, Map args)
      throws DebuggerException {
    if (bndlPrefix != null) {
      if (connector.transport().name().equals("dt_shmem")) {
        Argument a = (Argument) args.get("name");
        if (a == null) println(bundle.getString(bndlPrefix + "_shmem_noargs"), ERR_OUT);
        else
          println(
              new MessageFormat(bundle.getString(bndlPrefix + "_shmem"))
                  .format(new Object[] {a.value()}),
              ERR_OUT);
      } else if (connector.transport().name().equals("dt_socket")) {
        Argument name = (Argument) args.get("hostname");
        Argument port = (Argument) args.get("port");
        if ((name == null) || (port == null))
          println(bundle.getString(bndlPrefix + "_socket_noargs"), ERR_OUT);
        else
          println(
              new MessageFormat(bundle.getString(bndlPrefix + "_socket"))
                  .format(new Object[] {name.value(), port.value()}),
              ERR_OUT);
      } else println(bundle.getString(bndlPrefix), ERR_OUT);
    }

    // launch VM
    try { // S ystem.out.println ("attach to:" + ac + " : " + password); // NOI18N
      return connector.attach(args);
    } catch (Exception e) {
      finishDebugger();
      throw new DebuggerException(
          new MessageFormat(bundle.getString("EXC_While_connecting_to_debuggee"))
              .format(new Object[] {e.toString()}),
          e);
    }
  }

  /** Performs stop action. */
  void stop(boolean stop, final AbstractThread thread) {
    final ResourceBundle bundle = NbBundle.getBundle(JPDADebugger.class);
    if (stop) {
      removeStepRequest();
      setLastAction(ACTION_BREAKPOINT_HIT);
      setDebuggerState(DEBUGGER_STOPPED);
      operator.stopRequest();
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              thread.setCurrent(true);
              updateWatches();
              threadGroup.refresh();
            }
          });
    } else operator.resume();
  }

  private static AttachingConnector getAttachingConnectorFor(String transport) {
    List acs = Bootstrap.virtualMachineManager().attachingConnectors();
    AttachingConnector ac;
    int i, k = acs.size();
    for (i = 0; i < k; i++)
      if ((ac = (AttachingConnector) acs.get(i)).transport().name().equals(transport)) return ac;
    return null;
  }

  /**
   * Setter method for debugger state property.
   *
   * @param newState
   */
  public synchronized void setDebuggerState(final int newState) {
    super.setDebuggerState(newState);
  }

  // innerclasses ..............................................................

  private class ReconnectDebuggerInfo extends RemoteDebuggerInfo {
    private ReconnectDebuggerInfo(AttachingConnector connector, Map args) {
      super(connector, args);
    }
  }

  class Connector extends JPanel implements DebuggerInfoProducer, ActionListener {

    private JComboBox cbConnectors;
    private Map args;
    private java.util.List acs;
    private JTextField[] tfParams;
    private AttachingConnector ac;

    Connector() {
      VirtualMachineManager vmm = Bootstrap.virtualMachineManager();
      acs = vmm.attachingConnectors();
      setLayout(new GridBagLayout());
      refresh(0);
    }

    private void refresh(int index) {
      GridBagConstraints c = new GridBagConstraints();

      // No connector ................
      if (acs.size() == 0) {
        add(new JLabel(bundle.getString("CTL_No_Connector")), c);
        return;
      }

      // Connector switch ................
      if (acs.size() > 1) {
        c.insets = new Insets(0, 0, 3, 3);
        add(new JLabel(bundle.getString("CTL_Connector")), c);

        cbConnectors = new JComboBox();
        int i, k = acs.size();
        for (i = 0; i < k; i++) {
          AttachingConnector ac = (AttachingConnector) acs.get(i);
          int jj = ac.name().lastIndexOf('.');
          String s = (jj < 0) ? ac.name() : ac.name().substring(jj + 1);
          cbConnectors.addItem(s + " (" + ac.description() + ")");
        }
        c = new GridBagConstraints();
        c.insets = new Insets(0, 3, 3, 0);
        c.weightx = 1.0;
        c.fill = java.awt.GridBagConstraints.HORIZONTAL;
        c.gridwidth = 0;
        cbConnectors.setSelectedIndex(index);
        cbConnectors.setActionCommand("SwitchMe!");
        cbConnectors.addActionListener(this);
        add(cbConnectors, c);
      }

      ac = (AttachingConnector) acs.get(index);

      // Transport ................
      c = new GridBagConstraints();
      c.insets = new Insets(3, 0, 0, 3);
      add(new JLabel(bundle.getString("CTL_Transport")), c);

      JTextField tfTransport = new JTextField(ac.transport().name());
      tfTransport.setEnabled(false);
      c = new GridBagConstraints();
      c.gridwidth = 0;
      c.insets = new Insets(3, 3, 0, 0);
      c.fill = java.awt.GridBagConstraints.HORIZONTAL;
      c.weightx = 1.0;
      add(tfTransport, c);

      // Other params ................
      args = ac.defaultArguments();
      tfParams = new JTextField[args.size()];
      Iterator it = args.keySet().iterator();
      int i = 0;
      while (it.hasNext()) {
        String name = (String) it.next();
        Argument a = (Argument) args.get(name);

        c = new GridBagConstraints();
        c.insets = new Insets(6, 0, 0, 3);
        c.anchor = GridBagConstraints.WEST;
        add(new JLabel(a.label() + ": "), c);

        JTextField tfParam = new JTextField(a.value());
        tfParams[i++] = tfParam;
        tfParam.setName(name);
        c = new GridBagConstraints();
        c.gridwidth = 0;
        c.insets = new Insets(6, 3, 0, 0);
        c.fill = java.awt.GridBagConstraints.HORIZONTAL;
        c.weightx = 1.0;
        add(tfParam, c);
      }

      c = new GridBagConstraints();
      c.weighty = 1.0;
      JPanel p = new JPanel();
      p.setPreferredSize(new Dimension(1, 1));
      add(p, c);
    }

    /** Returns DebuggerInfo. */
    public DebuggerInfo getDebuggerInfo() {
      int i, k = tfParams.length;
      for (i = 0; i < k; i++) {
        Argument a = (Argument) args.get(tfParams[i].getName());
        a.setValue(tfParams[i].getText());
      }
      return new RemoteDebuggerInfo(ac, args);
    }

    public void actionPerformed(ActionEvent e) {
      removeAll();
      refresh(((JComboBox) e.getSource()).getSelectedIndex());
      Component w = getParent();
      while (!(w instanceof Window)) w = w.getParent();
      if (w != null) ((Window) w).pack(); // ugly hack...
      return;
    }

    private String translate(String name) {
      /*      if (name.equals ("SwitchMe!"))
        return
      else*/
      return name;
    }
  }
}
/**
 * Main Corona debugger class
 *
 * @author Jan Jancura, Jaroslav Tulach
 * @version 0.47, May 26, 1998
 */
public class ToolsDebugger extends AbstractDebugger {

  // static ........................................................................

  static final long serialVersionUID = 2791375515739651906L;

  /** bundle to obtain text information from */
  static ResourceBundle bundle = org.openide.util.NbBundle.getBundle(ToolsDebugger.class);

  static final int TIMEOUT = 5000;

  private static CoreBreakpoint.Event[] breakpointEvents;
  private static CoreBreakpoint.Action[] breakpointActions;
  private static String host = "localhost"; // NOI18N

  static {
    breakpointEvents =
        new CoreBreakpoint.Event[] {
          new LineBreakpoint(), new MethodBreakpoint(), new ExceptionBreakpoint()
        };
    breakpointActions = new CoreBreakpoint.Action[] {};
  }

  // variables .................................................................

  /* Helper for synchronizing sun.tools.debug */
  transient RequestSynchronizer synchronizer;
  /* sun.tools.debug main debugger class */
  transient RemoteDebugger remoteDebugger = null;
  /* debugged process */
  private transient Process process;
  private transient String hostName;
  private transient String password;
  private transient String mainClassName;
  private transient String stopClassName;

  // threads
  private transient ToolsThread currentThread = null;
  transient RemoteThread lastCurrentThread = null;
  protected transient ToolsThreadGroup threadGroup = new ToolsThreadGroup(this, null);

  /** Refresh thread. */
  private transient Thread debuggerThread;

  // properties
  private transient String sourcePath = null;
  private transient String[] exceptionCatchList = null;
  private transient DebuggerInfo debuggerInfo;

  transient RequestSynchronizer.RequestWaiter killer;

  transient boolean stopOnMainFlag;

  // init .......................................................................

  public ToolsDebugger() {
    this(false, null);
  }

  public ToolsDebugger(boolean multisession, Validator validator) {
    super(multisession, validator);
    killer =
        new RequestSynchronizer.RequestWaiter() {
          public void run(Thread t) {
            // S ystem.out.println ("KILL!!!"); // NOI18N
            // T hread.dumpStack ();
            if (process != null) {
              if (System.getProperty("netbeans.full.hack") == null) { // [PENDING]
                process.destroy();
                t.stop();
              }
              TopManager.getDefault()
                  .notify(
                      new NotifyDescriptor.Message(
                          new MessageFormat(bundle.getString("EXC_Deadlock"))
                              .format(new Object[] {t.getName()})));
            }
          }
        };
  }

  /** Deserializes debugger. */
  protected void setDebugger(AbstractDebugger debugger) {
    super.setDebugger(debugger);
  }

  // Debugger implementation .................................................................

  /**
   * Starts the debugger. The method stops the current debugging (if any) and takes information from
   * the provided info (containing the class to start and arguments to pass it and name of class to
   * stop debugging in) and starts new debugging session.
   *
   * @param info debugger info about class to start
   * @exception DebuggerException if an error occures during the start of the debugger
   */
  public void startDebugger(DebuggerInfo info) throws DebuggerException {
    debuggerInfo = info;
    if (remoteDebugger != null) finishDebugger();
    // S ystem.out.println("startDebugger " + info); // NOI18N
    // RemoteDebugging support
    hostName = null;
    password = null;
    boolean local = true;
    if (info instanceof ReconnectDebuggerInfo) {
      ReconnectDebuggerInfo rdi = (ReconnectDebuggerInfo) info;
      hostName = rdi.getHostName();
      password = rdi.getPassword();
      local = false;
    } else if (info instanceof RemoteDebuggerInfo) {
      hostName = ((RemoteDebuggerInfo) info).getHostName();
      password = ((RemoteDebuggerInfo) info).getPassword();
      local = false;
    }
    boolean stopOnMain = info.getStopClassName() != null;
    stopOnMainFlag = stopOnMain;
    // S ystem.out.println ("ToolsDebugger.startDebugger " + info.getStopClassName ()); // NOI18N
    // T hread.dumpStack ();

    synchronizer = new RequestSynchronizer();

    // open output window ...
    super.startDebugger(info);

    // start & init remote debugger ................................................
    //    process = null;
    if (local) {
      // create process & read password for local debugging

      // create starting string & NbProcessDescriptor
      NbProcessDescriptor debugerProcess;
      if (info instanceof ProcessDebuggerInfo)
        debugerProcess = ((ProcessDebuggerInfo) info).getDebuggerProcess();
      else debugerProcess = ProcessDebuggerType.DEFAULT_DEBUGGER_PROCESS;
      HashMap map;
      if (info instanceof ToolsDebugger10Info) {
        map =
            Utils.processDebuggerInfo(
                info,
                "-debug", // NOI18N
                "sun.tools.debug.EmptyApp" // NOI18N
                );
        map.put(ToolsDebugger10Type.JAVA_HOME_SWITCH, ((ToolsDebugger10Info) info).getJavaHome());
      } else {
        if (info instanceof ToolsDebugger11Info) {
          String javaHome11 = ((ToolsDebugger11Info) info).getJavaHome();
          if ((javaHome11 == null) || (javaHome11.trim().length() == 0)) {
            finishDebugger();
            throw new DebuggerException(bundle.getString("EXC_JDK11_home_is_not_set"));
          }
          map =
              Utils.processDebuggerInfo(
                  info,
                  "-debug -nojit", // NOI18N
                  "sun.tools.debug.EmptyApp" // NOI18N
                  );
          map.put(ToolsDebugger11Type.JAVA_HOME_SWITCH, javaHome11);
        } else {
          map =
              Utils.processDebuggerInfo(
                  info,
                  "-Xdebug", // NOI18N
                  "sun.tools.agent.EmptyApp" // NOI18N
                  );
        }
      }
      MapFormat format = new MapFormat(map);
      String s =
          format.format(
              debugerProcess.getProcessName() + " " + debugerProcess.getArguments() // NOI18N
              );
      println(s, ERR_OUT);

      // start process & read password ......................................
      try {
        process = debugerProcess.exec(format);
        BufferedReader bufferedreader =
            new BufferedReader(new InputStreamReader(process.getInputStream()));
        password = bufferedreader.readLine();
        showOutput(process, ERR_OUT, ERR_OUT);
        connectInput(process);
      } catch (java.lang.Exception e) {
        finishDebugger();
        throw new DebuggerException(
            new MessageFormat(bundle.getString("EXC_While_create_debuggee"))
                .format(
                    new Object[] {format.format(debugerProcess.getProcessName()), e.toString()}),
            e);
      }
      if (password == null) {
        // no reply
        finishDebugger();
        throw new DebuggerException(
            new MessageFormat(bundle.getString("EXC_While_connect_to_debuggee"))
                .format(new Object[] {format.format(debugerProcess.getProcessName())}));
      }
      if (password.indexOf("=") < 0) { // NOI18N
        // unexpected reply
        println(bundle.getString("CTL_Unexpected_reply") + ": " + password, ERR_OUT);
        showOutput(process, ERR_OUT + STD_OUT, ERR_OUT);
        finishDebugger();
        throw new DebuggerException(
            new MessageFormat(bundle.getString("EXC_Unecpected_debugger_reply"))
                .format(new Object[] {password}));
      }
      password = password.substring(password.indexOf("=") + 1); // NOI18N
      println(bundle.getString("CTL_Password") + ": " + password, ERR_OUT);
      hostName = "127.0.0.1"; // NOI18N
    } // end of local debugging specific
    else if (info instanceof ReconnectDebuggerInfo) {
      println(bundle.getString("CTL_Reconnecting"), ERR_OUT | STD_OUT);
    } else
      println(bundle.getString("CTL_Connecting_to") + ": " + hostName + ":" + password, ERR_OUT);

    // start RemoteDebugger ...................................................
    try {
      remoteDebugger =
          new RemoteDebugger(
              hostName,
              password.length() < 1 ? null : password,
              new ToolsCallback(this),
              isShowMessages());
    } catch (java.net.ConnectException e) {
      finishDebugger();
      throw new DebuggerException(
          new MessageFormat(bundle.getString("EXC_Cannot_connect_to_debuggee"))
              .format(new Object[] {e.toString()}),
          e);
    } catch (Throwable e) {
      if (e instanceof ThreadDeath) throw (ThreadDeath) e;
      // e.printStackTrace ();
      finishDebugger();
      throw new DebuggerException(
          new MessageFormat(bundle.getString("EXC_Cannot_connect_to_debuggee"))
              .format(new Object[] {e.toString()}),
          e);
    }

    // create arguments for main class ...............................................
    mainClassName = info.getClassName();
    RemoteClass cls;
    String[] args = null;
    if ((mainClassName != null) && (mainClassName.length() > 0)) {
      String[] infoArgs = info.getArguments();
      args = new String[infoArgs.length + 1];
      args[0] = mainClassName;
      System.arraycopy(infoArgs, 0, args, 1, infoArgs.length);
      // args[0] = name of class
      // args[...] = parameters

      // find main class .........................................................
      try {
        cls = remoteDebugger.findClass(mainClassName);
      } catch (Throwable e) {
        if (e instanceof ThreadDeath) throw (ThreadDeath) e;
        finishDebugger();
        throw new DebuggerException(
            new MessageFormat(bundle.getString("EXC_Cannot_find_class"))
                .format(new Object[] {mainClassName, e.toString()}),
            e);
      }
      if (cls == null) {
        finishDebugger();
        throw new DebuggerException(
            new MessageFormat(bundle.getString("EXC_Cannot_find_class"))
                .format(new Object[] {mainClassName, new ClassNotFoundException().toString()}));
      }
    }

    // set breakpoint on stop class method ...............................................
    if (stopOnMain) {
      RemoteClass stopClass = null;
      try {
        stopClass = remoteDebugger.findClass(stopClassName = info.getStopClassName());
      } catch (Throwable e) {
        if (e instanceof ThreadDeath) throw (ThreadDeath) e;
        println(
            bundle.getString("MSG_Exc_while_finding_class") + stopClassName + '\n' + e, ERR_OUT);
      }
      if (stopClass == null) {
        println(bundle.getString("CTL_No_such_class") + ": " + stopClassName, ERR_OUT);
      } else {
        try {
          RemoteField[] rf = stopClass.getMethods();
          int i, k = rf.length;
          Type t = Type.tMethod(Type.tVoid, new Type[] {Type.tArray(Type.tString)});
          Type startT = Type.tMethod(Type.tVoid);
          RemoteField startM = null;
          RemoteField initM = null;
          RemoteField constM = null;
          for (i = 0; i < k; i++) {
            if (rf[i].getName().equals("main")
                && // NOI18N
                rf[i].getType().equals(t)) break;
            else if (rf[i].getName().equals("start")
                && // NOI18N
                rf[i].getType().equals(startT)) startM = rf[i];
            else if (rf[i].getName().equals("init")
                && // NOI18N
                rf[i].getType().equals(startT)) initM = rf[i];
            else if (rf[i].getName().equals("<init>")
                && // NOI18N
                rf[i].getType().equals(startT)) constM = rf[i];
          }
          if (i < k) // [PENDING] stop on non main too !!!!!!!!!!!!!!!!!!!!!
          stopClass.setBreakpointMethod(rf[i]); // have main
          else if (initM != null) stopClass.setBreakpointMethod(initM);
          else if (startM != null) stopClass.setBreakpointMethod(startM);
          else if (constM != null) stopClass.setBreakpointMethod(constM);

          // S ystem.out.println ("Stop: " + (i <k) + " " + initM +" " + startM +" " + constM); //
          // NOI18N
          /*          pendingBreakpoints = new RemoteField [1];
          pendingBreakpoints [0] = rf[i];
          pendingBreakpointsClass = stopClass;*/
        } catch (Throwable e) {
          if (e instanceof ThreadDeath) throw (ThreadDeath) e;
          println(bundle.getString("MSG_Exc_while_setting_breakpoint") + '\n' + e, ERR_OUT);
        }
      }
    } // stopOnMain

    setBreakpoints();
    updateWatches();
    println(bundle.getString("CTL_Debugger_running"), STL_OUT);
    setDebuggerState(DEBUGGER_RUNNING);

    // run debugged class ...............................................
    if (args != null) {
      RemoteThreadGroup rtg = null;
      try {
        rtg = remoteDebugger.run(args.length, args);
        //        threadGroup.setRemoteThreadGroup (rtg);
      } catch (Throwable e) {
        if (e instanceof ThreadDeath) throw (ThreadDeath) e;
        finishDebugger();
        throw new DebuggerException(
            new MessageFormat(bundle.getString("EXC_While_calling_run"))
                .format(new Object[] {mainClassName, e.toString()}),
            e);
      }
      if (rtg == null) {
        finishDebugger();
        throw new DebuggerException(
            new MessageFormat(bundle.getString("EXC_While_calling_run"))
                .format(
                    new Object[] {
                      mainClassName, "" // NOI18N
                    }));
      }
    }

    // start refresh thread .................................................
    if (debuggerThread != null) debuggerThread.stop();
    debuggerThread =
        new Thread(
            new Runnable() {
              public void run() {
                for (; ; ) {
                  try {
                    Thread.sleep(5000);
                  } catch (InterruptedException ex) {
                  }
                  if (getState() == DEBUGGER_RUNNING)
                    try {
                      threadGroup.threadChanged();

                    } catch (Throwable e) {
                      if (e instanceof ThreadDeath) throw (ThreadDeath) e;
                      if (e instanceof java.net.SocketException) {
                        debuggerThread = null;
                        try {
                          finishDebugger();
                        } catch (Throwable ee) {
                          if (ee instanceof ThreadDeath) throw (ThreadDeath) ee;
                        }
                        Thread.currentThread().stop();
                      }
                    }
                }
              }
            },
            "Debugger refresh thread"); // NOI18N
    debuggerThread.setPriority(Thread.MIN_PRIORITY);
    debuggerThread.start();
  }

  /** Finishes debugger. */
  public void finishDebugger() throws DebuggerException {
    threadGroup.setRemoteThreadGroup(null);
    if (remoteDebugger != null) {
      remoteDebugger.close();
      remoteDebugger = null;
    }
    if (process != null) process.destroy();
    if (debuggerThread != null) {
      debuggerThread.interrupt();
      debuggerThread.stop();
    }

    super.finishDebugger();

    synchronizer = null;
  }

  /** Trace into. */
  public void traceInto() throws DebuggerException {
    if (currentThread == null) return;

    setLastAction(ACTION_TRACE_INTO);
    currentThread.setLastAction(ACTION_TRACE_INTO);
    new Protector("AbstractDebugger.traceInto") { // NOI18N
      public Object protect() throws Exception {
        currentThread.getRemoteThread().step(true);
        ToolsDebugger.super.traceInto();
        return null;
      }
    }.go(synchronizer, killer);
  }

  /** Trace over. */
  public void traceOver() throws DebuggerException {
    if (currentThread == null) return;

    setLastAction(ACTION_TRACE_OVER);
    currentThread.setLastAction(ACTION_TRACE_OVER);
    new Protector("AbstractDebugger.traceOver") { // NOI18N
      public Object protect() throws Exception {
        currentThread.getRemoteThread().next();
        ToolsDebugger.super.traceOver();
        return null;
      }
    }.go(synchronizer, killer);
  }

  /** Go. */
  public void go() throws DebuggerException {
    if (currentThread == null) return;

    setLastAction(ACTION_GO);
    currentThread.setLastAction(ACTION_GO);
    new Protector("AbstractDebugger.go") { // NOI18N
      public Object protect() throws Exception {
        remoteDebugger.cont();
        ToolsDebugger.super.go();
        return null;
      }
    }.go(synchronizer, killer);

    // threadGroup.setSuspended (false);
  }

  /** Step out. */
  public void stepOut() throws DebuggerException {
    if (currentThread == null) return;

    setLastAction(ACTION_STEP_OUT);
    currentThread.setLastAction(ACTION_STEP_OUT);
    new Protector("AbstractDebugger.stepOut") { // NOI18N
      public Object protect() throws Exception {
        currentThread.getRemoteThread().stepOut();
        ToolsDebugger.super.stepOut();
        return null;
      }
    }.go(synchronizer, killer);
  }

  // WATCHES ..............................................................

  /**
   * Creates new uninitialized watch. The watch is visible (not hidden).
   *
   * @return new uninitialized watch
   */
  public Watch createWatch() {
    ToolsWatch w = new ToolsWatch(this);
    watch.addElement(w);
    fireWatchCreated(w);
    return w;
  }

  /**
   * Creates a watch its expression is set to initial value. Also allows to create a watch not
   * presented to the user, for example for internal usage in the editor to obtain values of
   * variables under the mouse pointer.
   *
   * @param expr expresion to watch for
   * @param hidden allows to specify whether the watch should be presented to the user or not (be
   *     only of internal usage of the IDE).
   * @return new watch
   */
  public Watch createWatch(String expr, boolean hidden) {
    ToolsWatch w = new ToolsWatch(this);
    if (!hidden) watch.addElement(w);
    w.setVariableName(expr);
    if (!hidden) fireWatchCreated(w);
    return w;
  }

  // AbstractDebugger implementation ..................................................

  // properties ........................

  /** Returns version of this debugger. */
  public String getVersion() {
    return bundle.getString("CTL_Debugger_version");
  }

  /** Returns size of memory. */
  public int getTotalMemory() throws DebuggerException {
    if (remoteDebugger == null) return 0;
    try {
      return ((Integer)
              new Protector("AbstractDebugger.getTotalMemory") { // NOI18N
                public Object protect() throws Exception {
                  return new Integer(remoteDebugger.totalMemory());
                }
              }.throwAndWait(synchronizer, killer))
          .intValue();
    } catch (Exception e) {
      throw new DebuggerException(e);
    }
  }

  /** Returns size of free memory. */
  public int getFreeMemory() throws DebuggerException {
    if (remoteDebugger == null) return 0;
    try {
      return ((Integer)
              new Protector("AbstractDebugger.getFreeMemory") { // NOI18N
                public Object protect() throws Exception {
                  return new Integer(remoteDebugger.freeMemory());
                }
              }.throwAndWait(synchronizer, killer))
          .intValue();
    } catch (Exception e) {
      throw new DebuggerException(e);
    }
  }

  /** @return newly constructed string containing classpath obtained from filesystems */
  public String getClasspath() {
    if (remoteDebugger != null) {
      try {
        return remoteDebugger.getSourcePath();
      } catch (Exception e) {
      }
    }
    return ""; // getDefaultClasspath (); // NOI18N
  }

  /** @return Connect Panel for this version of debugger. */
  public JComponent getConnectPanel() {
    return new Connector();
  }

  /** @return name of proces for given DebuggerInfo. */
  public String getProcessName(DebuggerInfo info) {
    String n;
    if (info instanceof RemoteDebuggerInfo)
      return ((RemoteDebuggerInfo) info).getHostName()
          + ":"
          + // NOI18N
          ((RemoteDebuggerInfo) info).getPassword();
    else return (info.getStopClassName() != null) ? info.getStopClassName() : info.getClassName();
  }

  /** @return name of location for given DebuggerInfo. */
  public String getLocationName(DebuggerInfo info) {
    String n;
    if (info instanceof RemoteDebuggerInfo) return ((RemoteDebuggerInfo) info).getHostName();
    else return "localhost";
  }

  /** Returns false, ToolsDebugger does not support evaluation of expressions. */
  public boolean supportsExpressions() {
    return false;
  }

  // breakpoints ........................

  /** Returns events available for this version of debugger. */
  public CoreBreakpoint.Event[] getBreakpointEvents() {
    return breakpointEvents;
  }

  /** Returns actions available for this version of debugger. */
  public CoreBreakpoint.Action[] getBreakpointActions() {
    return breakpointActions;
  }

  // threads ........................

  /** Returns root of all threads. */
  public AbstractThreadGroup getThreadGroupRoot() {
    return threadGroup;
  }

  /** Returns current thread or null. */
  public AbstractThread getCurrentThread() {
    return currentThread;
  }

  /** Sets current thread. If thread is null, unsets curent thread. */
  public void setCurrentThread(AbstractThread thread) {
    if (currentThread == thread) return;
    Object old = currentThread;
    currentThread = (ToolsThread) thread;
    firePropertyChange(PROP_CURRENT_THREAD, old, thread);
  }

  // support for multisession debugging
  // ................................................................

  /** Disconnects from running debugged process. */
  public void disconnect() throws DebuggerException {
    threadGroup.setRemoteThreadGroup(null);
    if (remoteDebugger != null) {
      remoteDebugger.close();
      remoteDebugger = null;
    }
    if (debuggerThread != null) {
      debuggerThread.interrupt();
      debuggerThread.stop();
    }
    super.finishDebugger();
    synchronizer = null;
  }

  /** Reconnects to disconnected Virtual Machine. */
  public void reconnect() throws DebuggerException {
    startDebugger(new ReconnectDebuggerInfo(hostName, password));
  }

  /**
   * Adds breakpoint on the method specified from the RemoteDebugger. Is called from CoreBreakpoint
   * only.
   *
   * @return true if breakpoint is valid.
   */
  boolean addBreakpoint(final String className, final String method) {
    if (synchronizer == null) return false;
    try {
      return ((Boolean)
              new Protector("AbstractDebugger.addBreakpoint1") { // NOI18N
                public Object protect() throws Exception {
                  RemoteClass cls = remoteDebugger.findClass(className);
                  if (cls == null) return new Integer(0);
                  RemoteField m = cls.getMethod(method);
                  if (m == null) return new Integer(0);
                  String s = cls.setBreakpointMethod(m);
                  if (s.trim().equals("")) new Integer(1); // NOI18N
                  println(bundle.getString("CTL_Cannot_set_breakpoint") + ": " + s, ERR_OUT);
                  return new Boolean(false);
                }
              }.throwAndWait(synchronizer, killer))
          .booleanValue();
    } catch (Exception e) {
      return false;
    }
  }

  /**
   * Removes breakpoint on the method specified from the RemoteDebugger. Is called from
   * CoreBreakpoint only.
   */
  boolean removeBreakpoint(final String className, final String method) {
    if (synchronizer == null) return false;

    try {
      return ((Boolean)
              new Protector("AbstractDebugger.removeBreakpoint") { // NOI18N
                public Object protect() throws Exception {
                  RemoteClass cls = remoteDebugger.findClass(className);
                  if (cls == null) return new Boolean(false);
                  RemoteField m = cls.getMethod(method);
                  if (m == null) return new Boolean(false);
                  String s = cls.clearBreakpointMethod(m);
                  if (s.trim().equals("")) return new Boolean(true); // NOI18N
                  println(bundle.getString("CTL_Cannot_clear_breakpoint") + ": " + s, ERR_OUT);
                  return new Boolean(false);
                }
              }.throwAndWait(synchronizer, killer))
          .booleanValue();
    } catch (Exception e) {
      return false;
    }
  }

  /** Sets current line in editor. */
  Line getLine(final RemoteStackFrame stackFrame) {
    return (Line)
        new Protector("AbstractDebugger.showInEditor") { // NOI18N
          public Object protect() throws Exception {
            try {
              return Utils.getLineForSource(
                  stackFrame.getRemoteClass().getName(),
                  stackFrame.getRemoteClass().getSourceFileName(),
                  stackFrame.getLineNumber());
            } catch (Throwable e) {
              if (e instanceof ThreadDeath) throw (ThreadDeath) e;
            }
            return null;
          }
        }.wait(synchronizer, killer);
  }

  /** Performs stop action. */
  void stop(boolean stop, final AbstractThread thread) {
    /*
        int lastAction = null;
        int currentStackDepth = 0;
        int lastStackDepth = 0;
        if (!stop) { // obtain values only if they are really needed
          lastAction = ((ToolsThread)thread).getLastAction ();
          lastStackDepth = ((ToolsThread)thread).getLastStackDepth ();
          try {
            currentStackDepth = ((ToolsThread)thread).getStackDepth ();
          }
          catch (DebuggerException e) {
            currentStackDepth = lastStackDepth + 1; // the condition in the following 'if' will be false
          }
    }
        if (stop || (lastAction == ACTION_TRACE_INTO) ||
          ((lastAction == ACTION_TRACE_OVER) && (currentStackDepth <= lastStackDepth)) ||
          ((lastAction == ACTION_STEP_OUT) && (currentStackDepth < lastStackDepth))
          ) {
        */
    if (stop) {
      setLastAction(ACTION_BREAKPOINT_HIT);
      setDebuggerState(DEBUGGER_STOPPED);
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              try {
                threadGroup.threadChanged();
                // 'thread' could be created by Event.getThread (), but we must use a ToolsThread
                // created by threadChanged method (line above)
                AbstractThread tt = threadGroup.getThread(((ToolsThread) thread).getRemoteThread());
                if (tt == null) tt = thread;
                // **************************************************************************************
                tt.setCurrent(true);
                ((ToolsThread) tt).setLastAction(ACTION_BREAKPOINT_HIT);
              } catch (Throwable e) {
                if (e instanceof ThreadDeath) throw (ThreadDeath) e;
                println(bundle.getString("EXC_Debugger") + ": " + e, ERR_OUT); // NOI18N
                return;
              }
              updateWatches();
            }
          });
    } else {
      int lastAction = ((ToolsThread) thread).getLastAction();
      if ((lastAction == ACTION_TRACE_OVER) || (lastAction == ACTION_STEP_OUT))
        new Protector("AbstractDebugger.stepOut") { // NOI18N
          public Object protect() throws Exception {
            ((ToolsThread) thread).getRemoteThread().stepOut();
            return null;
          }
        }.go(synchronizer, killer);
      else {
        new Protector("AbstractDebugger.go") { // NOI18N
          public Object protect() throws Exception {
            remoteDebugger.cont();
            return null;
          }
        }.go(synchronizer, killer);
      }
    }
  }

  // innerclasses ......................................................................

  private class ReconnectDebuggerInfo extends RemoteDebuggerInfo {
    private ReconnectDebuggerInfo(String hostName, String password) {
      super(hostName, password);
    }
  }

  private class Connector extends JPanel implements DebuggerInfoProducer {

    private JTextField tfHost;
    private JTextField tfPassword;

    private Connector() {
      setLayout(new GridBagLayout());
      //      setBorder (new EmptyBorder (8, 8, 8, 8));
      GridBagConstraints c = new GridBagConstraints();

      c.insets = new Insets(0, 0, 3, 3);
      c.anchor = GridBagConstraints.WEST;
      add(new JLabel(bundle.getString("CTL_HostName")), c);

      tfHost = new JTextField(host, 25);
      c = new GridBagConstraints();
      c.gridwidth = 0;
      c.insets = new Insets(0, 3, 3, 0);
      c.fill = java.awt.GridBagConstraints.HORIZONTAL;
      c.weightx = 1.0;
      add(tfHost, c);

      c = new GridBagConstraints();
      c.insets = new Insets(3, 0, 0, 3);
      c.anchor = GridBagConstraints.WEST;
      add(new JLabel(bundle.getString("CTL_Password")), c);

      tfPassword = new JTextField(25);
      c = new GridBagConstraints();
      c.gridwidth = 0;
      c.fill = java.awt.GridBagConstraints.HORIZONTAL;
      c.weightx = 1.0;
      c.insets = new Insets(3, 3, 0, 0);
      add(tfPassword, c);

      c = new GridBagConstraints();
      c.fill = java.awt.GridBagConstraints.BOTH;
      c.weighty = 1.0;
      JPanel p = new JPanel();
      p.setPreferredSize(new Dimension(1, 1));
      add(p, c);
    }

    /** Returns DebuggerInfo. */
    public DebuggerInfo getDebuggerInfo() {
      return new RemoteDebuggerInfo(tfHost.getText(), tfPassword.getText());
    }
  }
}