In progress: [Issue 125] Support for custom options.
Started support for hyperlinking to option definition.
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/contentassist/ProtobufProposalProvider.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/contentassist/ProtobufProposalProvider.java
index 6ba951e..853b1c0 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/contentassist/ProtobufProposalProvider.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/contentassist/ProtobufProposalProvider.java
@@ -73,7 +73,7 @@
@Override public void completeBuiltInOption_Property(EObject model, Assignment assignment,
ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
ProtoDescriptor descriptor = descriptorProvider.get();
- Collection<Property> optionProperties = descriptor.availableOptionsFor(model);
+ Collection<Property> optionProperties = descriptor.availableOptionPropertiesFor(model);
if (!optionProperties.isEmpty()) proposeOptions(optionProperties, context, acceptor);
}
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/LiteralDescriptions.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/LiteralDescriptions.java
index b3d5568..0b847f3 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/LiteralDescriptions.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/LiteralDescriptions.java
@@ -8,6 +8,7 @@
*/
package com.google.eclipse.protobuf.scoping;
+import static java.util.Collections.emptyList;
import static org.eclipse.xtext.EcoreUtil2.getAllContentsOfType;
import static org.eclipse.xtext.resource.EObjectDescription.create;
@@ -24,6 +25,7 @@
class LiteralDescriptions {
Collection<IEObjectDescription> literalsOf(Enum anEnum) {
+ if (anEnum == null) return emptyList();
List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
for (Literal literal : getAllContentsOfType(anEnum, Literal.class))
descriptions.add(create(literal.getName(), literal));
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/OptionDescriptions.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/OptionDescriptions.java
new file mode 100644
index 0000000..89d219d
--- /dev/null
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/OptionDescriptions.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2011 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.scoping;
+
+import static com.google.eclipse.protobuf.scoping.OptionType.*;
+import static com.google.eclipse.protobuf.scoping.QualifiedNames.addLeadingDot;
+import static java.util.Collections.emptyList;
+import static org.eclipse.xtext.resource.EObjectDescription.create;
+
+import java.util.*;
+import java.util.Map.Entry;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.xtext.naming.*;
+import org.eclipse.xtext.resource.IEObjectDescription;
+
+import com.google.eclipse.protobuf.protobuf.*;
+import com.google.eclipse.protobuf.protobuf.Enum;
+import com.google.inject.Inject;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+class OptionDescriptions {
+
+ private static final Map<Class<?>, OptionType> OPTION_TYPES_BY_CONTAINER = new HashMap<Class<?>, OptionType>();
+
+ static {
+ OPTION_TYPES_BY_CONTAINER.put(Protobuf.class, FILE);
+ OPTION_TYPES_BY_CONTAINER.put(Enum.class, ENUM);
+ OPTION_TYPES_BY_CONTAINER.put(Message.class, MESSAGE);
+ OPTION_TYPES_BY_CONTAINER.put(Service.class, SERVICE);
+ OPTION_TYPES_BY_CONTAINER.put(Rpc.class, RPC);
+ }
+
+ @Inject private ProtoDescriptorProvider descriptorProvider;
+ @Inject private LocalNamesProvider localNamesProvider;
+ @Inject private IQualifiedNameProvider nameProvider;
+
+ Collection <IEObjectDescription> builtInOptionProperties(BuiltInOption option) {
+ ProtoDescriptor descriptor = descriptorProvider.get();
+ Collection<Property> properties = descriptor.availableOptionPropertiesFor(option.eContainer());
+ if (!properties.isEmpty()) return describe(properties);
+ return emptyList();
+ }
+
+ Collection <IEObjectDescription> localCustomOptionProperties(EObject root, CustomOption option) {
+ return localCustomOptionProperties(root, option, 0);
+ }
+
+ private Collection <IEObjectDescription> localCustomOptionProperties(EObject root, CustomOption option, int level) {
+ OptionType optionType = optionType(option);
+ if (optionType == null) return emptyList();
+ List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
+ for (EObject element : root.eContents()) {
+ if (isExtendingOptionMessage(element, optionType)) {
+ ExtendMessage extend = (ExtendMessage) element;
+ for (MessageElement e : extend.getElements()) {
+ if (!(e instanceof Property)) continue;
+ List<QualifiedName> names = localNamesProvider.namesOf(e);
+ int nameCount = names.size();
+ for (int i = level; i < nameCount; i++) {
+ descriptions.add(create(names.get(i), element));
+ }
+ descriptions.addAll(fullyQualifiedNamesOf(element));
+ }
+ continue;
+ }
+ if (element instanceof Message) {
+ descriptions.addAll(localCustomOptionProperties(element, option, level + 1));
+ }
+ }
+ return descriptions;
+ }
+
+ private OptionType optionType(CustomOption option) {
+ EObject container = option.eContainer();
+ for (Entry<Class<?>, OptionType> optionTypeByContainer : OPTION_TYPES_BY_CONTAINER.entrySet()) {
+ if (optionTypeByContainer.getKey().isInstance(container)) {
+ return optionTypeByContainer.getValue();
+ }
+ }
+ return null;
+ }
+
+ private boolean isExtendingOptionMessage(EObject o, OptionType optionType) {
+ if (!(o instanceof ExtendMessage)) return false;
+ Message message = messageFrom((ExtendMessage) o);
+ if (message == null) return false;
+ return optionType.messageName.equals(message.getName());
+ }
+
+ private Message messageFrom(ExtendMessage extend) {
+ MessageRef ref = extend.getMessage();
+ return ref == null ? null : ref.getType();
+ }
+
+ // TODO remove duplication in TypeDescriptions
+ private Collection<IEObjectDescription> fullyQualifiedNamesOf(EObject obj) {
+ List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
+ QualifiedName fqn = nameProvider.getFullyQualifiedName(obj);
+ descriptions.add(create(fqn, obj));
+ descriptions.add(create(addLeadingDot(fqn), obj));
+ return descriptions;
+ }
+
+ private Collection<IEObjectDescription> describe(Collection<Property> properties) {
+ List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
+ for (Property p : properties) {
+ descriptions.add(create(p.getName(), p));
+ }
+ return descriptions;
+ }
+}
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/OptionType.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/OptionType.java
index e166f13..5bfa135 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/OptionType.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/OptionType.java
@@ -12,5 +12,12 @@
* @author alruiz@google.com (Alex Ruiz)
*/
enum OptionType {
- FILE, MESSAGE, FIELD, ENUM;
+ FILE("FileOptions"), MESSAGE("MessageOptions"), FIELD("FieldOptions"), ENUM("EnumOptions"),
+ ENUM_LITERAL("EnumValueOptions"), SERVICE("ServiceOptions"), RPC("MethodOptions");
+
+ final String messageName;
+
+ private OptionType(String messageName) {
+ this.messageName = messageName;
+ }
}
\ No newline at end of file
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptor.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptor.java
index 032f1e7..fc0ca3e 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptor.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptor.java
@@ -46,10 +46,13 @@
private static final Map<String, OptionType> OPTION_DEFINITION_BY_NAME = new HashMap<String, OptionType>();
static {
- OPTION_DEFINITION_BY_NAME.put("FileOptions", FILE);
- OPTION_DEFINITION_BY_NAME.put("MessageOptions", MESSAGE);
- OPTION_DEFINITION_BY_NAME.put("FieldOptions", FIELD);
- OPTION_DEFINITION_BY_NAME.put("EnumOptions", ENUM);
+ addOptionTypes(FILE, MESSAGE, FIELD, ENUM);
+ }
+
+ private static void addOptionTypes(OptionType...types) {
+ for (OptionType type : types) {
+ OPTION_DEFINITION_BY_NAME.put(type.messageName, type);
+ }
}
private final List<Type> allTypes = new ArrayList<Type>();
@@ -130,16 +133,16 @@
}
/**
- * Returns the options available for the given object. For example, if the given object is an
+ * Returns the options available for the given option container. For example, if the given object is an
* <code>{@link Enum}</code>, this method will return <code>{@link #enumOptions()}</code>.
- * @param o the given object.
- * @return the options available for the given object, or an empty collection if the are not any options available for
- * the given object.
+ * @param optionContainer the given container of an option.
+ * @return the options available for the given option container, or an empty collection if the are not any
+ * options available for the given option container.
*/
- public Collection<Property> availableOptionsFor(EObject o) {
- if (o instanceof Protobuf) return fileOptions();
- if (o instanceof Enum) return enumOptions();
- if (o instanceof Message) return messageOptions();
+ public Collection<Property> availableOptionPropertiesFor(EObject optionContainer) {
+ if (optionContainer instanceof Protobuf) return fileOptions();
+ if (optionContainer instanceof Enum) return enumOptions();
+ if (optionContainer instanceof Message) return messageOptions();
return emptyList();
}
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider.java
index e42c07a..be4bcc9 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider.java
@@ -8,8 +8,6 @@
*/
package com.google.eclipse.protobuf.scoping;
-import static org.eclipse.xtext.resource.EObjectDescription.create;
-
import java.util.*;
import org.eclipse.emf.ecore.*;
@@ -37,6 +35,7 @@
@Inject private FieldOptions fieldOptions;
@Inject private ProtobufElementFinder finder;
@Inject private LiteralDescriptions literalDescriptions;
+ @Inject private OptionDescriptions optionDescriptions;
@Inject private Options options;
@Inject private TypeDescriptions typeDescriptions;
@@ -69,8 +68,9 @@
EObject container = literalRef.eContainer();
Enum anEnum = null;
if (container instanceof BuiltInOption) {
+ ProtoDescriptor descriptor = descriptorProvider.get();
Property p = options.propertyFrom((Option) container);
- anEnum = descriptorProvider.get().enumTypeOf(p);
+ anEnum = descriptor.enumTypeOf(p);
}
if (container instanceof Property) {
anEnum = finder.enumTypeOf((Property) container);
@@ -81,33 +81,25 @@
Property property = (Property) option.eContainer();
anEnum = finder.enumTypeOf(property);
} else {
- anEnum = descriptorProvider.get().enumTypeOf(option);
+ ProtoDescriptor descriptor = descriptorProvider.get();
+ anEnum = descriptor.enumTypeOf(option);
}
}
- if (anEnum != null) return createScope(literalDescriptions.literalsOf(anEnum));
- return null;
+ return createScope(literalDescriptions.literalsOf(anEnum));
}
@SuppressWarnings("unused")
IScope scope_PropertyRef_property(PropertyRef propertyRef, EReference reference) {
+ Set<IEObjectDescription> descriptions = new HashSet<IEObjectDescription>();
EObject mayBeOption = propertyRef.eContainer();
if (mayBeOption instanceof BuiltInOption) {
- ProtoDescriptor descriptor = descriptorProvider.get();
- EObject optionContainer = mayBeOption.eContainer();
- Collection<Property> propertyOptions = descriptor.availableOptionsFor(optionContainer);
- if (!propertyOptions.isEmpty()) return createScope(describe(propertyOptions));
+ descriptions.addAll(optionDescriptions.builtInOptionProperties((BuiltInOption) mayBeOption));
}
- List<IEObjectDescription> descriptions = Collections.emptyList();
- // return new SimpleScope(descriptions, DO_NOT_IGNORE_CASE);
- return null;
- }
-
- private Collection<IEObjectDescription> describe(Collection<Property> properties) {
- List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
- for (Property p : properties) {
- descriptions.add(create(p.getName(), p));
+ if (mayBeOption instanceof CustomOption) {
+ Protobuf root = finder.rootOf(propertyRef);
+ descriptions.addAll(optionDescriptions.localCustomOptionProperties(root, (CustomOption) mayBeOption));
}
- return descriptions;
+ return createScope(descriptions);
}
private static IScope createScope(Iterable<IEObjectDescription> descriptions) {
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/TypeDescriptions.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/TypeDescriptions.java
index e605bb6..7aea681 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/TypeDescriptions.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/TypeDescriptions.java
@@ -42,10 +42,10 @@
@Inject private ImportUriResolver uriResolver;
<T extends Type> Collection<IEObjectDescription> localTypes(EObject root, Class<T> targetType) {
- return children(root, targetType, 0);
+ return localTypes(root, targetType, 0);
}
- private <T extends Type> Collection<IEObjectDescription> children(EObject root, Class<T> targetType, int level) {
+ private <T extends Type> Collection<IEObjectDescription> localTypes(EObject root, Class<T> targetType, int level) {
List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
for (EObject element : root.eContents()) {
if (!targetType.isInstance(element)) continue;
@@ -57,7 +57,7 @@
descriptions.addAll(fullyQualifiedNamesOf(element));
// TODO investigate if groups can have messages, and if so, add those messages to the scope.
if (element instanceof Message) {
- descriptions.addAll(children(element, targetType, level + 1));
+ descriptions.addAll(localTypes(element, targetType, level + 1));
}
}
return descriptions;