Display the types of map fields (in hover popups and in the outline view)
as "map<KEY, VALUE>" (with actual types) rather than as "<unresolved>".

Change-Id: I3e54b99b47cae2ab90e9e449618357f13760d139
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/model/util/MessageFields_messageTypeOf_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/model/util/MessageFields_messageTypeOf_Test.java
new file mode 100644
index 0000000..df00a6a
--- /dev/null
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/model/util/MessageFields_messageTypeOf_Test.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2015 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.model.util;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.junit.Assert.assertThat;
+import static com.google.eclipse.protobuf.junit.core.UnitTestModule.unitTestModule;
+import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.emf.common.util.EList;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.google.eclipse.protobuf.junit.core.XtextRule;
+import com.google.eclipse.protobuf.protobuf.Message;
+import com.google.eclipse.protobuf.protobuf.MessageElement;
+import com.google.eclipse.protobuf.protobuf.MessageField;
+import com.google.inject.Inject;
+
+/**
+ * Tests for <code>{@link MessageFields#messageTypeOf(MessageField)}</code>.
+ *
+ * @author jogl@google.com (John Glassmyer)
+ */
+public class MessageFields_messageTypeOf_Test {
+  @Rule public XtextRule xtext = overrideRuntimeModuleWith(unitTestModule());
+
+  @Inject private MessageFields fields;
+
+  // syntax = "proto2";
+  //
+  // message Person {
+  //   optional string name = 1;
+  // }
+  @Test public void should_return_null_for_scalar() {
+    MessageField field = xtext.find("name", MessageField.class);
+    assertThat(fields.messageTypeOf(field), nullValue());
+  }
+
+  // syntax = "proto2";
+  //
+  // message Person {
+  //   optional string name = 1;
+  //   optional PhoneNumber number = 2;
+  //
+  //   message PhoneNumber {
+  //     optional string value = 1;
+  //   }
+  // }
+  @Test public void should_return_name_of_message() {
+    MessageField field = xtext.find("number", MessageField.class);
+    assertThat(fields.messageTypeOf(field).getName(), equalTo("PhoneNumber"));
+  }
+
+  // syntax = "proto2";
+  //
+  // message Person {
+  //   optional map<string, PhoneNumber> phone_book = 1;
+  //
+  //   message PhoneNumber {
+  //     optional string value = 1;
+  //   }
+  // }
+  @Test public void should_return_null_for_map() {
+    MessageField field = xtext.find("phone_book", MessageField.class);
+    assertThat(fields.messageTypeOf(field), nullValue());
+  }
+}
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/model/util/MessageFields_typeNameOf_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/model/util/MessageFields_typeNameOf_Test.java
index 038182a..618ae30 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/model/util/MessageFields_typeNameOf_Test.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/model/util/MessageFields_typeNameOf_Test.java
@@ -51,8 +51,22 @@
   //     optional string value = 1;
   //   }
   // }
-  @Test public void should_return_name_of_type() {
+  @Test public void should_return_name_of_message() {
     MessageField field = xtext.find("number", MessageField.class);
     assertThat(fields.typeNameOf(field), equalTo("PhoneNumber"));
   }
+
+  // syntax = "proto2";
+  //
+  // message Person {
+  //   optional map<string, PhoneNumber> phone_book = 1;
+  //
+  //   message PhoneNumber {
+  //     optional string value = 1;
+  //   }
+  // }
+  @Test public void should_return_name_of_map() {
+    MessageField field = xtext.find("phone_book", MessageField.class);
+    assertThat(fields.typeNameOf(field), equalTo("map<string, PhoneNumber>"));
+  }
 }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/MessageFields.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/MessageFields.java
index e5d9f34..d4a879e 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/MessageFields.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/MessageFields.java
@@ -29,6 +29,8 @@
 import com.google.eclipse.protobuf.protobuf.ComplexType;
 import com.google.eclipse.protobuf.protobuf.ComplexTypeLink;
 import com.google.eclipse.protobuf.protobuf.Enum;
+import com.google.eclipse.protobuf.protobuf.MapType;
+import com.google.eclipse.protobuf.protobuf.MapTypeLink;
 import com.google.eclipse.protobuf.protobuf.Message;
 import com.google.eclipse.protobuf.protobuf.MessageField;
 import com.google.eclipse.protobuf.protobuf.ModifierEnum;
@@ -142,14 +144,26 @@
    * @return the name of the type of the given field.
    */
   public String typeNameOf(MessageField field) {
-    ScalarType scalarType = scalarTypeOf(field);
+    return typeNameOf(field.getType());
+  }
+
+  private String typeNameOf(TypeLink typeLink) {
+    ScalarType scalarType = scalarTypeOf(typeLink);
     if (scalarType != null) {
       return scalarType.getName();
     }
-    ComplexType complexType = typeOf(field);
+
+    MapType mapType = mapTypeOf(typeLink);
+    if (mapType != null) {
+      return String.format(
+          "map<%s, %s>", typeNameOf(mapType.getKeyType()), typeNameOf(mapType.getValueType()));
+    }
+
+    ComplexType complexType = complexTypeOf(typeLink);
     if (complexType != null) {
       return complexType.getName();
     }
+
     return null;
   }
 
@@ -159,7 +173,8 @@
    * @return the message type of the given field or {@code null} if the type of the given field is not message.
    */
   public Message messageTypeOf(MessageField field) {
-    return fieldType(field, Message.class);
+    ComplexType fieldType = complexTypeOf(field.getType());
+    return fieldType instanceof Message ? (Message) fieldType : null;
   }
 
   /**
@@ -168,28 +183,12 @@
    * @return the enum type of the given field or {@code null} if the type of the given field is not enum.
    */
   public Enum enumTypeOf(MessageField field) {
-    return fieldType(field, Enum.class);
+    ComplexType fieldType = complexTypeOf(field.getType());
+    return fieldType instanceof Enum ? (Enum) fieldType : null;
   }
 
-  private <T extends ComplexType> T fieldType(MessageField field, Class<T> targetType) {
-    ComplexType type = typeOf(field);
-    if (targetType.isInstance(type)) {
-      return targetType.cast(type);
-    }
-    return null;
-  }
-
-  /**
-   * Returns the type of the given field.
-   * @param field the given field.
-   * @return the type of the given field.
-   */
-  public ComplexType typeOf(MessageField field) {
-    TypeLink link = field.getType();
-    if (link instanceof ComplexTypeLink) {
-      return ((ComplexTypeLink) link).getTarget();
-    }
-    return null;
+  private ComplexType complexTypeOf(TypeLink link) {
+    return link instanceof ComplexTypeLink ? ((ComplexTypeLink) link).getTarget() : null;
   }
 
   /**
@@ -198,10 +197,14 @@
    * @return the scalar type of the given field or {@code null} if the type of the given field is not a scalar.
    */
   public ScalarType scalarTypeOf(MessageField field) {
-    TypeLink link = (field).getType();
-    if (link instanceof ScalarTypeLink) {
-      return ((ScalarTypeLink) link).getTarget();
-    }
-    return null;
+    return scalarTypeOf(field.getType());
+  }
+
+  private ScalarType scalarTypeOf(TypeLink link) {
+    return link instanceof ScalarTypeLink ? ((ScalarTypeLink) link).getTarget() : null;
+  }
+
+  private MapType mapTypeOf(TypeLink typeLink) {
+    return typeLink instanceof MapTypeLink ? ((MapTypeLink) typeLink).getTarget() : null;
   }
 }