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) {