Provide a ProtoDescriptor defining a single message, MapEntry, allowing
map "key" and "value" fields within complex option values to be resolved.

Change-Id: I2cbc66eff70d321f89f2a40ce502bbfe5533f640
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/model/util/MessageFields_mapEntryTypeOf_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/model/util/MessageFields_mapEntryTypeOf_Test.java
new file mode 100644
index 0000000..0bdb09f
--- /dev/null
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/model/util/MessageFields_mapEntryTypeOf_Test.java
@@ -0,0 +1,72 @@
+/*
+ * 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 org.junit.Rule;
+import org.junit.Test;
+
+import com.google.eclipse.protobuf.junit.core.XtextRule;
+import com.google.eclipse.protobuf.protobuf.MessageField;
+import com.google.inject.Inject;
+
+/**
+ * Tests for <code>{@link MessageFields#mapEntryTypeOf(MessageField)}</code>.
+ *
+ * @author jogl@google.com (John Glassmyer)
+ */
+public class MessageFields_mapEntryTypeOf_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.mapEntryTypeOf(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_null_for_message() {
+    MessageField field = xtext.find("number", MessageField.class);
+    assertThat(fields.mapEntryTypeOf(field), nullValue());
+  }
+
+  // syntax = "proto2";
+  //
+  // message Person {
+  //   optional map<string, PhoneNumber> phone_book = 1;
+  //
+  //   message PhoneNumber {
+  //     optional string value = 1;
+  //   }
+  // }
+  @Test public void should_return_entry_type_for_map() {
+    MessageField field = xtext.find("phone_book", MessageField.class);
+    assertThat(fields.mapEntryTypeOf(field).getName(), equalTo("MapEntry"));
+  }
+}
diff --git a/com.google.eclipse.protobuf/build.properties b/com.google.eclipse.protobuf/build.properties
index cddf73c..f88afe0 100644
--- a/com.google.eclipse.protobuf/build.properties
+++ b/com.google.eclipse.protobuf/build.properties
@@ -4,4 +4,5 @@
                .,\
                plugin.xml,\
                OSGI-INF/,\
-               descriptor.proto
+               descriptor.proto,\
+               map_entry.proto
diff --git a/com.google.eclipse.protobuf/map_entry.proto b/com.google.eclipse.protobuf/map_entry.proto
new file mode 100644
index 0000000..64ef6a5
--- /dev/null
+++ b/com.google.eclipse.protobuf/map_entry.proto
@@ -0,0 +1,23 @@
+// Copyright 2015 Google Inc.  All rights reserved.
+syntax = "proto2";
+
+package google.protobuf;
+
+// An entry in a map field.
+//
+// Specified here as if the defining map field were "map<string, string>" as a temporary hack
+// until protobuf-dt is able to synthesize entry types for map fields with arbitary value types.
+// This avoids "Couldn't resolve" errors on "key" and "value" fields and enables completion of them.
+message MapEntry {
+  // The key of an entry in a map field.
+  //
+  // Specified here as "string" due to technical limitations in protobuf-dt. The actual type of
+  // this key field is specified by the defining map field.
+  optional string key = 1;
+
+  // The value of an entry in a map field.
+  //
+  // Specified here as "string" due to technical limitations in protobuf-dt. The actual type of
+  // this value field is specified by the defining map field.
+  optional string value = 2;
+}
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 d4a879e..1107a3d 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
@@ -37,6 +37,8 @@
 import com.google.eclipse.protobuf.protobuf.ScalarType;
 import com.google.eclipse.protobuf.protobuf.ScalarTypeLink;
 import com.google.eclipse.protobuf.protobuf.TypeLink;
+import com.google.eclipse.protobuf.scoping.ProtoDescriptorProvider;
+import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
 /**
@@ -45,6 +47,8 @@
  * @author alruiz@google.com (Alex Ruiz)
  */
 @Singleton public class MessageFields {
+  @Inject private ProtoDescriptorProvider descriptorProvider;
+
   /**
    * Indicates whether the modifier of the given field is <code>{@link ModifierEnum#OPTIONAL}</code>.
    * @param field the given field.
@@ -207,4 +211,15 @@
   private MapType mapTypeOf(TypeLink typeLink) {
     return typeLink instanceof MapTypeLink ? ((MapTypeLink) typeLink).getTarget() : null;
   }
+
+  /**
+   * Returns a Message representing an entry in a map of the given field's type, or null if the
+   * given field is not of map type.
+   */
+  public Message mapEntryTypeOf(MessageField field) {
+    // TODO(jogl): Dynamically create and return a message type with key and value fields matching
+    // the key and value types of the target MapType.
+    return field.getType() instanceof MapTypeLink
+        ? (Message) descriptorProvider.mapEntryDescriptor().allTypes().get(0) : null;
+  }
 }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/MessageFieldFinderStrategy.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/MessageFieldFinderStrategy.java
index f54118a..0a847c4 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/MessageFieldFinderStrategy.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/MessageFieldFinderStrategy.java
@@ -39,7 +39,11 @@
   @Override public Collection<IEObjectDescription> findOptionFields(IndexedElement reference) {
     Collection<? extends EObject> elements;
     if (reference instanceof MessageField) {
-      Message fieldType = messageFields.messageTypeOf((MessageField) reference);
+      Message fieldType = messageFields.mapEntryTypeOf((MessageField) reference);
+      if (fieldType == null) {
+        fieldType = messageFields.messageTypeOf((MessageField) reference);
+      }
+
       if (fieldType != null) {
         elements = fieldType.getElements();
       } else {
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/NormalFieldNameFinderStrategy.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/NormalFieldNameFinderStrategy.java
index f888abf..c220a72 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/NormalFieldNameFinderStrategy.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/NormalFieldNameFinderStrategy.java
@@ -16,6 +16,7 @@
 import com.google.eclipse.protobuf.model.util.MessageFields;
 import com.google.eclipse.protobuf.protobuf.Group;
 import com.google.eclipse.protobuf.protobuf.IndexedElement;
+import com.google.eclipse.protobuf.protobuf.Message;
 import com.google.eclipse.protobuf.protobuf.MessageField;
 import com.google.eclipse.protobuf.protobuf.OneOf;
 import com.google.inject.Inject;
@@ -33,11 +34,19 @@
   @Inject private MessageFields messageFields;
 
   @Override public Collection<IEObjectDescription> findMessageFields(IndexedElement reference) {
-    Iterable<? extends EObject> elements = reference instanceof Group
-        ? ((Group) reference).getElements()
-        : reference instanceof MessageField
-            ? messageFields.messageTypeOf((MessageField) reference).getElements()
-            : null;
+    Iterable<? extends EObject> elements;
+    if (reference instanceof Group) {
+      elements = ((Group) reference).getElements();
+    } else if (reference instanceof MessageField) {
+      MessageField field = (MessageField) reference;
+      Message fieldType = messageFields.mapEntryTypeOf(field);
+      if (fieldType == null) {
+        fieldType = messageFields.messageTypeOf(field);
+      }
+      elements = fieldType.getElements();
+    } else {
+      elements = null;
+    }
     return getDescriptions(elements);
   }
 
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptorProvider.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptorProvider.java
index 9605687..af1c4c9 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptorProvider.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptorProvider.java
@@ -55,6 +55,11 @@
   private final ProtoDescriptorInfo openSourceProtoDescriptorInfo;
   private final ProtoDescriptorInfo extensionPointDescriptorInfo;
 
+  private static final String MAP_ENTRY_DESCRIPTOR_PATH = "google/protobuf/map_entry.proto";
+  private static final URI MAP_ENTRY_DESCRIPTOR_LOCATION =
+      URI.createURI("platform:/plugin/com.google.eclipse.protobuf/map_entry.proto");;
+  private final ProtoDescriptorInfo mapEntryDescriptorInfo;
+
   private static final Logger LOG =
       Logger.getLogger(ProtoDescriptorProvider.class.getCanonicalName());
 
@@ -76,6 +81,7 @@
     this.nodes = nodes;
     this.resolver = resolver;
     this.openSourceProtoDescriptorInfo = getOpenSourceProtoDescriptorInfo();
+    this.mapEntryDescriptorInfo = getMapEntryDescriptorInfo();
     this.extensionPointDescriptorInfo = getExtensionPointDescriptorInfo();
   }
 
@@ -121,6 +127,10 @@
     return null;
   }
 
+  public ProtoDescriptor mapEntryDescriptor() {
+    return mapEntryDescriptorInfo.protoDescriptor;
+  }
+
   private Map<String, ProtoDescriptorInfo> loadDescriptorInfos(final IProject project) {
     Map<String, ProtoDescriptorInfo> descriptorInfos =
         new LinkedHashMap<String, ProtoDescriptorProvider.ProtoDescriptorInfo>();
@@ -193,6 +203,13 @@
         DEFAULT_DESCRIPTOR_LOCATION, descriptor);
   }
 
+  private ProtoDescriptorInfo getMapEntryDescriptorInfo() {
+    ProtoDescriptor descriptor = new ProtoDescriptor(
+        MAP_ENTRY_DESCRIPTOR_PATH, MAP_ENTRY_DESCRIPTOR_LOCATION, parser, nodes);
+    return new ProtoDescriptorInfo(
+        MAP_ENTRY_DESCRIPTOR_PATH, MAP_ENTRY_DESCRIPTOR_LOCATION, descriptor);
+  }
+
   private ProtoDescriptorInfo getExtensionPointDescriptorInfo() {
     IConfigurationElement[] config = registry.getConfigurationElementsFor(EXTENSION_ID);
     if (config == null) {