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