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;