Added tests, fixed a bug, made speed improvements"

Change-Id: I9846b23f1ca2b5ff33a1b9fe409e7e7997189dfb
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_LiteralLink_target_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_LiteralLink_target_Test.java
index aad3151..0f703dc 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_LiteralLink_target_Test.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_LiteralLink_target_Test.java
@@ -27,6 +27,7 @@
 import com.google.eclipse.protobuf.protobuf.FieldOption;
 import com.google.eclipse.protobuf.protobuf.Literal;
 import com.google.eclipse.protobuf.protobuf.LiteralLink;
+import com.google.eclipse.protobuf.protobuf.Message;
 import com.google.eclipse.protobuf.protobuf.MessageField;
 import com.google.eclipse.protobuf.protobuf.Option;
 import com.google.inject.Inject;
@@ -108,7 +109,6 @@
   @Test public void should_provide_Literals_for_source_of_field_of_custom_option() {
     Option option = xtext.find("info", ")", Option.class);
     IScope scope = scopeProvider.getScope(valueOf(option), LITERAL_LINK__TARGET);
-    xtext.find("Type", " {", Enum.class);
     assertThat(descriptionsIn(scope), contain("ONE", "TWO"));
   }
 
@@ -146,7 +146,6 @@
   @Test public void should_provide_Literals_for_source_of_custom_field_option() {
     FieldOption option = xtext.find("type", ")", FieldOption.class);
     IScope scope = scopeProvider.getScope(valueOf(option), LITERAL_LINK__TARGET);
-    xtext.find("Type", " {", Enum.class);
     assertThat(descriptionsIn(scope), contain("ONE", "TWO"));
   }
 
@@ -173,7 +172,6 @@
   @Test public void should_provide_Literals_for_source_of_field_in_custom_field_option() {
     FieldOption option = xtext.find("info", ")", FieldOption.class);
     IScope scope = scopeProvider.getScope(valueOf(option), LITERAL_LINK__TARGET);
-    xtext.find("Type", " {", Enum.class);
     assertThat(descriptionsIn(scope), contain("ONE", "TWO"));
   }
 
@@ -198,19 +196,65 @@
   //   optional Grape grape = 4 [default = BAR];
   // }
   @Test public void should_provide_Literal_nearest_to_option() {
-    // Beta
     MessageField field = xtext.find("grape", " =", MessageField.class);
     DefaultValueFieldOption option = (DefaultValueFieldOption) field.getFieldOptions().get(0);
     LiteralLink link = (LiteralLink) option.getValue();
     Enum enumBeta = (Enum)link.getTarget().eContainer();
-
-    // Alpha
     Enum beta = xtext.find("Beta", Enum.class);
     Literal literal = (Literal) beta.getElements().get(0);
     Enum expectedEnum = (Enum) literal.eContainer();
     assertEquals(expectedEnum, enumBeta);
   }
 
+  // syntax = "proto2";
+  //
+  // message Status {
+  //   enum StatusCode {
+  //     SUCCESS = 1;
+  //   }
+  //   required StatusCode statusCode = 1 [default = SUCCESS];
+  // }
+  //
+  // message Response {
+  //   enum Status {
+  //     SUCCESS = 1;
+  //   }
+  //   required Status status = 1 [default = SUCCESS];
+  // }
+  @Test public void should_provide_Literals_in_nearest_scope_to_default_value_field_option() {
+    MessageField field = xtext.find("status", " =", MessageField.class);
+    DefaultValueFieldOption option = (DefaultValueFieldOption) field.getFieldOptions().get(0);
+    LiteralLink link = (LiteralLink) option.getValue();
+    Enum enumActual = (Enum)link.getTarget().eContainer();
+    Message message = xtext.find("Response", Message.class);
+    Enum status = (Enum) message.getElements().get(0);
+    Literal literal = (Literal) status.getElements().get(0);
+    Enum expectedEnum = (Enum) literal.eContainer();
+    assertEquals(expectedEnum, enumActual);
+  }
+
+  // syntax = "proto2";
+  //
+  // message Status {
+  //   enum StatusCode {
+  //     SUCCESS = 1;
+  //   }
+  //   required StatusCode statusCode = 1 [default = SUCCESS];
+  // }
+  //
+  // message Response {
+  //   enum Status {
+  //     SUCCESS = 1;
+  //   }
+  //   required Status status = 1 [default = SUCCESS];
+  // }
+  @Test public void should_provide_Literals_for_source_of_default_value_field_option() {
+    MessageField field = xtext.find("status", " =", MessageField.class);
+    DefaultValueFieldOption option = (DefaultValueFieldOption) field.getFieldOptions().get(0);
+    IScope scope = scopeProvider.getScope(valueOf(option), LITERAL_LINK__TARGET);
+    assertThat(descriptionsIn(scope), contain("SUCCESS"));
+  }
+
   private static LiteralLink valueOf(FieldOption option) {
     return (LiteralLink) option.getValue();
   }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportedNamespaceAwareLocalScopeProvider.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportedNamespaceAwareLocalScopeProvider.java
index 9767067..f3cbf24 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportedNamespaceAwareLocalScopeProvider.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportedNamespaceAwareLocalScopeProvider.java
@@ -21,9 +21,11 @@
 import org.eclipse.emf.ecore.resource.Resource;
 import org.eclipse.xtext.naming.QualifiedName;
 import org.eclipse.xtext.resource.ISelectable;
+import org.eclipse.xtext.scoping.IGlobalScopeProvider;
 import org.eclipse.xtext.scoping.IScope;
 import org.eclipse.xtext.scoping.impl.ImportNormalizer;
 import org.eclipse.xtext.scoping.impl.ImportScope;
+import org.eclipse.xtext.scoping.impl.ImportUriGlobalScopeProvider;
 import org.eclipse.xtext.scoping.impl.ImportedNamespaceAwareLocalScopeProvider;
 import org.eclipse.xtext.util.Strings;
 
@@ -39,6 +41,7 @@
 public class ProtobufImportedNamespaceAwareLocalScopeProvider
     extends ImportedNamespaceAwareLocalScopeProvider {
   @Inject private ProtobufQualifiedNameConverter qualifiedNameConverter;
+  @Inject private IGlobalScopeProvider globalScopeProvider;
 
   private static final boolean WILDCARD = true;
 
@@ -190,4 +193,8 @@
     return ProtobufSelectableBasedScope.createScope(
         parent, allDescriptions, reference.getEReferenceType(), isIgnoreCase(reference));
   }
+
+  protected ProtobufImportUriGlobalScopeProvider getGlobalScopeProvider() {
+    return (ProtobufImportUriGlobalScopeProvider) globalScopeProvider;
+  }
 }
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 2071827..aa27afe 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
@@ -21,8 +21,8 @@
 import static org.eclipse.xtext.util.CancelIndicator.NullImpl;
 
 import com.google.eclipse.protobuf.model.util.ModelObjects;
-import com.google.eclipse.protobuf.naming.NameResolver;
 import com.google.eclipse.protobuf.naming.ProtobufQualifiedNameConverter;
+import com.google.eclipse.protobuf.naming.ProtobufQualifiedNameProvider;
 import com.google.eclipse.protobuf.preferences.general.PreferenceNames;
 import com.google.eclipse.protobuf.protobuf.AbstractCustomOption;
 import com.google.eclipse.protobuf.protobuf.AbstractOption;
@@ -46,6 +46,7 @@
 import com.google.eclipse.protobuf.protobuf.OneOf;
 import com.google.eclipse.protobuf.protobuf.OptionField;
 import com.google.eclipse.protobuf.protobuf.OptionSource;
+import com.google.eclipse.protobuf.protobuf.Package;
 import com.google.eclipse.protobuf.protobuf.Protobuf;
 import com.google.eclipse.protobuf.protobuf.Rpc;
 import com.google.eclipse.protobuf.protobuf.Stream;
@@ -71,11 +72,9 @@
 import org.eclipse.xtext.nodemodel.ICompositeNode;
 import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
 import org.eclipse.xtext.resource.IEObjectDescription;
-import org.eclipse.xtext.resource.ISelectable;
 import org.eclipse.xtext.scoping.IScope;
 import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider;
 import org.eclipse.xtext.scoping.impl.ImportNormalizer;
-import org.eclipse.xtext.scoping.impl.SelectableBasedScope;
 import org.eclipse.xtext.ui.editor.preferences.IPreferenceStoreAccess;
 import org.eclipse.xtext.util.IResourceScopeCache;
 
@@ -107,10 +106,8 @@
   @Inject private IPreferenceStoreAccess storeAccess;
   @Inject private IUriResolver uriResolver;
   @Inject private IResourceScopeCache cache;
-  @Inject private NameResolver nameResolver;
   @Inject private ProtobufQualifiedNameConverter nameConverter;
-
-  private static final String CACHEKEY = "IMPORT_NORMALIZER_CACHE_KEY";
+  @Inject private ProtobufQualifiedNameProvider nameProvider;
 
   /**
    * Returns the name of the descriptor.proto message declaring default options. The options must
@@ -158,21 +155,27 @@
 
   // TODO (atrookey) Create utility for getting package.
   private String getPackageOfResource(Resource resource) {
-    Protobuf object;
-    if (resource != null && (object = (Protobuf) resource.getContents().get(0)) != null) {
-      for (EObject content : object.getElements()) {
-        if (content instanceof com.google.eclipse.protobuf.protobuf.Package) {
-          return ((com.google.eclipse.protobuf.protobuf.Package) content).getImportedNamespace();
-        }
-      }
-    }
-    return "";
+    return cache.get(
+        "Package",
+        resource,
+        new Provider<String>() {
+          @Override
+          public String get() {
+            Protobuf protobuf;
+            if (resource != null && (protobuf = (Protobuf) resource.getContents().get(0)) != null) {
+              for (EObject content : protobuf.getElements()) {
+                if (content instanceof Package) {
+                  return ((Package) content).getImportedNamespace();
+                }
+              }
+            }
+            return "";
+          }
+        });
   }
 
-  /**
-   * Returns descriptor associated with the current project.
-   */
-  private  @Nullable Resource getDescriptorResource(EObject context) {
+  /** Returns descriptor associated with the current project. */
+  private @Nullable Resource getDescriptorResource(EObject context) {
     URI descriptorLocation;
     IProject project = EResources.getProjectOf(context.eResource());
     IPreferenceStore store = storeAccess.getWritablePreferenceStore(project);
@@ -196,22 +199,23 @@
     return null;
   }
 
-  /**
-   * Returns name of an object as a QualifiedName.
-   */
+  /** Returns name of an object as a QualifiedName. */
   private QualifiedName getEObjectName(EObject object) {
     ICompositeNode node = NodeModelUtils.getNode(object);
     String name = linkingHelper.getCrossRefNodeAsString(node, true);
     return QualifiedName.create(name);
   }
 
-  /**
-   * Returns the local scope provider.
-   */
+  /** Returns the local scope provider. */
   private ProtobufImportedNamespaceAwareLocalScopeProvider getLocalScopeProvider() {
     return (ProtobufImportedNamespaceAwareLocalScopeProvider) super.getDelegate();
   }
 
+  /** Returns the local scope provider. */
+  private ProtobufImportUriGlobalScopeProvider getGlobalScopeProvider() {
+    return getLocalScopeProvider().getGlobalScopeProvider();
+  }
+
   @Override
   public IScope getScope(EObject context, EReference reference) {
     if (DEBUG_SCOPING) {
@@ -224,9 +228,7 @@
     return scope;
   }
 
-  /**
-   * Recursively scope {@code FieldName} starting with an {@code OptionSource}.
-   */
+  /** Recursively scope {@code FieldName} starting with an {@code OptionSource}. */
   private IScope getScopeOfFieldName(
       IScope delegatedScope, OptionSource optionSource, EReference reference) {
     IScope retval = IScope.NULLSCOPE;
@@ -240,9 +242,7 @@
     return retval;
   }
 
-  /**
-   * Locally scope any children of {@code context} that are of type {@code OneOf}.
-   */
+  /** Locally scope any children of {@code context} that are of type {@code OneOf}. */
   private IScope getScopeOfOneOf(IScope scope, EObject context, EReference reference) {
     IScope result = scope;
     for (EObject element : context.eContents()) {
@@ -253,11 +253,13 @@
     return result;
   }
 
-  /**
-   * Recursively scope {@code OptionField} starting with an {@code OptionSource}.
-   */
+  /** Recursively scope {@code OptionField} starting with an {@code OptionSource}. */
   private IScope getScopeOfOptionField(
-      IScope scope, OptionSource optionSource, EReference reference, int fieldIndex, EList<OptionField> fields) {
+      IScope scope,
+      OptionSource optionSource,
+      EReference reference,
+      int fieldIndex,
+      EList<OptionField> fields) {
     if (fieldIndex < 0 || fields.size() <= fieldIndex) {
       throw new IllegalArgumentException();
     }
@@ -362,9 +364,7 @@
   /**
    * Recursively scopes the {@code FieldName} starting with the {@code OptionSource}.
    *
-   * For example:
-   *
-   * <pre>
+   * <p>For example: <pre>
    * message FooOptions {
    *   optional int32 opt1 = 1;
    * }
@@ -419,12 +419,10 @@
   }
 
   /**
-   * Creates a scope containing elements of type {@code Literal} that can be
-   * referenced with their local name only.
+   * Creates a scope containing elements of type {@code Literal} that can be referenced with their
+   * local name only.
    *
-   * For example:
-   *
-   * <pre>
+   * <p>For example: <pre>
    * enum MyEnum {
    *   FOO = 1;
    * }
@@ -440,83 +438,66 @@
    */
   public IScope scope_LiteralLink_target(LiteralLink literalLink, EReference reference) {
     IScope scope = IScope.NULLSCOPE;
-    scope =
-        createLiteralLinkResolvedScopeForResource(
-            reference, scope, getDescriptorResource(literalLink));
-    scope = createLiteralLinkResolvedScopeForResource(reference, scope, literalLink.eResource());
+    Resource descriptorResource = getDescriptorResource(literalLink);
+    EObject descriptor = descriptorResource.getContents().get(0);
+    scope = createLiteralLinkResolvedScope(reference, scope, descriptor);
+    scope = createLiteralLinkResolvedScope(reference, scope, literalLink);
     return scope;
   }
 
-  // TODO (atrookey) Create resolvers for ProtobufImportScopes at all scope levels,
-  // not just top level.
-  private IScope createLiteralLinkResolvedScopeForResource(
-      EReference reference, IScope parent, Resource resource) {
-    IScope scope = parent;
-    scope = getProtobufImportScope(scope, resource, reference);
-    List<ImportNormalizer> protoNormalizers =
-        cache.get(
-            CACHEKEY,
-            resource,
-            new Provider<List<ImportNormalizer>>() {
-              @Override
-              public List<ImportNormalizer> get() {
-                return createEnumElementResolvers(
-                    resource.getContents().get(0), getPackageOfResource(resource), false);
-              }
-            });
-    if (scope instanceof ProtobufImportScope) {
-      for (ImportNormalizer normalizer : protoNormalizers) {
-        ((ProtobufImportScope) scope).addNormalizer(normalizer);
-      }
-    } else if (scope instanceof SelectableBasedScope) {
-      ISelectable allDescriptions = getLocalScopeProvider().getAllDescriptions(resource);
-      scope =
-          getLocalScopeProvider()
-              .createImportScope(
-                  scope, protoNormalizers, allDescriptions, reference.getEReferenceType(), false);
+  private IScope createLiteralLinkResolvedScope(
+      EReference reference, IScope parent, EObject context) {
+    if (context == null) {
+      return parent;
     }
-    return scope;
+    final IScope parentScope =
+        createLiteralLinkResolvedScope(reference, parent, context.eContainer());
+    return cache.get(
+        context,
+        context.eResource(),
+        new Provider<IScope>() {
+          @Override
+          public IScope get() {
+            List<ImportNormalizer> protoNormalizers = createEnumElementResolvers(context, false);
+            IScope scope = getSingleLevelProtobufImport(parentScope, context, reference);
+            if (scope instanceof ProtobufImportScope) {
+              for (ImportNormalizer normalizer : protoNormalizers) {
+                ((ProtobufImportScope) scope).addNormalizer(normalizer);
+              }
+            }
+            return scope;
+          }
+        });
   }
 
   /**
-   * Creates an {@code ImportNormalizer} for every {@code Enum} that is a descendant of {@code context}.
+   * Creates an {@code ImportNormalizer} for every {@code Enum} that is a descendant of {@code
+   * context}.
    */
-  private List<ImportNormalizer> createEnumElementResolvers(
-      EObject context, String qualifiedName, boolean ignoreCase) {
+  private List<ImportNormalizer> createEnumElementResolvers(EObject context, boolean ignoreCase) {
     List<ImportNormalizer> importedNamespaceResolvers = new ArrayList<>();
     for (EObject child : context.eContents()) {
       if (child instanceof Enum) {
-        String name = appendNameOfEObject(qualifiedName, child);
-        if (!name.isEmpty()) {
+        QualifiedName name = nameProvider.getFullyQualifiedName(child);
+        if (name != null && !name.isEmpty()) {
           ImportNormalizer resolver =
-              getLocalScopeProvider().createImportedNamespaceResolver(name, ignoreCase);
+              getLocalScopeProvider().createImportedNamespaceResolver(name.toString(), ignoreCase);
           if (resolver != null) {
             importedNamespaceResolvers.add(resolver);
           }
         }
       }
       if (child instanceof Message) {
-        String name = appendNameOfEObject(qualifiedName, child);
-        importedNamespaceResolvers.addAll(createEnumElementResolvers(child, name, ignoreCase));
+        importedNamespaceResolvers.addAll(createEnumElementResolvers(child, ignoreCase));
       }
     }
     return importedNamespaceResolvers;
   }
 
-  private String appendNameOfEObject(String qualifiedName, EObject child) {
-    String childName = nameResolver.nameOf(child);
-    if (qualifiedName.isEmpty()) {
-      return childName;
-    }
-    return qualifiedName + nameConverter.getDelimiter() + childName;
-  }
-
   /**
    * Recursively scopes the {@code OptionField} starting with the {@code OptionSource}.
    *
-   * For example:
-   *
-   * <pre>
+   * <p>For example: <pre>
    * message Code {
    *   optional double number = 1;
    * }
@@ -558,9 +539,7 @@
   /**
    * Creates a scope containing the default options defined in descriptor.proto.
    *
-   * For example:
-   *
-   * <pre>
+   * <p>For example: <pre>
    * option java_package = "com.example.foo";
    * </pre>
    *
@@ -568,26 +547,52 @@
    * google.protobuf.FileOptions.java_package} defined in descriptor.proto.
    */
   public IScope scope_OptionSource_target(OptionSource optionSource, EReference reference) {
-    Resource descriptorResource = getDescriptorResource(optionSource);
-    String descriptorMessage =
-        getPackageOfResource(descriptorResource)
-            + nameConverter.getDelimiter()
-            + getOptionType(optionSource);
-    ImportNormalizer normalizer =
-        getLocalScopeProvider().createImportedNamespaceResolver(descriptorMessage, false);
-    IScope scope = delegateGetScope(optionSource, reference);
-    scope = getProtobufImportScope(scope, getDescriptorResource(optionSource), reference);
-    ((ProtobufImportScope) scope).addNormalizer(normalizer);
-    return scope;
+    String optionType = getOptionType(optionSource);
+    Resource resource = optionSource.eResource();
+    IScope descriptorScope = cache.get(
+        optionType,
+        resource,
+        new Provider<IScope>() {
+          @Override
+          public IScope get() {
+            IScope scope = getGlobalScopeProvider().getScope(resource, reference);
+            Resource descriptorResource = getDescriptorResource(optionSource);
+            String descriptorMessage =
+                getPackageOfResource(descriptorResource)
+                    + nameConverter.getDelimiter()
+                    + optionType;
+            ImportNormalizer normalizer =
+                getLocalScopeProvider().createImportedNamespaceResolver(descriptorMessage, false);
+            scope =
+                getProtobufImportScope(
+                    scope, getDescriptorResource(optionSource).getContents().get(0), reference);
+            ((ProtobufImportScope) scope).addNormalizer(normalizer);
+            return scope;
+          }
+        });
+    return getProtobufImportScope(descriptorScope, optionSource, reference);
   }
 
-  /** Returns the top level scope of the {@code Resource}. */
-  private ProtobufImportScope getProtobufImportScope(
-      IScope parent, Resource resource, EReference reference) {
-    EObject protobuf = resource.getContents().get(0);
-    IScope descriptorResourceScope =
-        getLocalScopeProvider().getResourceScope(parent, protobuf, reference);
+  /** Returns a multi level ProtobufImportScope {@code EObject}. */
+  private IScope getProtobufImportScope(
+      IScope parent, EObject context, EReference reference) {
+    IScope scope = parent;
+    if (context.eContainer() == null) {
+      scope = getLocalScopeProvider().getResourceScope(scope, context, reference);
+    } else {
+      scope = getProtobufImportScope(scope, context.eContainer(), reference);
+    }
+    return getLocalScopeProvider().getLocalElementsScope(scope, context, reference);
+  }
+
+  /** Returns a single level ProtobufImportScope {@code EObject}. */
+  private ProtobufImportScope getSingleLevelProtobufImport(
+      IScope parent, EObject context, EReference reference) {
+    IScope scope = parent;
+    if (context.eContainer() == null) {
+      scope = getLocalScopeProvider().getResourceScope(scope, context, reference);
+    }
     return (ProtobufImportScope)
-        getLocalScopeProvider().getLocalElementsScope(descriptorResourceScope, protobuf, reference);
+        getLocalScopeProvider().getLocalElementsScope(scope, context, reference);
   }
 }