Fixed minor bugs in the label of certain proto elements displayed in the
Outline View. Code cleanup. Added more tests.
diff --git a/com.google.eclipse.protobuf.ui.functional.test/src/com/google/eclipse/protobuf/ui/labeling/Images_imageFor_Test.java b/com.google.eclipse.protobuf.ui.functional.test/src/com/google/eclipse/protobuf/ui/labeling/Images_imageFor_Test.java
index 7cdf980..87a8d9d 100644
--- a/com.google.eclipse.protobuf.ui.functional.test/src/com/google/eclipse/protobuf/ui/labeling/Images_imageFor_Test.java
+++ b/com.google.eclipse.protobuf.ui.functional.test/src/com/google/eclipse/protobuf/ui/labeling/Images_imageFor_Test.java
@@ -265,4 +265,10 @@
     assertThat(image, equalTo("syntax.gif"));
     assertThat(image, existsInProject());
   }
+
+  @Test public void should_contain_default_image() {
+    String image = images.defaultImage();
+    assertThat(image, equalTo("empty.gif"));
+    assertThat(image, existsInProject());
+  }
 }
\ No newline at end of file
diff --git a/com.google.eclipse.protobuf.ui.functional.test/src/com/google/eclipse/protobuf/ui/labeling/Labels_labelFor_Test.java b/com.google.eclipse.protobuf.ui.functional.test/src/com/google/eclipse/protobuf/ui/labeling/Labels_labelFor_Test.java
new file mode 100644
index 0000000..1d2f097
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui.functional.test/src/com/google/eclipse/protobuf/ui/labeling/Labels_labelFor_Test.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2012 Google Inc.
+ *
+ * All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse
+ * Public License v1.0 which accompanies this distribution, and is available at
+ *
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package com.google.eclipse.protobuf.ui.labeling;
+
+import static com.google.eclipse.protobuf.junit.core.XtextRule.createWith;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+
+import org.eclipse.jface.viewers.StyledString;
+import org.junit.*;
+
+import com.google.eclipse.protobuf.junit.core.XtextRule;
+import com.google.eclipse.protobuf.protobuf.*;
+import com.google.eclipse.protobuf.ui.plugin.ProtobufEditorPlugIn;
+import com.google.inject.Inject;
+
+/**
+ * Tests for <code>{@link Labels#labelFor(Object)}</code>.
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class Labels_labelFor_Test {
+  @Rule public XtextRule xtext = createWith(ProtobufEditorPlugIn.injector());
+
+  @Inject private Labels labels;
+
+  // syntax = "proto2";
+  //
+  // option optimize_for = SPEED;
+  @Test public void should_return_label_for_native_option() {
+    NativeOption option = xtext.find("optimize_for", NativeOption.class);
+    Object label = labels.labelFor(option);
+    assertThat(label, instanceOf(String.class));
+    String labelText = (String) label;
+    assertThat(labelText, equalTo("optimize_for"));
+  }
+
+  // syntax = "proto2";
+  //
+  // import 'google/protobuf/descriptor.proto';
+  //
+  // message Type {
+  //   optional double code = 1;
+  //   optional string name = 2;
+  //   extensions 10 to max;
+  // }
+  //
+  // extend Type {
+  //   optional bool active = 10;
+  // }
+  //
+  // extend google.protobuf.FileOptions {
+  //   optional Type type = 1000;
+  // }
+  //
+  // option (type).(active) = true;
+  @Test public void should_return_label_for_custom_option() {
+    CustomOption option = xtext.find("type", ")", CustomOption.class);
+    Object label = labels.labelFor(option);
+    assertThat(label, instanceOf(String.class));
+    String labelText = (String) label;
+    assertThat(labelText, equalTo("(type).(active)"));
+  }
+
+  // syntax = "proto2";
+  //
+  // message Type {
+  //   extensions 1 to 10, 20 to 30, 100 to max;
+  // }
+  @Test public void should_return_label_for_extensions() {
+    Extensions extensions = xtext.findFirst(Extensions.class);
+    Object label = labels.labelFor(extensions);
+    assertThat(label, instanceOf(String.class));
+    String labelText = (String) label;
+    assertThat(labelText, equalTo("1 > 10, 20 > 30, 100 > max"));
+  }
+
+  // syntax = "proto2";
+  //
+  // import 'google/protobuf/descriptor.proto';
+  @Test public void should_return_label_for_import() {
+    Import anImport = xtext.findFirst(Import.class);
+    Object label = labels.labelFor(anImport);
+    assertThat(label, instanceOf(String.class));
+    String labelText = (String) label;
+    assertThat(labelText, equalTo("google/protobuf/descriptor.proto"));
+  }
+
+  // syntax = "proto2";
+  //
+  // enum PhoneType {
+  //   HOME = 0;
+  // }
+  @Test public void should_return_label_for_literal() {
+    Literal literal = xtext.find("HOME", Literal.class);
+    Object label = labels.labelFor(literal);
+    assertThat(label, instanceOf(StyledString.class));
+    StyledString labelText = (StyledString) label;
+    assertThat(labelText.getString(), equalTo("HOME [0]"));
+   }
+
+  // syntax = "proto2";
+  //
+  // message Type {}
+  //
+  // message Person {
+  //   optional Type type = 1;
+  // }
+  @Test public void should_return_label_for_field() {
+    MessageField field = xtext.find("type", MessageField.class);
+    Object label = labels.labelFor(field);
+    assertThat(label, instanceOf(StyledString.class));
+    StyledString labelText = (StyledString) label;
+    assertThat(labelText.getString(), equalTo("type [1] : Type"));
+  }
+
+  // syntax = "proto2";
+  //
+  // message Person {
+  //   optional Type type = 1;
+  // }
+  @Test public void should_return_label_for_field_with_unresolved_type() {
+    MessageField field = xtext.find("type", MessageField.class);
+    Object label = labels.labelFor(field);
+    assertThat(label, instanceOf(StyledString.class));
+    StyledString labelText = (StyledString) label;
+    assertThat(labelText.getString(), equalTo("type [1] : <unresolved>"));
+  }
+
+  // syntax = "proto2";
+  //
+  // message Person {}
+  //
+  // message Type {}
+  //
+  // service PersonService {
+  //   rpc PersonRpc (Person) returns (Type);
+  // }
+  @Test public void should_return_label_for_rpc() {
+    Rpc rpc = xtext.findFirst(Rpc.class);
+    Object label = labels.labelFor(rpc);
+    assertThat(label, instanceOf(StyledString.class));
+    StyledString labelText = (StyledString) label;
+    assertThat(labelText.getString(), equalTo("PersonRpc : Person > Type"));
+  }
+
+  // syntax = "proto2";
+  //
+  // message Type {}
+  //
+  // service PersonService {
+  //   rpc PersonRpc (Person) returns (Type);
+  // }
+  @Test public void should_return_label_for_rpc_with_unresolved_argument_type() {
+    Rpc rpc = xtext.findFirst(Rpc.class);
+    Object label = labels.labelFor(rpc);
+    assertThat(label, instanceOf(StyledString.class));
+    StyledString labelText = (StyledString) label;
+    assertThat(labelText.getString(), equalTo("PersonRpc : <unresolved> > Type"));
+  }
+
+  // syntax = "proto2";
+  //
+  // message Person {}
+  //
+  // service PersonService {
+  //   rpc PersonRpc (Person) returns (Type);
+  // }
+  @Test public void should_return_label_for_rpc_with_unresolved_return_type() {
+    Rpc rpc = xtext.findFirst(Rpc.class);
+    Object label = labels.labelFor(rpc);
+    assertThat(label, instanceOf(StyledString.class));
+    StyledString labelText = (StyledString) label;
+    assertThat(labelText.getString(), equalTo("PersonRpc : Person > <unresolved>"));
+  }
+
+  // syntax = "proto2";
+  //
+  // message Person {}
+  //
+  // extend Person {}
+  @Test public void should_return_label_for_type_extension() {
+    TypeExtension typeExtension = xtext.findFirst(TypeExtension.class);
+    Object label = labels.labelFor(typeExtension);
+    assertThat(label, instanceOf(String.class));
+    String labelText = (String) label;
+    assertThat(labelText, equalTo("Person"));
+  }
+
+  // syntax = "proto2";
+  //
+  // extend Person {}
+  @Test public void should_return_label_for_type_extension_with_unresolved_type() {
+    TypeExtension typeExtension = xtext.findFirst(TypeExtension.class);
+    Object label = labels.labelFor(typeExtension);
+    assertThat(label, instanceOf(String.class));
+    String labelText = (String) label;
+    assertThat(labelText, equalTo("<unresolved>"));
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Labels.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Labels.java
index bc3b141..03898aa 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Labels.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Labels.java
@@ -9,31 +9,34 @@
  */
 package com.google.eclipse.protobuf.ui.labeling;
 
-import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.IMPORT__IMPORT_URI;
+import static com.google.eclipse.protobuf.ui.labeling.Messages.unresolved;
 import static org.eclipse.jface.viewers.StyledString.DECORATIONS_STYLER;
 
-import com.google.eclipse.protobuf.model.util.*;
-import com.google.eclipse.protobuf.naming.NameResolver;
-import com.google.eclipse.protobuf.protobuf.*;
-import com.google.inject.*;
-
 import org.eclipse.emf.common.util.EList;
 import org.eclipse.emf.ecore.EObject;
 import org.eclipse.jface.viewers.StyledString;
-import org.eclipse.xtext.nodemodel.INode;
+
+import com.google.eclipse.protobuf.model.util.*;
+import com.google.eclipse.protobuf.naming.NameResolver;
+import com.google.eclipse.protobuf.protobuf.*;
+import com.google.inject.Inject;
 
 /**
  * Registry of commonly used text in the 'Protocol Buffer' editor.
  *
  * @author alruiz@google.com (Alex Ruiz)
  */
-@Singleton public class Labels {
-  @Inject private NameResolver nameResolver;
+public class Labels {
+  @Inject private Imports imports;
   @Inject private MessageFields messageFields;
-  @Inject private INodes nodes;
+  @Inject private NameResolver nameResolver;
   @Inject private Options options;
 
   public Object labelFor(Object o) {
+    if (o instanceof AbstractOption) {
+      AbstractOption option = (AbstractOption) o;
+      return labelFor(option);
+    }
     if (o instanceof Extensions) {
       Extensions extensions = (Extensions) o;
       return labelFor(extensions);
@@ -46,28 +49,46 @@
       Literal literal = (Literal) o;
       return labelFor(literal);
     }
-    if (o instanceof TypeExtension) {
-      TypeExtension extend = (TypeExtension) o;
-      return labelFor(extend);
-    }
     if (o instanceof MessageField) {
       MessageField field = (MessageField) o;
       return labelFor(field);
     }
-    if (o instanceof AbstractOption) {
-      AbstractOption option = (AbstractOption) o;
-      return labelFor(option);
-    }
     if (o instanceof Rpc) {
       Rpc rpc = (Rpc) o;
       return labelFor(rpc);
     }
+    if (o instanceof TypeExtension) {
+      TypeExtension extension = (TypeExtension) o;
+      return labelFor(extension);
+    }
     if (o instanceof EObject) {
       return nameResolver.nameOf((EObject) o);
     }
     return null;
   }
 
+  private Object labelFor(AbstractOption option) {
+    IndexedElement e = options.rootSourceOf(option);
+    String name = options.nameForOption(e);
+    if (option instanceof AbstractCustomOption) {
+      AbstractCustomOption customOption = (AbstractCustomOption) option;
+      StringBuilder b = new StringBuilder();
+      b.append(formatCustomOptionElement(name));
+      for (OptionField field : options.fieldsOf(customOption)) {
+        IndexedElement source = field.getTarget();
+        String sourceName = options.nameForOption(source);
+        b.append(".")
+         .append(formatCustomOptionElement(sourceName));
+      }
+      return b.toString();
+    }
+    return name;
+  }
+
+  private String formatCustomOptionElement(String name) {
+    return String.format("(%s)", name);
+  }
+
   private Object labelFor(Extensions extensions) {
     StringBuilder builder = new StringBuilder();
     EList<Range> ranges = extensions.getRanges();
@@ -87,11 +108,7 @@
   }
 
   private Object labelFor(Import anImport) {
-    INode node = nodes.firstNodeForFeature(anImport, IMPORT__IMPORT_URI);
-    if (node == null) {
-      return anImport.getImportURI();
-    }
-    return node.getText();
+    return imports.uriAsEnteredByUser(anImport);
   }
 
   private Object labelFor(Literal literal) {
@@ -101,50 +118,18 @@
     return text;
   }
 
-  private Object labelFor(TypeExtension extension) {
-    return typeName(extension.getType());
-  }
-
-  private String typeName(ExtensibleTypeLink link) {
-    ExtensibleType type = link.getTarget();
-    if (type == null) {
-      return null;
-    }
-    return nameResolver.nameOf(type);
-  }
 
   private Object labelFor(MessageField field) {
     StyledString text = new StyledString(nameResolver.nameOf(field));
     String typeName = messageFields.typeNameOf(field);
     if (typeName == null) {
-      typeName = "<unresolved reference>"; // TODO move to properties file
+      typeName = unresolved;
     }
     String indexAndType = String.format(" [%d] : %s", field.getIndex(), typeName);
     text.append(indexAndType, DECORATIONS_STYLER);
     return text;
   }
 
-  private Object labelFor(AbstractOption option) {
-    IndexedElement e = options.rootSourceOf(option);
-    String name = options.nameForOption(e);
-    if (option instanceof AbstractCustomOption) {
-      AbstractCustomOption customOption = (AbstractCustomOption) option;
-      StringBuilder b = new StringBuilder();
-      b.append(formatCustomOptionName(name));
-      for (OptionField field : options.fieldsOf(customOption)) {
-        IndexedElement source = field.getTarget();
-        b.append(".")
-         .append(options.nameForOption(source));
-      }
-      return b.toString();
-    }
-    return name;
-  }
-
-  private String formatCustomOptionName(String name) {
-    return String.format("(%s)", name);
-  }
-
   private Object labelFor(Rpc rpc) {
     StyledString text = new StyledString(nameResolver.nameOf(rpc));
     String types = String.format(" : %s > %s", messageName(rpc.getArgType()), messageName(rpc.getReturnType()));
@@ -153,10 +138,16 @@
   }
 
   private String messageName(MessageLink link) {
-    Message m = link.getTarget();
-    if (m == null) {
-      return null;
-    }
-    return nameResolver.nameOf(m);
+    return nameOf(link.getTarget());
+  }
+
+  private Object labelFor(TypeExtension extension) {
+    ExtensibleTypeLink type = extension.getType();
+    return nameOf(type.getTarget());
+  }
+
+  private String nameOf(EObject e) {
+    String name = (e == null) ? null : nameResolver.nameOf(e);
+    return (name == null) ? unresolved : name;
   }
 }
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Messages.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Messages.java
new file mode 100644
index 0000000..fcd7a81
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Messages.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2012 Google Inc.
+ *
+ * All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse
+ * Public License v1.0 which accompanies this distribution, and is available at
+ *
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package com.google.eclipse.protobuf.ui.labeling;
+
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class Messages extends NLS {
+  public static String unresolved;
+
+  static {
+    Class<Messages> type = Messages.class;
+    NLS.initializeMessages(type.getName(), type);
+  }
+
+  private Messages() {}
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Messages.properties b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Messages.properties
new file mode 100644
index 0000000..15e5d2e
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Messages.properties
@@ -0,0 +1 @@
+unresolved=<unresolved>