Fixed the scoping of elements with intersecting packages.

Change-Id: I40a143395e7eabecd4586f1674c97ac8fecc2012
Signed-off-by: Alexander Rookey <atrookey@google.com>
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue131_AddOptionsForService_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue131_AddOptionsForService_Test.java
index d924229..0ee39e3 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue131_AddOptionsForService_Test.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue131_AddOptionsForService_Test.java
@@ -51,8 +51,6 @@
   @Test public void should_support_Service_options() {
     Option option = xtext.find("code", ")", Option.class);
     IScope scope = scopeProvider.getScope(option.getSource(), OPTION_SOURCE__TARGET);
-    assertThat(descriptionsIn(scope), contain("code", "proto.code", "google.proto.code",
-        "com.google.proto.code", "info", "proto.info", "google.proto.info",
-        "com.google.proto.info"));
+    assertThat(descriptionsIn(scope), contain("code", "com.google.proto.code"));
   }
 }
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue147_AddSupportForGroupOptions_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue147_AddSupportForGroupOptions_Test.java
index 8abafc0..af74aec 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue147_AddSupportForGroupOptions_Test.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue147_AddSupportForGroupOptions_Test.java
@@ -69,8 +69,6 @@
     CustomFieldOption option = xtext.find("code", ")", CustomFieldOption.class);
     IScope scope = scopeProvider.scope_OptionSource_target(option.getSource(),
         OPTION_SOURCE__TARGET);
-    assertThat(descriptionsIn(scope), contain("code", "proto.code", "google.proto.code",
-        "com.google.proto.code", "info", "proto.info", "google.proto.info",
-        "com.google.proto.info"));
+    assertThat(descriptionsIn(scope), contain("com.google.proto.code", "com.google.proto.info"));
   }
 }
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue161_PackageScoping_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue161_PackageScoping_Test.java
index 35ad6a2..4b132ba 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue161_PackageScoping_Test.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue161_PackageScoping_Test.java
@@ -62,7 +62,6 @@
     when(reference.getEContainingClass()).thenReturn(COMPLEX_TYPE_LINK);
     MessageField field = xtext.find("type", " =", MessageField.class);
     IScope scope = scopeProvider.getScope((ComplexTypeLink) field.getType(), reference);
-    assertThat(descriptionsIn(scope), contain("base.shared.Type", "proto.base.shared.Type",
-        "google.proto.base.shared.Type", "com.google.proto.base.shared.Type"));
+    assertThat(descriptionsIn(scope), contain("com.google.proto.base.shared.Type"));
   }
 }
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue167_PackageScopingWithNestedTypes_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue167_PackageScopingWithNestedTypes_Test.java
index 6357a39..9f5b7ce 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue167_PackageScopingWithNestedTypes_Test.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/bugs/Issue167_PackageScopingWithNestedTypes_Test.java
@@ -8,17 +8,12 @@
  */
 package com.google.eclipse.protobuf.bugs;
 
-import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.when;
-
-import static com.google.eclipse.protobuf.junit.IEObjectDescriptions.descriptionsIn;
+import static org.junit.Assert.assertEquals;
 import static com.google.eclipse.protobuf.junit.core.IntegrationTestModule.integrationTestModule;
 import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith;
-import static com.google.eclipse.protobuf.junit.matchers.ContainNames.contain;
-import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.COMPLEX_TYPE;
-import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.COMPLEX_TYPE_LINK;
+import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.COMPLEX_TYPE_LINK__TARGET;
 
-import org.eclipse.emf.ecore.EReference;
+import org.eclipse.xtext.naming.QualifiedName;
 import org.eclipse.xtext.scoping.IScope;
 import org.junit.Rule;
 import org.junit.Test;
@@ -36,7 +31,6 @@
 public class Issue167_PackageScopingWithNestedTypes_Test {
   @Rule public XtextRule xtext = overrideRuntimeModuleWith(integrationTestModule());
 
-  @Inject private EReference reference;
   @Inject private ProtobufScopeProvider scopeProvider;
 
   // // Create file types.proto
@@ -60,12 +54,11 @@
   //   repeated base.shared.Outer.Type type = 1;
   // }
   @Test public void should_include_package_intersection() {
-    when(reference.getEReferenceType()).thenReturn(COMPLEX_TYPE);
-    when(reference.getEContainingClass()).thenReturn(COMPLEX_TYPE_LINK);
     MessageField field = xtext.find("type", " =", MessageField.class);
-    IScope scope = scopeProvider.getScope((ComplexTypeLink) field.getType(), reference);
-    assertThat(descriptionsIn(scope), contain("base.shared.Outer.Type", "proto.base.shared.Outer.Type",
-            "google.proto.base.shared.Outer.Type",
-            "com.google.proto.base.shared.Outer.Type"));
+    IScope scope = scopeProvider.getScope((ComplexTypeLink) field.getType(),
+        COMPLEX_TYPE_LINK__TARGET);
+    QualifiedName unqualifiedName = QualifiedName.create("base","shared","Outer","Type");
+    String qualifiedName = scope.getSingleElement(unqualifiedName).getQualifiedName().toString();
+    assertEquals(qualifiedName, "com.google.proto.base.shared.Outer.Type");
   }
 }
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_ComplexTypeLink_target_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_ComplexTypeLink_target_Test.java
index 8062b5d..ba61f9a 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_ComplexTypeLink_target_Test.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_ComplexTypeLink_target_Test.java
@@ -62,10 +62,8 @@
   @Test public void should_provide_Types() {
     MessageField field = xtext.find("type", MessageField.class);
     IScope scope = scopeProvider.getScope(typeOf(field), COMPLEX_TYPE_LINK__TARGET);
-    assertThat(descriptionsIn(scope), containAll("Type", "proto.Type", "google.proto.Type",
-        "com.google.proto.Type", "Address", "proto.Address", "google.proto.Address",
-        "com.google.proto.Address", "Contact", "proto.Contact", "google.proto.Contact",
-        "com.google.proto.Contact"));
+    assertThat(descriptionsIn(scope), containAll("Type", "com.google.proto.Type", "Address",
+        "com.google.proto.Address", "Contact", "com.google.proto.Contact"));
   }
 
   // // Create file types.proto
@@ -97,7 +95,7 @@
     MessageField field = xtext.find("type", " =", MessageField.class);
     IScope scope = scopeProvider.getScope(typeOf(field), COMPLEX_TYPE_LINK__TARGET);
     assertThat(descriptionsIn(scope), containAll("test.proto.Type", "test.proto.Address",
-        "Contact", "proto.Contact", "google.proto.Contact", "com.google.proto.Contact"));
+        "Contact", "com.google.proto.Contact"));
   }
 
   // // Create file types.proto
@@ -128,10 +126,8 @@
   @Test public void should_provide_imported_Types_with_equal_package() {
     MessageField field = xtext.find("type", " =", MessageField.class);
     IScope scope = scopeProvider.getScope(typeOf(field), COMPLEX_TYPE_LINK__TARGET);
-    assertThat(descriptionsIn(scope), containAll("Type", "proto.Type", "google.proto.Type",
-        "com.google.proto.Type", "Address", "proto.Address", "google.proto.Address",
-        "com.google.proto.Address", "Contact", "proto.Contact", "google.proto.Contact",
-        "com.google.proto.Contact"));
+    assertThat(descriptionsIn(scope), containAll("Type", "com.google.proto.Type", "Address",
+        "com.google.proto.Address", "Contact", "com.google.proto.Contact"));
   }
 
   // // Create file types.proto
@@ -163,7 +159,7 @@
     MessageField field = xtext.find("type", " =", MessageField.class);
     IScope scope = scopeProvider.getScope(typeOf(field), COMPLEX_TYPE_LINK__TARGET);
     assertThat(descriptionsIn(scope), containAll("test.proto.Type", "test.proto.Address",
-        "Contact", "proto.Contact", "google.proto.Contact", "com.google.proto.Contact"));
+        "Contact", "com.google.proto.Contact"));
   }
 
   // // Create file public-types.proto
@@ -202,7 +198,7 @@
     MessageField field = xtext.find("type", " =", MessageField.class);
     IScope scope = scopeProvider.getScope(typeOf(field), COMPLEX_TYPE_LINK__TARGET);
     assertThat(descriptionsIn(scope), containAll("test.proto.Type", "test.proto.Address",
-        "Contact", "proto.Contact", "google.proto.Contact", "com.google.proto.Contact"));
+        "Contact", "com.google.proto.Contact"));
   }
 
   // // Create file sample_proto.proto
@@ -224,13 +220,13 @@
   //  message Fruits {
   //   optional Fruit grape = 1;
   // }
-  @Test public void should_provide_nearest_ComplexType() {
-    Enum expectedEnum = xtext.find("Fruit", " {", Enum.class);
+  @Test public void should_provide_ComplexType_when_packages_intersect() {
     MessageField field = xtext.find("grape", " =", MessageField.class);
+    Enum expectedEnum = (Enum) field.eContainer().eContainer().eContents().get(3);
     IScope scope = scopeProvider.getScope(field, COMPLEX_TYPE_LINK__TARGET);
     Object scopedEnum =
         scope
-        .getSingleElement(QualifiedName.create("sample", "proto", "foo", "Fruit"))
+        .getSingleElement(QualifiedName.create("Fruit"))
         .getEObjectOrProxy();
     assertEquals(expectedEnum, scopedEnum);
   }
@@ -248,8 +244,7 @@
     MapType mapType = ((MapTypeLink) mapField.getType()).getTarget();
     TypeLink valueType = mapType.getValueType();
     IScope scope = scopeProvider.getScope(valueType, COMPLEX_TYPE_LINK__TARGET);
-    assertThat(descriptionsIn(scope), containAll("Foo", "proto.foo.Foo", "foo.Foo",
-        "Bar", "proto.foo.Bar", "foo.Bar", "sample.proto.foo.Foo",
+    assertThat(descriptionsIn(scope), containAll("Foo", "Bar", "sample.proto.foo.Foo",
         "sample.proto.foo.Bar"));
   }
 
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionSource_target_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionSource_target_Test.java
index 175a43a..9381ee3 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionSource_target_Test.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionSource_target_Test.java
@@ -72,9 +72,7 @@
   @Test public void should_provide_sources_for_custom_option() {
     Option option = xtext.find("code", ")", Option.class);
     IScope scope = scopeProvider.getScope(option.getSource(), OPTION_SOURCE__TARGET);
-    assertThat(descriptionsIn(scope), contain("code", "proto.code", "google.proto.code",
-        "com.google.proto.code", "info", "proto.info", "google.proto.info",
-        "com.google.proto.info"));
+    assertThat(descriptionsIn(scope), contain("code", "com.google.proto.code"));
   }
 
   // // Create file custom-options.proto
@@ -120,9 +118,7 @@
   @Test public void should_provide_imported_sources_for_custom_option_with_equal_package() {
     Option option = xtext.find("code", ")", Option.class);
     IScope scope = scopeProvider.getScope(option.getSource(), OPTION_SOURCE__TARGET);
-    assertThat(descriptionsIn(scope), contain("code", "proto.code", "google.proto.code",
-        "com.google.proto.code", "info", "proto.info", "google.proto.info",
-        "com.google.proto.info"));
+    assertThat(descriptionsIn(scope), contain("code", "com.google.proto.code"));
   }
 
   // syntax = "proto2";
@@ -140,8 +136,6 @@
   @Test public void should_provide_sources_for_custom_field_option() {
     CustomFieldOption option = xtext.find("code", ")", CustomFieldOption.class);
     IScope scope = scopeProvider.getScope(option.getSource(), OPTION_SOURCE__TARGET);
-    assertThat(descriptionsIn(scope), contain("code", "proto.code", "google.proto.code",
-        "com.google.proto.code", "info", "proto.info", "google.proto.info",
-        "com.google.proto.info"));
+    assertThat(descriptionsIn(scope), contain("code", "com.google.proto.code"));
   }
 }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportScope.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportScope.java
index 25e8514..cdec890 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportScope.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportScope.java
@@ -9,10 +9,7 @@
 package com.google.eclipse.protobuf.scoping;
 
 import static com.google.eclipse.protobuf.model.util.QualifiedNames.removeLeadingDot;
-
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 
 import org.eclipse.emf.ecore.EClass;
@@ -24,81 +21,45 @@
 import org.eclipse.xtext.scoping.impl.ImportNormalizer;
 import org.eclipse.xtext.scoping.impl.ImportScope;
 
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Multimap;
-
 /**
- * {@link ImportScope} that allows additional ImportNormalizers to be added.
+ * {@link ImportScope} with support for intersecting packages.
  *
  * @author (atrookey@google.com) Alexander Rookey
  */
 public class ProtobufImportScope extends ImportScope {
-  private final EClass type;
-  private List<ImportNormalizer> normalizers;
+  private final List<ImportNormalizer> importNormalizersForIntersectingPackages;
+  private EClass type;
 
   public ProtobufImportScope(
       List<ImportNormalizer> namespaceResolvers,
+      List<ImportNormalizer> intersectingNamespaceResolvers,
       IScope parent,
       ISelectable importFrom,
       EClass type,
       boolean ignoreCase) {
     super(namespaceResolvers, parent, importFrom, type, ignoreCase);
+    this.importNormalizersForIntersectingPackages = intersectingNamespaceResolvers;
     this.type = type;
-    this.normalizers = removeDuplicates(namespaceResolvers);
   }
 
-  /*
-   * Override {@link ImportScope.getAliasedElements(Iterable<IEObjectDescription>)} to use local
-   * {@link ImportNormalizer} list.
-   */
-  @Override
-  protected Iterable<IEObjectDescription> getAliasedElements(
-      Iterable<IEObjectDescription> candidates) {
-    Multimap<QualifiedName, IEObjectDescription> keyToDescription = LinkedHashMultimap.create();
-    Multimap<QualifiedName, ImportNormalizer> keyToNormalizer = HashMultimap.create();
-
-    for (IEObjectDescription imported : candidates) {
-      QualifiedName fullyQualifiedName = imported.getName();
-      for (ImportNormalizer normalizer : normalizers) {
-        QualifiedName alias = normalizer.deresolve(fullyQualifiedName);
-        if (alias != null) {
-          QualifiedName key = alias;
-          if (isIgnoreCase()) {
-            key = key.toLowerCase();
-          }
-          keyToDescription.put(key, new AliasedEObjectDescription(alias, imported));
-          keyToNormalizer.put(key, normalizer);
-        }
-      }
-    }
-    for (QualifiedName name : keyToNormalizer.keySet()) {
-      if (keyToNormalizer.get(name).size() > 1) keyToDescription.removeAll(name);
-    }
-    return keyToDescription.values();
-  }
-
-  /*
-   * Override {@link ImportScope.getLocalElementsByName(QualifiedName)} to use local
-   * {@link ImportNormalizer} list.
-   */
   @Override
   protected Iterable<IEObjectDescription> getLocalElementsByName(QualifiedName name) {
+    Iterable<IEObjectDescription> localElementsByName = super.getLocalElementsByName(name);
+    if (!localElementsByName.iterator().hasNext()) {
+      localElementsByName = getLocalElementsFromIntersectingPackages(name);
+    }
+    return localElementsByName;
+  }
+
+  private Iterable<IEObjectDescription> getLocalElementsFromIntersectingPackages(QualifiedName name) {
     List<IEObjectDescription> result = new ArrayList<>();
-    QualifiedName resolvedQualifiedName = null;
     ISelectable importFrom = getImportFrom();
-    for (ImportNormalizer normalizer : normalizers) {
+    for (ImportNormalizer normalizer : importNormalizersForIntersectingPackages) {
       final QualifiedName resolvedName = normalizer.resolve(name);
       if (resolvedName != null) {
         Iterable<IEObjectDescription> resolvedElements =
             importFrom.getExportedObjects(type, resolvedName, isIgnoreCase());
         for (IEObjectDescription resolvedElement : resolvedElements) {
-          if (resolvedQualifiedName == null) resolvedQualifiedName = resolvedName;
-          else if (!resolvedQualifiedName.equals(resolvedName)) {
-            if (result.get(0).getEObjectOrProxy() != resolvedElement.getEObjectOrProxy()) {
-              return Collections.emptyList();
-            }
-          }
           QualifiedName alias = normalizer.deresolve(resolvedElement.getName());
           if (alias == null)
             throw new IllegalStateException(
@@ -117,12 +78,4 @@
   public IEObjectDescription getSingleElement(QualifiedName name) {
     return super.getSingleElement(removeLeadingDot(name));
   }
-
-  public void addNormalizer(ImportNormalizer normalizer) {
-    normalizers.add(normalizer);
-  }
-
-  public void addAllNormalizers(Collection<ImportNormalizer> normalizer) {
-    normalizers.addAll(normalizer);
-  }
 }
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 e37db31..43294fa 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
@@ -8,11 +8,12 @@
  */
 package com.google.eclipse.protobuf.scoping;
 
-import java.lang.reflect.Array;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+
 import java.util.ArrayList;
 import java.util.List;
 
-import org.eclipse.emf.common.util.EList;
 import org.eclipse.emf.ecore.EClass;
 import org.eclipse.emf.ecore.EObject;
 import org.eclipse.emf.ecore.EReference;
@@ -48,55 +49,18 @@
       ISelectable importFrom,
       EClass type,
       boolean ignoreCase) {
-    return new ProtobufImportScope(namespaceResolvers, parent, importFrom, type, ignoreCase);
+    return createImportScope(parent, namespaceResolvers, emptyList(), importFrom, type, ignoreCase);
   }
 
-  @Override
-  protected List<ImportNormalizer> internalGetImportedNamespaceResolvers(
-      final EObject context, boolean ignoreCase) {
-    List<ImportNormalizer> importedNamespaceResolvers = new ArrayList<>();
-    EList<EObject> eContents = context.eContents();
-    for (EObject child : eContents) {
-      String name = getImportedNamespace(child);
-      if (name != null && !name.isEmpty()) {
-        ImportNormalizer resolver = createImportedNamespaceResolver(name, ignoreCase);
-        if (resolver != null) {
-          importedNamespaceResolvers.add(resolver);
-        }
-        importedNamespaceResolvers.addAll(createResolversForInnerNamespaces(name, ignoreCase));
-      }
-    }
-    return importedNamespaceResolvers;
-  }
-
-  /**
-   * Creates resolvers required for scoping to handle intersecting packages. The imported namespace
-   * {@code com.google.proto.foo} requires the following resolvers:
-   *
-   * <ul>
-   * <li>{@code com.*}
-   * <li>{@code com.google.*}
-   * <li>{@code com.google.proto.*}
-   * </ul>
-   *
-   * @param namespace the namespace.
-   * @param ignoreCase {@code true} if the resolver should be case insensitive.
-   * @return a list of the resolvers for an imported namespace
-   */
-  private List<ImportNormalizer> createResolversForInnerNamespaces(
-      String namespace, boolean ignoreCase) {
-    String[] splitValue = namespace.split("\\.");
-    List<ImportNormalizer> importedNamespaceResolvers = new ArrayList<>();
-    String currentNamespaceResolver = "";
-    for (int i = 0; i < Array.getLength(splitValue) - 1; i++) {
-      currentNamespaceResolver += splitValue[i] + ".";
-      ImportNormalizer resolver =
-          createImportedNamespaceResolver(currentNamespaceResolver, ignoreCase);
-      if (resolver != null) {
-        importedNamespaceResolvers.add(resolver);
-      }
-    }
-    return importedNamespaceResolvers;
+  protected ImportScope createImportScope(
+      IScope parent,
+      List<ImportNormalizer> namespaceResolvers,
+      List<ImportNormalizer> intersectingNormalizers,
+      ISelectable importFrom,
+      EClass type,
+      boolean ignoreCase) {
+    return new ProtobufImportScope(
+        namespaceResolvers, intersectingNormalizers, parent, importFrom, type, ignoreCase);
   }
 
   /** Creates an {@link ImportNormalizer} with wildcards. */
@@ -112,11 +76,106 @@
     return doCreateImportNormalizer(importedNamespace, WILDCARD, ignoreCase);
   }
 
-  /** Makes {@code getLocalElementsScope()} visible to {@link ProtobufScopeProvider} */
+  /**
+   * Creates {@code ImportNormalizer}s to correctly scope intersecting packages.
+   * The package {@code com.google.proto.foo} requires the following normalizers:
+   *
+   * <ul>
+   * <li>{@code com.google.proto.*}
+   * <li>{@code com.google.*}
+   * <li>{@code com.*}
+   * </ul>
+   *
+   * The {@code ImportNormalizer}s must be ordered by decreasing length.
+   * This ensures that the listed returned by
+   * {@link ProtobufImportScope.getLocalElementsFromIntersectingPackages()}
+   * includes the best matching element first.
+   *
+   * @param package The package of the protocol buffer resource.
+   * @param ignoreCase {@code true} if the resolver should be case insensitive.
+   * @return {@code ImportNormalizer}s for the packages intersecting {@code package}.
+   */
+  private List<ImportNormalizer> getImportNormalizersForIntersectingPackages(
+      QualifiedName protoPackage, boolean ignoreCase) {
+    List<ImportNormalizer> intersectingNormalizers = new ArrayList<>();
+    for (int i = 1; i < protoPackage.getSegmentCount(); i++) {
+      intersectingNormalizers.add(doCreateImportNormalizer(protoPackage.skipLast(i), WILDCARD, ignoreCase));
+    }
+    return intersectingNormalizers;
+  }
+
+ /**
+  * Includes support for intersecting packages.
+  */
   @Override
+  protected IScope getLocalElementsScope(IScope parent, EObject context, EReference reference) {
+    IScope result = parent;
+    ISelectable allDescriptions = getAllDescriptions(context.eResource());
+    QualifiedName name = getQualifiedNameOfLocalElement(context);
+    boolean ignoreCase = isIgnoreCase(reference);
+    final List<ImportNormalizer> namespaceResolvers =
+        getImportedNamespaceResolvers(context, ignoreCase);
+    if (!namespaceResolvers.isEmpty()) {
+      if (isRelativeImport() && name != null && !name.isEmpty()) {
+        ImportNormalizer localNormalizer = doCreateImportNormalizer(name, WILDCARD, ignoreCase);
+        result =
+            createImportScope(
+                result,
+                singletonList(localNormalizer),
+                allDescriptions,
+                reference.getEReferenceType(),
+                isIgnoreCase(reference));
+      }
+      List<ImportNormalizer> intersectingNormalizers;
+      // Only a package has the structural feature "importedNamespace".
+      // Therefore there should only be one.
+      if (namespaceResolvers.size() == 1) {
+        ImportNormalizer normalizer = namespaceResolvers.get(0);
+        intersectingNormalizers =
+            getImportNormalizersForIntersectingPackages(
+                normalizer.getImportedNamespacePrefix(), ignoreCase);
+      } else {
+        intersectingNormalizers = emptyList();
+      }
+      result =
+          createImportScope(
+              result,
+              namespaceResolvers,
+              intersectingNormalizers,
+              null,
+              reference.getEReferenceType(),
+              isIgnoreCase(reference));
+    }
+    if (name != null) {
+      ImportNormalizer localNormalizer = doCreateImportNormalizer(name, WILDCARD, ignoreCase);
+      result =
+          createImportScope(
+              result,
+              singletonList(localNormalizer),
+              allDescriptions,
+              reference.getEReferenceType(),
+              isIgnoreCase(reference));
+    }
+    return result;
+  }
+
+  /**
+   * Create an {@link ImportScope} using a {@code List<ImportNormalizer>} generated in
+   * {@link ProtobufScopeProvider}
+   */
   protected IScope getLocalElementsScope(
-      IScope parent, EObject context, EReference reference) {
-    return super.getLocalElementsScope(parent, context, reference);
+      IScope parent,
+      final EObject context,
+      final EReference reference,
+      List<ImportNormalizer> normalizers) {
+    IScope result = parent;
+    ISelectable allDescriptions = getAllDescriptions(context.eResource());
+    return createImportScope(
+        result,
+        normalizers,
+        allDescriptions,
+        reference.getEReferenceType(),
+        isIgnoreCase(reference));
   }
 
   /** Makes {@code getResourceScope()} visible to {@link ProtobufScopeProvider} */
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 5baf2c7..ab63d3b 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
@@ -10,6 +10,8 @@
 
 import static com.google.eclipse.protobuf.util.Tracer.DEBUG_SCOPING;
 import static com.google.eclipse.protobuf.validation.ProtobufResourceValidator.getScopeProviderTimingCollector;
+import static java.util.Collections.singletonList;
+
 import com.google.eclipse.protobuf.naming.ProtobufQualifiedNameConverter;
 import com.google.eclipse.protobuf.naming.ProtobufQualifiedNameProvider;
 import com.google.eclipse.protobuf.protobuf.ComplexType;
@@ -136,22 +138,24 @@
           scope = getGlobalScopeProvider().getScope(complexType.eResource(), reference);
           List<ImportNormalizer> normalizers =
               createImportNormalizersForComplexType(complexType, false);
-          scope = createProtobufImportScope(scope, complexType, reference);
-          ((ProtobufImportScope) scope).addAllNormalizers(normalizers);
+          scope = createProtobufImportScope(scope, complexType, reference, normalizers);
         }
       }
       if (indexedElement instanceof Group) {
         Group group = (Group) indexedElement;
         scope = getGlobalScopeProvider().getScope(group.eResource(), reference);
         ImportNormalizer normalizer = createImportNormalizerForEObject(group, false);
-        scope = createProtobufImportScope(scope, group, reference);
-        ((ProtobufImportScope) scope).addNormalizer(normalizer);
+        scope = createProtobufImportScope(scope, group, reference, singletonList(normalizer));
       }
       scopeMap.put(reference, scope);
     }
     return scopeMap.get(reference);
   }
 
+  /**
+   * Let the local scope provider compute the list of {@link ImportNormalizer}s to be associated with
+   * the {@link ProtobufImportScope}.
+   */
   private IScope createProtobufImportScope(IScope parent, EObject context, EReference reference) {
     IScope scope = parent;
     if (context.eContainer() == null) {
@@ -162,11 +166,27 @@
     return getLocalScopeProvider().getLocalElementsScope(scope, context, reference);
   }
 
+  /**
+   * Associate the {@link ProtobufImportScope} with the list of {@link ImportNormalizer}s
+   * passed as an argument.
+   */
+  private IScope createProtobufImportScope(
+      IScope parent, EObject context, EReference reference, List<ImportNormalizer> normalizers) {
+    IScope scope = parent;
+    if (context.eContainer() == null) {
+      scope = getLocalScopeProvider().getResourceScope(scope, context, reference);
+    } else {
+      scope = createProtobufImportScope(scope, context.eContainer(), reference, normalizers);
+    }
+    return getLocalScopeProvider().getLocalElementsScope(scope, context, reference, normalizers);
+  }
+
   /** Returns descriptor associated with the current project. */
   private @Nullable Resource getDescriptorResource(EObject context) {
     IProject project = EResources.getProjectOf(context.eResource());
     ResourceSet resourceSet = context.eResource().getResourceSet();
-    ProtoDescriptorProvider.ProtoDescriptorInfo descriptorInfo = descriptorProvider.primaryDescriptor(project);
+    ProtoDescriptorProvider.ProtoDescriptorInfo descriptorInfo =
+        descriptorProvider.primaryDescriptor(project);
     return resourceSet.getResource(descriptorInfo.location, true);
   }
 
@@ -406,8 +426,10 @@
                         .createImportedNamespaceResolver(descriptorMessage, false);
                 scope =
                     createProtobufImportScope(
-                        scope, getDescriptorResource(optionSource).getContents().get(0), reference);
-                ((ProtobufImportScope) scope).addNormalizer(normalizer);
+                        scope,
+                        descriptorResource.getContents().get(0),
+                        reference,
+                        singletonList(normalizer));
                 return scope;
               }
             });