In progress: [Issue 125] Support for custom options.

Fixed scoping of local custom options. Content assist for all option
names (built-in and custom) is broken.
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
index 716232e..d92fbfb 100644
--- 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
@@ -8,19 +8,23 @@
  */
 package com.google.eclipse.protobuf.scoping;
 
-import static com.google.eclipse.protobuf.scoping.OptionType.*;
 import static java.util.Collections.emptyList;
+import static org.eclipse.emf.common.util.URI.createURI;
+import static org.eclipse.emf.ecore.util.EcoreUtil.getAllContents;
 import static org.eclipse.xtext.resource.EObjectDescription.create;
 
 import java.util.*;
-import java.util.Map.Entry;
 
+import org.eclipse.emf.common.util.*;
 import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.resource.*;
 import org.eclipse.xtext.naming.QualifiedName;
 import org.eclipse.xtext.resource.IEObjectDescription;
+import org.eclipse.xtext.scoping.impl.ImportUriResolver;
 
 import com.google.eclipse.protobuf.protobuf.*;
-import com.google.eclipse.protobuf.protobuf.Enum;
+import com.google.eclipse.protobuf.protobuf.Package;
+import com.google.eclipse.protobuf.util.*;
 import com.google.inject.Inject;
 
 /**
@@ -28,19 +32,14 @@
  */
 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 ProtobufElementFinder finder;
+  @Inject private ImportedNamesProvider importedNamesProvider;
+  @Inject private Imports imports;
   @Inject private LocalNamesProvider localNamesProvider;
+  @Inject private PackageResolver packageResolver;
   @Inject private QualifiedNameDescriptions qualifiedNamesDescriptions;
+  @Inject private ImportUriResolver uriResolver;
 
   Collection <IEObjectDescription> builtInOptionProperties(BuiltInOption option) {
     ProtoDescriptor descriptor = descriptorProvider.get();
@@ -49,12 +48,11 @@
     return emptyList();
   }
 
-  Collection <IEObjectDescription> localCustomOptionProperties(EObject root, CustomOption option) {
-    return localCustomOptionProperties(root, option, 0);
+  Collection <IEObjectDescription> localCustomOptionProperties(EObject root, OptionType optionType) {
+    return localCustomOptionProperties(root, optionType, 0);
   }
 
-  private  Collection <IEObjectDescription> localCustomOptionProperties(EObject root, CustomOption option, int level) {
-    OptionType optionType = optionType(option);
+  private Collection <IEObjectDescription> localCustomOptionProperties(EObject root, OptionType optionType, int level) {
     if (optionType == null) return emptyList();
     List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
     for (EObject element : root.eContents()) {
@@ -72,23 +70,87 @@
         continue;
       }
       if (element instanceof Message) {
-        descriptions.addAll(localCustomOptionProperties(element, option, level + 1));
+        descriptions.addAll(localCustomOptionProperties(element, optionType, 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;
+  Collection<IEObjectDescription> importedCustomOptionProperties(Protobuf root, OptionType optionType) {
+    List<Import> allImports = finder.importsIn(root);
+    if (allImports.isEmpty()) return emptyList();
+    ResourceSet resourceSet = root.eResource().getResourceSet();
+    return importedCustomOptionProperties(allImports, finder.packageOf(root), resourceSet, optionType);
   }
 
-  private boolean isExtendingOptionMessage(EObject o, OptionType optionType) {
+  private Collection<IEObjectDescription> importedCustomOptionProperties(List<Import> allImports, Package aPackage,
+      ResourceSet resourceSet, OptionType optionType) {
+    List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
+    for (Import anImport : allImports) {
+      if (imports.isImportingDescriptor(anImport)) continue;
+      Resource importedResource = importedResource(anImport, resourceSet);
+      Protobuf importedRoot = finder.rootOf(importedResource);
+      if (importedRoot != null) {
+        descriptions.addAll(publicImportedCustomOptionProperties(importedRoot, optionType));
+        if (arePackagesRelated(aPackage, importedRoot)) {
+          descriptions.addAll(localCustomOptionProperties(importedRoot, optionType));
+          continue;
+        }
+      }
+      descriptions.addAll(localCustomOptionProperties(importedResource, optionType));
+    }
+    return descriptions;
+  }
+
+  private Resource importedResource(Import anImport, ResourceSet resourceSet) {
+    URI importUri = createURI(uriResolver.apply(anImport));
+    try {
+      return resourceSet.getResource(importUri, true);
+    } catch (Throwable t) {
+      return null;
+    }
+  }
+
+  private <T extends Type> Collection<IEObjectDescription> publicImportedCustomOptionProperties(Protobuf root,
+      OptionType optionType) {
+    List<Import> allImports = finder.publicImportsIn(root);
+    if (allImports.isEmpty()) return emptyList();
+    ResourceSet resourceSet = root.eResource().getResourceSet();
+    return importedCustomOptionProperties(allImports, finder.packageOf(root), resourceSet, optionType);
+  }
+
+  private boolean arePackagesRelated(Package aPackage, EObject root) {
+    Package p = finder.packageOf(root);
+    return packageResolver.areRelated(aPackage, p);
+  }
+
+  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;
+  }
+
+  private Collection<IEObjectDescription> localCustomOptionProperties(Resource resource, OptionType optionType) {
+    List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
+    TreeIterator<Object> contents = getAllContents(resource, true);
+    while (contents.hasNext()) {
+      Object next = contents.next();
+      if (!isExtendingOptionMessage(next, optionType)) continue;
+      ExtendMessage extend = (ExtendMessage) next;
+      for (MessageElement e : extend.getElements()) {
+        if (!(e instanceof Property)) continue;
+        descriptions.addAll(qualifiedNamesDescriptions.qualifiedNames(e));
+        for (QualifiedName name : importedNamesProvider.namesOf(e)) {
+          descriptions.add(create(name, e));
+        }
+      }
+    }
+    return descriptions;
+  }
+
+  private boolean isExtendingOptionMessage(Object o, OptionType optionType) {
     if (!(o instanceof ExtendMessage)) return false;
     Message message = messageFrom((ExtendMessage) o);
     if (message == null) return false;
@@ -99,12 +161,4 @@
     MessageRef ref = extend.getMessage();
     return ref == null ? null : ref.getType();
   }
-
-  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 5bfa135..ce32976 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
@@ -8,6 +8,14 @@
  */
 package com.google.eclipse.protobuf.scoping;
 
+import java.util.*;
+import java.util.Map.Entry;
+
+import org.eclipse.emf.ecore.EObject;
+
+import com.google.eclipse.protobuf.protobuf.*;
+import com.google.eclipse.protobuf.protobuf.Enum;
+
 /**
  * @author alruiz@google.com (Alex Ruiz)
  */
@@ -15,9 +23,29 @@
   FILE("FileOptions"), MESSAGE("MessageOptions"), FIELD("FieldOptions"), ENUM("EnumOptions"),
       ENUM_LITERAL("EnumValueOptions"), SERVICE("ServiceOptions"), RPC("MethodOptions");
 
+  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);
+  }
+
   final String messageName;
 
   private OptionType(String messageName) {
     this.messageName = messageName;
   }
+
+  static 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;
+  }
 }
\ No newline at end of file
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 be4bcc9..041fcc1 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,6 +8,8 @@
  */
 package com.google.eclipse.protobuf.scoping;
 
+import static com.google.eclipse.protobuf.scoping.OptionType.optionType;
+
 import java.util.*;
 
 import org.eclipse.emf.ecore.*;
@@ -97,7 +99,13 @@
     }
     if (mayBeOption instanceof CustomOption) {
       Protobuf root = finder.rootOf(propertyRef);
-      descriptions.addAll(optionDescriptions.localCustomOptionProperties(root, (CustomOption) mayBeOption));
+      OptionType optionType = optionType((CustomOption) mayBeOption);
+      EObject current = mayBeOption.eContainer();
+      while (current != null) {
+        descriptions.addAll(optionDescriptions.localCustomOptionProperties(current, optionType));
+        current = current.eContainer();
+      }
+      descriptions.addAll(optionDescriptions.importedCustomOptionProperties(root, optionType));
     }
     return createScope(descriptions);
   }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/QualifiedNames.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/QualifiedNames.java
index eb5bdc0..a073fe8 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/QualifiedNames.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/QualifiedNames.java
@@ -33,7 +33,7 @@
     return QualifiedName.create(segments.toArray(new String[segments.size()]));
   }
 
-  List<QualifiedName> addPackageNameSegments(QualifiedName name, Package p) {
+  Collection<QualifiedName> addPackageNameSegments(QualifiedName name, Package p) {
     QualifiedName current = name;
     List<String> segments = fqnSegments(p);
     int segmentCount = segments.size();
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 5082138..8892b06 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
@@ -19,12 +19,12 @@
 import org.eclipse.emf.ecore.EObject;
 import org.eclipse.emf.ecore.resource.*;
 import org.eclipse.xtext.naming.QualifiedName;
-import org.eclipse.xtext.resource.*;
+import org.eclipse.xtext.resource.IEObjectDescription;
 import org.eclipse.xtext.scoping.impl.ImportUriResolver;
 
 import com.google.eclipse.protobuf.protobuf.*;
 import com.google.eclipse.protobuf.protobuf.Package;
-import com.google.eclipse.protobuf.util.ProtobufElementFinder;
+import com.google.eclipse.protobuf.util.*;
 import com.google.inject.Inject;
 
 /**
@@ -35,6 +35,7 @@
   @Inject private ProtoDescriptorProvider descriptorProvider;
   @Inject private ProtobufElementFinder finder;
   @Inject private ImportedNamesProvider importedNamesProvider;
+  @Inject private Imports imports;
   @Inject private LocalNamesProvider localNamesProvider;
   @Inject private PackageResolver packageResolver;
   @Inject private QualifiedNameDescriptions qualifiedNamesDescriptions;
@@ -65,19 +66,20 @@
   <T extends Type> Collection<IEObjectDescription> importedTypes(Protobuf root, Class<T> targetType) {
     List<Import> allImports = finder.importsIn(root);
     if (allImports.isEmpty()) return emptyList();
-    return importedTypes(allImports, finder.packageOf(root), targetType);
+    ResourceSet resourceSet = root.eResource().getResourceSet();
+    return importedTypes(allImports, finder.packageOf(root), resourceSet, targetType);
   }
 
   private <T extends Type> Collection<IEObjectDescription> importedTypes(List<Import> allImports, Package aPackage,
-      Class<T> targetType) {
+      ResourceSet resourceSet, Class<T> targetType) {
     List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
     for (Import anImport : allImports) {
-      if (isImportingDescriptor(anImport)) {
+      if (imports.isImportingDescriptor(anImport)) {
         descriptions.addAll(allBuiltInTypes(targetType));
         continue;
       }
-      Resource importedResource = importedResourceFrom(anImport);
-      Protobuf importedRoot = rootElementOf(importedResource);
+      Resource importedResource = importedResource(anImport, resourceSet);
+      Protobuf importedRoot = finder.rootOf(importedResource);
       if (importedRoot != null) {
         descriptions.addAll(publicImportedTypes(importedRoot, targetType));
         if (arePackagesRelated(aPackage, importedRoot)) {
@@ -85,16 +87,11 @@
           continue;
         }
       }
-      descriptions.addAll(children(importedResource, targetType));
+      descriptions.addAll(localTypes(importedResource, targetType));
     }
     return descriptions;
   }
 
-  private boolean isImportingDescriptor(Import anImport) {
-    String descriptorLocation = descriptorProvider.descriptorLocation().toString();
-    return descriptorLocation.equals(anImport.getImportURI());
-  }
-
   private <T extends Type> Collection<IEObjectDescription> allBuiltInTypes(Class<T> targetType) {
     List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
     ProtoDescriptor descriptor = descriptorProvider.get();
@@ -106,14 +103,7 @@
     return descriptions;
   }
 
-  private <T extends Type> Collection<IEObjectDescription> publicImportedTypes(Protobuf root, Class<T> targetType) {
-    List<Import> allImports = finder.publicImportsIn(root);
-    if (allImports.isEmpty()) return emptyList();
-    return importedTypes(allImports, finder.packageOf(root), targetType);
-  }
-
-  private Resource importedResourceFrom(Import anImport) {
-    ResourceSet resourceSet = finder.rootOf(anImport).eResource().getResourceSet();
+  private Resource importedResource(Import anImport, ResourceSet resourceSet) {
     URI importUri = createURI(uriResolver.apply(anImport));
     try {
       return resourceSet.getResource(importUri, true);
@@ -122,17 +112,11 @@
     }
   }
 
-  private Protobuf rootElementOf(Resource resource) {
-    if (resource instanceof XtextResource) {
-      EObject root = ((XtextResource) resource).getParseResult().getRootASTElement();
-      return (Protobuf) root;
-    }
-    TreeIterator<Object> contents = getAllContents(resource, true);
-    if (contents.hasNext()) {
-      Object next = contents.next();
-      if (next instanceof Protobuf) return (Protobuf) next;
-    }
-    return null;
+  private <T extends Type> Collection<IEObjectDescription> publicImportedTypes(Protobuf root, Class<T> targetType) {
+    List<Import> allImports = finder.publicImportsIn(root);
+    if (allImports.isEmpty()) return emptyList();
+    ResourceSet resourceSet = root.eResource().getResourceSet();
+    return importedTypes(allImports, finder.packageOf(root), resourceSet, targetType);
   }
 
   private boolean arePackagesRelated(Package aPackage, EObject root) {
@@ -140,7 +124,7 @@
     return packageResolver.areRelated(aPackage, p);
   }
 
-  private <T extends Type> Collection<IEObjectDescription> children(Resource resource, Class<T> targetType) {
+  private <T extends Type> Collection<IEObjectDescription> localTypes(Resource resource, Class<T> targetType) {
     List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
     TreeIterator<Object> contents = getAllContents(resource, true);
     while (contents.hasNext()) {
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/Imports.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/Imports.java
index ea34fd0..d2ed78e 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/Imports.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/Imports.java
@@ -11,7 +11,8 @@
 import static com.google.eclipse.protobuf.scoping.ProtoDescriptor.DESCRIPTOR_IMPORT_URI;
 
 import com.google.eclipse.protobuf.protobuf.Import;
-import com.google.inject.Singleton;
+import com.google.eclipse.protobuf.scoping.ProtoDescriptorProvider;
+import com.google.inject.*;
 
 /**
  * Utility methods related to imports.
@@ -21,6 +22,8 @@
 @Singleton
 public class Imports {
 
+  @Inject private ProtoDescriptorProvider descriptorProvider;
+
   /**
    * Indicates whether the URI of the given import is equal to the path of descriptor.proto
    * ("google/protobuf/descriptor.proto").
@@ -42,4 +45,14 @@
   public boolean isUnresolvedDescriptorUri(String uri) {
     return DESCRIPTOR_IMPORT_URI.equals(uri);
   }
+
+  /**
+   * Indicates whether the given <code>{@link Import}</code> is pointing to descriptor.proto.
+   * @param anImport the given import to check.
+   * @return {@code true} if the given import is pointing to descriptor.proto, {@code false} otherwise.
+   */
+  public boolean isImportingDescriptor(Import anImport) {
+    String descriptorLocation = descriptorProvider.descriptorLocation().toString();
+    return descriptorLocation.equals(anImport.getImportURI());
+  }
 }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/ProtobufElementFinder.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/ProtobufElementFinder.java
index 64ba56d..e3a255c 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/ProtobufElementFinder.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/ProtobufElementFinder.java
@@ -9,10 +9,14 @@
 package com.google.eclipse.protobuf.util;
 
 import static java.util.Collections.unmodifiableList;
+import static org.eclipse.emf.ecore.util.EcoreUtil.getAllContents;
 
 import java.util.*;
 
+import org.eclipse.emf.common.util.TreeIterator;
 import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.xtext.resource.XtextResource;
 
 import com.google.eclipse.protobuf.protobuf.*;
 import com.google.eclipse.protobuf.protobuf.Enum;
@@ -103,4 +107,21 @@
     }
     return unmodifiableList(imports);
   }
-}
+
+  /**
+   * Returns the root element of the given resource.
+   * @param resource the given resource.
+   * @return the root element of the given resource, or {@code null} if the given resource does not have a root element.
+   */
+  public Protobuf rootOf(Resource resource) {
+    if (resource instanceof XtextResource) {
+      EObject root = ((XtextResource) resource).getParseResult().getRootASTElement();
+      return (Protobuf) root;
+    }
+    TreeIterator<Object> contents = getAllContents(resource, true);
+    if (contents.hasNext()) {
+      Object next = contents.next();
+      if (next instanceof Protobuf) return (Protobuf) next;
+    }
+    return null;
+  }}