Group targets listed in IntelliJ by type.

Summary:
Replace JList with JTree in order to group targets by type.
Unit tests and other targets are grouped into two separate groups.
Different types of targets have different icons.

Test Plan: Launch plugin and see targets list.
diff --git a/plugin/src/com/facebook/buck/plugin/intellij/BuckPluginComponent.java b/plugin/src/com/facebook/buck/plugin/intellij/BuckPluginComponent.java
index 5beb3fe..2b0cdf6 100644
--- a/plugin/src/com/facebook/buck/plugin/intellij/BuckPluginComponent.java
+++ b/plugin/src/com/facebook/buck/plugin/intellij/BuckPluginComponent.java
@@ -148,6 +148,7 @@
    * @param target Specified target to build
    */
   public void buildTarget(final BuckTarget target) {
+    Preconditions.checkNotNull(target);
     try {
       final BuckRunner buckRunner = getBuckRunner();
       Task.Backgroundable task = new Task.Backgroundable(project,
diff --git a/plugin/src/com/facebook/buck/plugin/intellij/BuckTarget.java b/plugin/src/com/facebook/buck/plugin/intellij/BuckTarget.java
index 28dce91..59e8d09 100644
--- a/plugin/src/com/facebook/buck/plugin/intellij/BuckTarget.java
+++ b/plugin/src/com/facebook/buck/plugin/intellij/BuckTarget.java
@@ -16,6 +16,7 @@
 
 package com.facebook.buck.plugin.intellij;
 
+import com.facebook.buck.plugin.intellij.ui.TargetNode;
 import com.google.common.base.Preconditions;
 
 public class BuckTarget {
@@ -50,4 +51,18 @@
   public String getBasePath() {
     return basePath;
   }
+
+  public TargetNode createTreeNode() {
+    TargetNode.Type nodeType;
+    if ("java_library".equals(type)) {
+      nodeType = TargetNode.Type.JAVA_LIBRARY;
+    } else if ("java_binary".equals(type)) {
+      nodeType = TargetNode.Type.JAVA_BINARY;
+    } else if ("java_test".equals(type)) {
+      nodeType = TargetNode.Type.JAVA_TEST;
+    } else {
+      nodeType = TargetNode.Type.OTHER;
+    }
+    return new TargetNode(nodeType, getFullName(), this);
+  }
 }
diff --git a/plugin/src/com/facebook/buck/plugin/intellij/ui/BuckTargetsPanel.form b/plugin/src/com/facebook/buck/plugin/intellij/ui/BuckTargetsPanel.form
index daa760d..bff79b4 100644
--- a/plugin/src/com/facebook/buck/plugin/intellij/ui/BuckTargetsPanel.form
+++ b/plugin/src/com/facebook/buck/plugin/intellij/ui/BuckTargetsPanel.form
@@ -36,14 +36,14 @@
           </component>
         </children>
       </toolbar>
-      <scrollpane id="8bbd6">
+      <scrollpane id="8bbd6" binding="scrollPane" custom-create="true">
         <constraints>
           <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
         </constraints>
         <properties/>
         <border type="none"/>
         <children>
-          <component id="42c00" class="javax.swing.JList" binding="targetsList" custom-create="true">
+          <component id="12f06" class="javax.swing.JTree" binding="tree" custom-create="true">
             <constraints/>
             <properties/>
           </component>
diff --git a/plugin/src/com/facebook/buck/plugin/intellij/ui/BuckTargetsPanel.java b/plugin/src/com/facebook/buck/plugin/intellij/ui/BuckTargetsPanel.java
index bfe2ce8..8feec54 100644
--- a/plugin/src/com/facebook/buck/plugin/intellij/ui/BuckTargetsPanel.java
+++ b/plugin/src/com/facebook/buck/plugin/intellij/ui/BuckTargetsPanel.java
@@ -19,35 +19,42 @@
 import com.facebook.buck.plugin.intellij.BuckPluginComponent;
 import com.facebook.buck.plugin.intellij.BuckTarget;
 import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.intellij.ui.components.JBList;
+import com.intellij.ui.treeStructure.Tree;
 
-import java.awt.Component;
 import java.awt.EventQueue;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
-import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
 
-import javax.swing.DefaultListCellRenderer;
 import javax.swing.JButton;
-import javax.swing.JComponent;
-import javax.swing.JList;
 import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
 import javax.swing.SwingUtilities;
 import javax.swing.event.AncestorEvent;
 import javax.swing.event.AncestorListener;
+import javax.swing.event.MouseInputAdapter;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreePath;
 
 public class BuckTargetsPanel {
 
-  public static String DISPLAY_NAME = "Targets";
+  public static final String DISPLAY_NAME = "Targets";
+  public static final String TREE_ROOT = "Buck";
+  public static final String TARGETS_ROOT = "Targets";
+  public static final String TESTS_ROOT = "Tests";
 
   private final BuckPluginComponent component;
   private JPanel targetsPanel;
-  private JList targetsList;
   private JButton refreshTargetsButton;
   private JButton cleanButton;
+  private JTree tree;
+  @SuppressWarnings("unused")
+  private JScrollPane scrollPane;
+  private DefaultTreeModel treeModel;
+  private TargetNode treeRoot;
+  private TargetNode targetsRoot;
+  private TargetNode testsRoot;
 
   public BuckTargetsPanel(BuckPluginComponent component) {
     this.component = Preconditions.checkNotNull(component);
@@ -78,8 +85,30 @@
       }
     });
 
-    targetsList = Preconditions.checkNotNull(createTargetsList());
+    // Tree
+    treeRoot = new TargetNode(TargetNode.Type.DIRECTORY, TREE_ROOT, null);
+    targetsRoot = new TargetNode(TargetNode.Type.DIRECTORY, TARGETS_ROOT, null);
+    testsRoot = new TargetNode(TargetNode.Type.DIRECTORY, TESTS_ROOT, null);
+    tree = new Tree(new DefaultTreeModel(treeRoot));
+    scrollPane = TargetsTreeRenderer.install(tree);
 
+    final MouseInputAdapter mouseListener = new MouseInputAdapter() {
+      @Override
+      public void mouseClicked(MouseEvent e) {
+        if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) {
+          // Double click
+          TreePath selected = tree.getSelectionPath();
+          TargetNode targetNode = (TargetNode) selected.getLastPathComponent();
+          BuckTarget target = targetNode.getTarget();
+          if (target != null) {
+            component.buildTarget(target);
+          }
+        }
+      }
+    };
+    tree.addMouseListener(mouseListener);
+
+    // Buttons
     refreshTargetsButton = createToolbarIcon();
     refreshTargetsButton.addActionListener(new ActionListener() {
       @Override
@@ -102,46 +131,24 @@
     return button;
   }
 
-  private JList createTargetsList() {
-    final JBList targetsList = new JBList(new TargetsListModel(ImmutableList.<BuckTarget>of()));
-    targetsList.setCellRenderer(new DefaultListCellRenderer() {
-      @Override
-      public Component getListCellRendererComponent(JList list,
-                                                    Object value,
-                                                    int index,
-                                                    boolean isSelected,
-                                                    boolean cellHasFocus) {
-        BuckTarget target = (BuckTarget) value;
-        Component renderer = super.getListCellRendererComponent(list,
-            target.getFullName(),
-            index,
-            isSelected,
-            cellHasFocus
-        );
-        ((JComponent) renderer).setToolTipText(target.getFullName());
-        return renderer;
-      }
-    });
-
-    final MouseListener mouseListener = new MouseAdapter() {
-      @Override
-      public void mouseClicked(MouseEvent e) {
-        if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) {
-          // Double click
-          BuckTarget target = (BuckTarget) targetsList.getSelectedValue();
-          component.buildTarget(target);
-        }
-      }
-    };
-    targetsList.addMouseListener(mouseListener);
-    return targetsList;
-  }
-
   public void updateTargets() {
     EventQueue.invokeLater(new Runnable() {
       @Override
       public void run() {
-        targetsList.setModel(new TargetsListModel(component.getTargets()));
+        treeModel = new DefaultTreeModel(treeRoot);
+        treeModel.insertNodeInto(targetsRoot, treeRoot, treeRoot.getChildCount());
+        treeModel.insertNodeInto(testsRoot, treeRoot, treeRoot.getChildCount());
+        for (BuckTarget target : component.getTargets()) {
+          TargetNode node = target.createTreeNode();
+          TargetNode parent;
+          if (node.getType() == TargetNode.Type.JAVA_TEST) {
+            parent = testsRoot;
+          } else {
+            parent = targetsRoot;
+          }
+          treeModel.insertNodeInto(node, parent, parent.getChildCount());
+        }
+        tree.setModel(treeModel);
       }
     });
   }
diff --git a/plugin/src/com/facebook/buck/plugin/intellij/ui/TargetNode.java b/plugin/src/com/facebook/buck/plugin/intellij/ui/TargetNode.java
new file mode 100644
index 0000000..1ea73cf
--- /dev/null
+++ b/plugin/src/com/facebook/buck/plugin/intellij/ui/TargetNode.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013-present Facebook, Inc.
+ *
+ * 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.facebook.buck.plugin.intellij.ui;
+
+import com.facebook.buck.plugin.intellij.BuckTarget;
+import com.google.common.base.Preconditions;
+
+import javax.annotation.Nullable;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+public class TargetNode extends DefaultMutableTreeNode {
+
+  public enum Type {
+    DIRECTORY,
+    JAVA_LIBRARY,
+    JAVA_BINARY,
+    JAVA_TEST,
+    OTHER
+  }
+
+  private final BuckTarget target;
+
+  private final String name;
+  private final Type type;
+
+  public TargetNode(Type type, String name, @Nullable BuckTarget target) {
+    super();
+    this.name = Preconditions.checkNotNull(name);
+    this.type = Preconditions.checkNotNull(type);
+    this.target = target;
+  }
+
+  public Type getType() {
+    return type;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public BuckTarget getTarget() {
+    return target;
+  }
+}
diff --git a/plugin/src/com/facebook/buck/plugin/intellij/ui/TargetsListModel.java b/plugin/src/com/facebook/buck/plugin/intellij/ui/TargetsListModel.java
deleted file mode 100644
index 0cd0e42..0000000
--- a/plugin/src/com/facebook/buck/plugin/intellij/ui/TargetsListModel.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2013-present Facebook, Inc.
- *
- * 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.facebook.buck.plugin.intellij.ui;
-
-import com.facebook.buck.plugin.intellij.BuckTarget;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-
-import javax.swing.AbstractListModel;
-
-public class TargetsListModel extends AbstractListModel {
-
-  private ImmutableList<BuckTarget> targets;
-
-  public TargetsListModel(ImmutableList<BuckTarget> targets) {
-    Preconditions.checkNotNull(targets);
-    setTargets(targets);
-  }
-
-  public void setTargets(ImmutableList<BuckTarget> targets) {
-    Preconditions.checkNotNull(targets);
-    this.targets = ImmutableList.copyOf(targets);
-    fireContentsChanged(this, 0 /* start index */ , this.targets.size() + 1 /* end index */);
-  }
-
-  @Override
-  public int getSize() {
-    return targets.size();
-  }
-
-  @Override
-  public Object getElementAt(int index) {
-    return targets.get(index);
-  }
-}
diff --git a/plugin/src/com/facebook/buck/plugin/intellij/ui/TargetsTreeRenderer.java b/plugin/src/com/facebook/buck/plugin/intellij/ui/TargetsTreeRenderer.java
new file mode 100644
index 0000000..129a290
--- /dev/null
+++ b/plugin/src/com/facebook/buck/plugin/intellij/ui/TargetsTreeRenderer.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013-present Facebook, Inc.
+ *
+ * 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.facebook.buck.plugin.intellij.ui;
+
+import com.google.common.base.Strings;
+import com.intellij.icons.AllIcons;
+import com.intellij.ui.IdeBorderFactory;
+import com.intellij.ui.MultilineTreeCellRenderer;
+import com.intellij.ui.SideBorder;
+
+import javax.swing.Icon;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+
+class TargetsTreeRenderer extends MultilineTreeCellRenderer {
+
+  private TargetsTreeRenderer() {
+  }
+
+  public static JScrollPane install(JTree tree) {
+    JScrollPane scrollPane = installRenderer(tree, new TargetsTreeRenderer());
+    scrollPane.setBorder(IdeBorderFactory.createBorder(SideBorder.LEFT));
+    return scrollPane;
+  }
+
+  @Override
+  protected void initComponent(JTree tree,
+                               Object value,
+                               boolean selected,
+                               boolean expanded,
+                               boolean leaf,
+                               int row,
+                               boolean hasFocus) {
+    if (value instanceof TargetNode) {
+      TargetNode node = (TargetNode) value;
+      String[] text = new String[] {node.getName()};
+      Icon icon;
+      switch (node.getType()) {
+        case DIRECTORY:
+          icon = AllIcons.Hierarchy.Base;
+          break;
+        case JAVA_LIBRARY:
+          icon = AllIcons.Modules.Library;
+          break;
+        case JAVA_BINARY:
+          icon = AllIcons.RunConfigurations.Application;
+          break;
+        case JAVA_TEST:
+          icon = AllIcons.RunConfigurations.Junit;
+          break;
+        case OTHER:
+          icon = AllIcons.General.ExternalTools;
+          break;
+        default:
+          icon = AllIcons.General.Error;
+      }
+      setText(text, "");
+      setIcon(icon);
+    } else {
+      String[] text = new String[] {value == null ? "" : value.toString()};
+      text[0] = Strings.nullToEmpty(text[0]);
+      setText(text, null);
+      setIcon(null);
+    }
+  }
+}