/**
   * returns an array of strings of all selected targets, or null if nothing is selected
   *
   * @return array of string of targets
   */
  private String[] getSelectedTargetsAsArray() {
    TreePath[] paths = this.getSelectionPaths();
    if (paths == null) {
      return null;
    }

    String targets[] = new String[paths.length];
    for (int i = 0; i < paths.length; i++) {
      TreePath path = paths[i];
      DefaultMutableTreeNode dmtn = (DefaultMutableTreeNode) path.getLastPathComponent();
      SVNTreeNodeData svnNode = (SVNTreeNodeData) dmtn.getUserObject();
      targets[i] =
          (ConfigurationManager.getInstance().getConfig().getWorkingDirectory())
              + svnNode.getPath();
    }
    return targets;
  }
 /**
  * returns a default mutable tree representation of a given working copy node
  *
  * @param svnTreeNode mode of an svn working copy model
  * @return root DefaultMutableTreeNode of the given model
  */
 public static DefaultMutableTreeNode buildTreeNode(SVNTreeNodeData svnTreeNode) {
   DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(svnTreeNode);
   for (Iterator iterator = svnTreeNode.getChildren().iterator(); iterator.hasNext(); ) {
     SVNTreeNodeData svnTreeNodeData = (SVNTreeNodeData) iterator.next();
     DefaultMutableTreeNode newNode = new DefaultMutableTreeNode();
     newNode.setUserObject(svnTreeNodeData);
     DefaultMutableTreeNode newChild = buildTreeNode(svnTreeNodeData);
     if (newChild != null) {
       rootNode.add(newChild);
     }
   }
   return rootNode;
 }
 /**
  * recursively refreshes a given node within the tree
  *
  * @param node node at which to begin the resursive refresh
  * @param online indicator as to whether an online refresh should be performed
  */
 private void refresh(final DefaultMutableTreeNode node, final boolean online) {
   SVNTreeNodeData nodeData = (SVNTreeNodeData) node.getUserObject();
   // if a userObject doesn't exist for the given node, then just return
   if (nodeData == null) {
     return;
   }
   final String workingCopy =
       ConfigurationManager.getInstance().getConfig().getWorkingDirectory() + nodeData.getPath();
   // since building SVNTreeModel and buildTreeNode can take some time, we'll execute them in a
   // separate thread
   // so that we can continue updating the UI in realtime.
   Thread t =
       new Thread(
           new Runnable() {
             public void run() {
               SwingUtilities.invokeLater(
                   new Runnable() {
                     public void run() {
                       Application.getApplicationFrame()
                           .processJSVNEvent(new JSVNStatusEvent(JSVNStatusEvent.REFRESHING));
                     }
                   });
               SVNTreeModel partialModel = new SVNTreeModel(workingCopy, online);
               DefaultMutableTreeNode foo = buildTreeNode(partialModel);
               // reconcile updated tree with current tree
               reconcileNodes(node, foo);
               SwingUtilities.invokeLater(
                   new Runnable() {
                     public void run() {
                       Application.getApplicationFrame()
                           .processJSVNEvent(new JSVNStatusEvent(JSVNStatusEvent.READY));
                     }
                   });
             }
           });
   t.start();
 }
  /**
   * returns a string of all selected targets separated by spaces, or null if nothing is selected
   *
   * @return space-separated string of targets
   */
  private String getSelectedTargetsAsString() {
    TreePath[] paths = this.getSelectionPaths();
    if (paths == null) {
      return null;
    }

    StringBuffer targets = new StringBuffer();
    for (int i = 0; i < paths.length; i++) {
      TreePath path = paths[i];
      DefaultMutableTreeNode dmtn = (DefaultMutableTreeNode) path.getLastPathComponent();
      SVNTreeNodeData svnNode = (SVNTreeNodeData) dmtn.getUserObject();
      String target =
          ConfigurationManager.getInstance().getConfig().getWorkingDirectory() + svnNode.getPath();

      // if target contains a space, wrap the target in quotes (eg: c:\Document and Settings\...)
      if (target.indexOf(SPACE) > -1) {
        targets.append(DOUBLE_QUOTE).append(target).append(DOUBLE_QUOTE);
      } else {
        targets.append(target);
      }
      targets.append(" ");
    }
    return targets.toString().trim();
  }
  /**
   * Performs a "live-update" to the original DefaultMutableTreeNode by reconsiling the differences
   * between it and a given updated DefaultMutableTreeNode. The resulting original
   * DefaultMutableTreeNode will look identical to the updated node. Appropriate events will fire to
   * keep the GUI consistant with any changes that were made to the model.
   *
   * @param original the model's existing node
   * @param updated the node representing the model node's new state
   */
  private void reconcileNodes(DefaultMutableTreeNode original, DefaultMutableTreeNode updated) {
    SVNTreeNodeData originalData = (SVNTreeNodeData) original.getUserObject();
    SVNTreeNodeData updatedData = (SVNTreeNodeData) updated.getUserObject();
    if (!originalData.equals(updatedData)) {
      originalData.copyNodeValues(updatedData);
    }
    int originalChildCount = original.getChildCount();
    int insertionPoint = 0;

    // loop through the original node's children
    for (int i = insertionPoint; i < originalChildCount; i++) {
      // loop through remaining updated node's children
      boolean found = false;
      DefaultMutableTreeNode originalChildNode =
          (DefaultMutableTreeNode) original.getChildAt(insertionPoint);
      SVNTreeNodeData originalChildData = (SVNTreeNodeData) originalChildNode.getUserObject();
      while (updated.children().hasMoreElements()) {
        DefaultMutableTreeNode updatedChildNode =
            (DefaultMutableTreeNode) updated.children().nextElement();
        SVNTreeNodeData updatedChildData = (SVNTreeNodeData) updatedChildNode.getUserObject();
        if (originalChildData.getPath().equals(updatedChildData.getPath())) {
          // same resource, check equality
          insertionPoint++;
          found = true;
          // remove from updated list so we know we handled this node
          updated.remove(updatedChildNode);
          if (!originalChildData.equals(updatedChildData)) {
            // same resource, different values -- update
            originalChildData.copyNodeValues(updatedChildData);
            reconcileNodes(originalChildNode, updatedChildNode);
            ((DefaultTreeModel) this.getModel()).nodeChanged(originalChildNode);
            break;
          } else {
            // same values, do nothing
            reconcileNodes(originalChildNode, updatedChildNode);
            break;
          }
        } else {
          // different resources
          // does updated node's child need to be inserted before original node's child?
          if (updatedChildData.getPath().compareTo(originalChildData.getPath()) < 0) {
            original.insert(updatedChildNode, insertionPoint);
            ((DefaultTreeModel) this.getModel())
                .nodesWereInserted(original, new int[] {insertionPoint});
            insertionPoint++;
            // inserting the node into the original has the side-effect of removing it from the
            // updated
            // node, just keep going until we've handled all children, or until the remaining
            // children
            // come after the last original node
          } else {
            break;
          }
        }
      }
      if (!found) {
        original.remove(insertionPoint);
        ((DefaultTreeModel) this.getModel())
            .nodesWereRemoved(
                original, new int[] {insertionPoint}, new Object[] {originalChildNode});
      }
    }
    // add any remaining new child nodes
    while (updated.children().hasMoreElements()) {
      DefaultMutableTreeNode updatedChildNode =
          (DefaultMutableTreeNode) updated.children().nextElement();
      original.add(updatedChildNode);
      // adding the new node to the original has the side-effect of removing it from the updated
      // node's children
      // so we just keep going until no children remain
      ((DefaultTreeModel) this.getModel())
          .nodesWereInserted(original, new int[] {insertionPoint++});
    }
  }
 /** @param ae */
 public void actionPerformed(ActionEvent ae) {
   String targets = getSelectedTargetsAsString();
   if (targets == null) {
     // user hasn't selected anything -- pop-up a error message
   } else {
     if (ae.getActionCommand().equals(ACTION_ADD)) {
       // process add request
       Commandable command = new Add();
       Map args = new HashMap();
       args.put(Add.TARGETS, targets);
       executeCommand(command, args);
     } else if (ae.getActionCommand().equals(ACTION_CAT)) {
       // process cat request
       CommandDialog dialog = new CatDialog(Application.getApplicationFrame(), true);
       initializeAndShowDialog(dialog, targets);
     } else if (ae.getActionCommand().equals(ACTION_CHECKOUT)) {
       // process checkout request
       CommandDialog dialog = new CheckoutDialog(Application.getApplicationFrame(), true);
       initializeAndShowDialog(dialog, targets);
     } else if (ae.getActionCommand().equals(ACTION_CLEANUP)) {
       // check that all targets are directories
       TreePath[] paths = this.getSelectionPaths();
       for (int i = 0; i < paths.length; i++) {
         TreePath path = paths[i];
         DefaultMutableTreeNode dmtn = (DefaultMutableTreeNode) path.getLastPathComponent();
         SVNTreeNodeData svnNode = (SVNTreeNodeData) dmtn.getUserObject();
         if (svnNode.getNodeKind() != SVNTreeNodeData.NODE_KIND_DIRECTORY) {
           showMessageDialog(CLEANUP_ERROR + svnNode.getPath());
           return;
         }
       }
       // process cleanup request
       Commandable command = new Cleanup();
       Map args = new HashMap();
       args.put(Cleanup.TARGETS, targets);
       executeCommand(command, args);
     } else if (ae.getActionCommand().equals(ACTION_COMMIT)) {
       // process commit request
       CommandDialog dialog = new CommitDialog(Application.getApplicationFrame(), true);
       initializeAndShowDialog(dialog, targets);
     } else if (ae.getActionCommand().equals(ACTION_COPY)) {
       // process copy request
       CommandDialog dialog = new CopyDialog(Application.getApplicationFrame(), true);
       initializeAndShowDialog(dialog, targets);
     } else if (ae.getActionCommand().equals(ACTION_DELETE)) {
       // process delete request
       Commandable command = new Delete();
       Map args = new HashMap();
       args.put(Delete.TARGETS, targets);
       executeCommand(command, args);
     } else if (ae.getActionCommand().equals(ACTION_DIFF)) {
       // process diff request
       CommandDialog dialog = new DiffDialog(Application.getApplicationFrame(), true);
       initializeAndShowDialog(dialog, targets);
     } else if (ae.getActionCommand().equals(ACTION_INFO)) {
       // process info request
       Commandable command = new Info();
       Map args = new HashMap();
       args.put(Info.TARGETS, targets);
       executeCommand(command, args);
     } else if (ae.getActionCommand().equals(ACTION_LOG)) {
       // process log request
       CommandDialog dialog = new LogDialog(Application.getApplicationFrame(), true);
       initializeAndShowDialog(dialog, targets);
     } else if (ae.getActionCommand().equals(ACTION_LS)) {
       // process log request
       CommandDialog dialog = new LsDialog(Application.getApplicationFrame(), true);
       initializeAndShowDialog(dialog, targets);
     } else if (ae.getActionCommand().equals(ACTION_MERGE)) {
       // process merge request
       CommandDialog dialog = new MergeDialog(Application.getApplicationFrame(), true);
       initializeAndShowDialog(dialog, targets);
     } else if (ae.getActionCommand().equals(ACTION_MOVE)) {
       // process move request
       CommandDialog dialog = new MoveDialog(Application.getApplicationFrame(), true);
       initializeAndShowDialog(dialog, targets);
     } else if (ae.getActionCommand().equals(ACTION_REFRESH_ONLINE)) {
       // process recursive online refresh request
       refreshSelectedTargets(true);
     } else if (ae.getActionCommand().equals(ACTION_REFRESH_OFFLINE)) {
       // process recursive offline refresh request
       refreshSelectedTargets(false);
     } else if (ae.getActionCommand().equals(ACTION_RESOLVE)) {
       // process resolve request
       String message = RESOLVE_WARNING;
       String[] targetArray = getSelectedTargetsAsArray();
       for (int i = 0; i < targetArray.length; i++) {
         String s = targetArray[i];
         message += s + Constants.NEWLINE;
       }
       int confirmation =
           JOptionPane.showConfirmDialog(
               this, message, CONFIRMATION, JOptionPane.OK_CANCEL_OPTION);
       if (confirmation == JOptionPane.OK_OPTION) {
         // process add request
         Commandable command = new Resolve();
         Map args = new HashMap();
         args.put(Resolve.TARGETS, targets);
         executeCommand(command, args);
       }
     } else if (ae.getActionCommand().equals(ACTION_REVERT)) {
       // process revert request
       String message = REVERT_WARNING;
       String[] targetArray = getSelectedTargetsAsArray();
       for (int i = 0; i < targetArray.length; i++) {
         String s = targetArray[i];
         message += s + Constants.NEWLINE;
       }
       int confirmation =
           JOptionPane.showConfirmDialog(
               this, message, CONFIRMATION, JOptionPane.OK_CANCEL_OPTION);
       if (confirmation == JOptionPane.OK_OPTION) {
         // process add request
         Commandable command = new Revert();
         Map args = new HashMap();
         args.put(Revert.TARGETS, targets);
         executeCommand(command, args);
       }
     } else if (ae.getActionCommand().equals(ACTION_STATUS)) {
       // process status request
       CommandDialog dialog = new StatusDialog(Application.getApplicationFrame(), true);
       initializeAndShowDialog(dialog, targets);
     } else if (ae.getActionCommand().equals(ACTION_SWITCH)) {
       // process switch request
       CommandDialog dialog = new SwitchDialog(Application.getApplicationFrame(), true);
       initializeAndShowDialog(dialog, targets);
     } else if (ae.getActionCommand().equals(ACTION_UPDATE)) {
       // process update request
       CommandDialog dialog = new UpdateDialog(Application.getApplicationFrame(), true);
       initializeAndShowDialog(dialog, targets);
     }
   }
 }