In progress: [Issue 199] Add ability to navigate to proto element from
generated C++ code.

* Fixed pattern for qualified name matching
* Made matching more efficient
diff --git a/com.google.eclipse.protobuf.cdt.test/src/com/google/eclipse/protobuf/cdt/matching/ProtobufElementMatcher_findUriOfMatchingProtobufElement_Test.java b/com.google.eclipse.protobuf.cdt.test/src/com/google/eclipse/protobuf/cdt/matching/ProtobufElementMatcher_findUriOfMatchingProtobufElement_Test.java
new file mode 100644
index 0000000..ac4e225
--- /dev/null
+++ b/com.google.eclipse.protobuf.cdt.test/src/com/google/eclipse/protobuf/cdt/matching/ProtobufElementMatcher_findUriOfMatchingProtobufElement_Test.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2012 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.cdt.matching;
+
+import static com.google.eclipse.protobuf.junit.core.UnitTestModule.unitTestModule;
+import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.*;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.xtext.naming.*;
+import org.eclipse.xtext.resource.*;
+import org.eclipse.xtext.resource.impl.ResourceSetBasedResourceDescriptions;
+import org.junit.*;
+
+import com.google.eclipse.protobuf.cdt.mapping.CppToProtobufMapping;
+import com.google.eclipse.protobuf.junit.core.XtextRule;
+import com.google.eclipse.protobuf.protobuf.Message;
+import com.google.inject.Inject;
+
+/**
+ * Tests for <code>{@link ProtobufElementMatcher#findUriOfMatchingProtobufElement(IResourceDescription, CppToProtobufMapping)}</code>
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class ProtobufElementMatcher_findUriOfMatchingProtobufElement_Test {
+  @Rule public XtextRule xtext = overrideRuntimeModuleWith(unitTestModule());
+
+  @Inject private IQualifiedNameConverter fqnConverter;
+  @Inject private ResourceSetBasedResourceDescriptions index;
+  @Inject private ProtobufElementMatcher matcher;
+
+  // syntax = "proto2";
+  // package com.google.proto;
+  //
+  // message Outer {
+  //   message Inner {}
+  // }
+  @Test public void should_return_URI_nested_message_when_qualified_name_does_not_contain_underscores() {
+    CppToProtobufMapping mapping = messageMapping("com.google.proto.Outer.Inner");
+    URI foundUri = matcher.findUriOfMatchingProtobufElement(descriptionOf(xtext.resource()), mapping);
+    assertThat(foundUri, equalTo(uriOfMessageWithName("Inner")));
+  }
+
+  // syntax = "proto2";
+  // package com.google.proto;
+  //
+  // message Outer {
+  //   message Inner {}
+  // }
+  @Test public void should_return_URI_nested_message_when_underscores_in_qualified_name_represent_nesting() {
+    CppToProtobufMapping mapping = messageMapping("com.google.proto.Outer_Inner");
+    URI foundUri = matcher.findUriOfMatchingProtobufElement(descriptionOf(xtext.resource()), mapping);
+    assertThat(foundUri, equalTo(uriOfMessageWithName("Inner")));
+  }
+
+  // syntax = "proto2";
+  // package com.google.proto;
+  //
+  // message Outer_Message {
+  //   message Inner {}
+  // }
+  @Test public void should_return_URI_nested_message_when_underscores_are_part_of_naming() {
+    CppToProtobufMapping mapping = messageMapping("com.google.proto.Outer_Message_Inner");
+    URI foundUri = matcher.findUriOfMatchingProtobufElement(descriptionOf(xtext.resource()), mapping);
+    assertThat(foundUri, equalTo(uriOfMessageWithName("Inner")));
+  }
+
+  // syntax = "proto2";
+  // package com.google.proto;
+  //
+  // message Outer_Message {
+  //   message Inner {}
+  // }
+  //
+  // message Outer {
+  //   message Message {
+  //     message Inner {}
+  //   }
+  // }
+  @Test public void should_return_first_match_if_both_qualified_names_and_types_match() {
+    CppToProtobufMapping mapping = messageMapping("com.google.proto.Outer_Message_Inner");
+    URI foundUri = matcher.findUriOfMatchingProtobufElement(descriptionOf(xtext.resource()), mapping);
+    assertThat(foundUri, equalTo(uriOfMessageWithName("Inner")));
+  }
+
+  // syntax = "proto2";
+  // package com.google.proto;
+  //
+  // message Outer {
+  //   message Inner {}
+  // }
+  @Test public void should_return_null_if_match_not_found() {
+    CppToProtobufMapping mapping = messageMapping("com.google.proto.Outer__Inner");
+    URI foundUri = matcher.findUriOfMatchingProtobufElement(descriptionOf(xtext.resource()), mapping);
+    assertNull(foundUri);
+  }
+
+  private CppToProtobufMapping messageMapping(String qualifiedNameAsText) {
+    QualifiedName qualifiedName = fqnConverter.toQualifiedName(qualifiedNameAsText);
+    return new CppToProtobufMapping(qualifiedName, Message.class);
+  }
+
+  private URI uriOfMessageWithName(String name) {
+    XtextResource resource = xtext.resource();
+    Message message = xtext.find(name, Message.class);
+    String fragment = resource.getURIFragment(message);
+    return resource.getURI().appendFragment(fragment);
+  }
+
+  private IResourceDescription descriptionOf(Resource resource) {
+    index.setContext(resource);
+    return index.getResourceDescription(resource.getURI());
+  }
+}
diff --git a/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/CppToProtobufMapping.java b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/CppToProtobufMapping.java
index 695d380..35df4c4 100644
--- a/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/CppToProtobufMapping.java
+++ b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/CppToProtobufMapping.java
@@ -20,7 +20,7 @@
   private final QualifiedName qualifiedName;
   private final Class<? extends EObject> type;
 
-  CppToProtobufMapping(QualifiedName qualifiedName, Class<? extends EObject> type) {
+  public CppToProtobufMapping(QualifiedName qualifiedName, Class<? extends EObject> type) {
     this.qualifiedName = qualifiedName;
     this.type = type;
   }
diff --git a/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/matching/ProtobufElementMatcher.java b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/matching/ProtobufElementMatcher.java
index 43a0d2c..f7a254b 100644
--- a/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/matching/ProtobufElementMatcher.java
+++ b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/matching/ProtobufElementMatcher.java
@@ -36,16 +36,19 @@
   public URI findUriOfMatchingProtobufElement(IResourceDescription resource, CppToProtobufMapping mapping) {
     QualifiedName qualifiedName = mapping.qualifiedName();
     Pattern pattern = patternToMatchFrom(qualifiedName);
-    List<IEObjectDescription> matches = descriptions.matchingQualifiedNames(resource, pattern);
-    if (matches.size() == 1) {
+    List<IEObjectDescription> matches = descriptions.matchingQualifiedNames(resource, pattern, mapping.type());
+    if (!matches.isEmpty()) {
       return matches.get(0).getEObjectURI();
     }
     return null;
   }
 
-  Pattern patternToMatchFrom(QualifiedName qualifiedName) {
+  private Pattern patternToMatchFrom(QualifiedName qualifiedName) {
     String qualifiedNameAsText = converter.toString(qualifiedName);
-    String regex = Pattern.quote(qualifiedNameAsText.replaceAll("_", "."));
+    // escape existing "."
+    // replace "_" with "(\.|_)"
+    String regex = qualifiedNameAsText.replaceAll("\\.", "\\\\.")
+                                      .replaceAll("_", "\\(\\\\.|_\\)");
     return Pattern.compile(regex);
   }
 }
diff --git a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/resource/IndexLookup_resourceIn_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/resource/IndexLookup_resourceIn_Test.java
similarity index 93%
rename from com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/resource/IndexLookup_resourceIn_Test.java
rename to com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/resource/IndexLookup_resourceIn_Test.java
index a6cb9b5..1936dc8 100644
--- a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/resource/IndexLookup_resourceIn_Test.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/resource/IndexLookup_resourceIn_Test.java
@@ -8,7 +8,7 @@
  */
 package com.google.eclipse.protobuf.resource;
 
-import static com.google.eclipse.protobuf.junit.core.IntegrationTestModule.integrationTestModule;
+import static com.google.eclipse.protobuf.junit.core.UnitTestModule.unitTestModule;
 import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith;
 import static org.hamcrest.core.IsEqual.equalTo;
 import static org.junit.Assert.*;
@@ -28,7 +28,7 @@
  * @author alruiz@google.com (Alex Ruiz)
  */
 public class IndexLookup_resourceIn_Test {
-  @Rule public XtextRule xtext = overrideRuntimeModuleWith(integrationTestModule());
+  @Rule public XtextRule xtext = overrideRuntimeModuleWith(unitTestModule());
 
   @Inject private IndexLookup lookup;
 
diff --git a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/resource/ResourceDescriptions_modelObjectUri_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/resource/ResourceDescriptions_modelObjectUri_Test.java
similarity index 77%
rename from com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/resource/ResourceDescriptions_modelObjectUri_Test.java
rename to com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/resource/ResourceDescriptions_modelObjectUri_Test.java
index 0d680f5..a566616 100644
--- a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/resource/ResourceDescriptions_modelObjectUri_Test.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/resource/ResourceDescriptions_modelObjectUri_Test.java
@@ -8,7 +8,7 @@
  */
 package com.google.eclipse.protobuf.resource;
 
-import static com.google.eclipse.protobuf.junit.core.IntegrationTestModule.integrationTestModule;
+import static com.google.eclipse.protobuf.junit.core.UnitTestModule.unitTestModule;
 import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith;
 import static org.hamcrest.core.IsEqual.equalTo;
 import static org.junit.Assert.*;
@@ -30,13 +30,12 @@
  * @author alruiz@google.com (Alex Ruiz)
  */
 public class ResourceDescriptions_modelObjectUri_Test {
-  @Rule public XtextRule xtext = overrideRuntimeModuleWith(integrationTestModule());
+  @Rule public XtextRule xtext = overrideRuntimeModuleWith(unitTestModule());
 
   @Inject private IQualifiedNameConverter fqnConverter;
   @Inject private ResourceSetBasedResourceDescriptions index;
   @Inject private ResourceDescriptions resourceDescriptions;
 
-
   // syntax = "proto2";
   // package com.google.proto;
   //
@@ -45,13 +44,16 @@
   //   TWO = 2;
   // }
   @Test public void should_find_URI_of_model_object_given_its_qualified_name() {
-    XtextResource resource = xtext.resource();
     QualifiedName qualifiedName = fqnConverter.toQualifiedName("com.google.proto.Type");
-    URI foundUri = resourceDescriptions.modelObjectUri(describe(resource), qualifiedName);
+    URI foundUri = resourceDescriptions.modelObjectUri(descriptionOf(xtext.resource()), qualifiedName);
+    assertThat(foundUri, equalTo(uriOfEnumWithName("Type")));
+  }
+
+  private URI uriOfEnumWithName(String name) {
+    XtextResource resource = xtext.resource();
     Enum anEnum = xtext.find("Type", Enum.class);
     String fragment = resource.getURIFragment(anEnum);
-    URI expectedUri = resource.getURI().appendFragment(fragment);
-    assertThat(foundUri, equalTo(expectedUri));
+    return resource.getURI().appendFragment(fragment);
   }
 
   // syntax = "proto2";
@@ -60,11 +62,11 @@
   // message Person {}
   @Test public void should_return_null_if_file_name_is_equal_but_file_path_is_not() {
     QualifiedName qualifiedName = fqnConverter.toQualifiedName("com.google.proto.Type");
-    URI foundUri = resourceDescriptions.modelObjectUri(describe(xtext.resource()), qualifiedName);
+    URI foundUri = resourceDescriptions.modelObjectUri(descriptionOf(xtext.resource()), qualifiedName);
     assertNull(foundUri);
   }
 
-  private IResourceDescription describe(Resource resource) {
+  private IResourceDescription descriptionOf(Resource resource) {
     index.setContext(resource);
     return index.getResourceDescription(resource.getURI());
   }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/resource/ResourceDescriptions.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/resource/ResourceDescriptions.java
index b0416fb..b28ffe3 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/resource/ResourceDescriptions.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/resource/ResourceDescriptions.java
@@ -8,6 +8,7 @@
  */
 package com.google.eclipse.protobuf.resource;
 
+import static com.google.common.base.Objects.equal;
 import static com.google.common.collect.Lists.newArrayList;
 import static java.util.Collections.unmodifiableList;
 
@@ -15,6 +16,7 @@
 import java.util.regex.*;
 
 import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.*;
 import org.eclipse.xtext.naming.*;
 import org.eclipse.xtext.resource.*;
 
@@ -29,10 +31,10 @@
   @Inject private IQualifiedNameConverter converter;
 
   /**
-   * Finds the URI of a model object in the given resource whose qualified name matches the given one.
+   * Finds the URI of a model object, in the given resource, whose qualified name matches the given one.
    * @param resource the given resource.
    * @param qualifiedName the qualified name to match.
-   * @return the URI of the found model object, or {@code null} if a model object with a matching URI could not be
+   * @return the URI of the matching model object, or {@code null} if a model object with a matching URI could not be
    * found.
    */
   public URI modelObjectUri(IResourceDescription resource, QualifiedName qualifiedName) {
@@ -45,15 +47,31 @@
     return null;
   }
 
-  public List<IEObjectDescription> matchingQualifiedNames(IResourceDescription resource, Pattern pattern) {
+  /**
+   * Finds the URIs of the model objects, in the given resource, that:
+   * <ol>
+   * <li>have a qualified name that match the given pattern, and</li>
+   * <li>have an {@code EClass} whose name is equal to the simple name of the given type.
+   * </ol>
+   * @param resource the given resource.
+   * @param pattern the pattern to match.
+   * @param type the type of model object we are looking for.
+   * @return the URI of the matching object models, or an empty list if matches could not be found.
+   */
+  public List<IEObjectDescription> matchingQualifiedNames(IResourceDescription resource, Pattern pattern,
+      Class<? extends EObject> type) {
     List<IEObjectDescription> descriptions = newArrayList();
     for (IEObjectDescription exported : resource.getExportedObjects()) {
       QualifiedName qualifiedName = exported.getQualifiedName();
       Matcher matcher = pattern.matcher(converter.toString(qualifiedName));
-      if (matcher.matches()) {
+      if (matcher.matches() && haveMatchingNames(exported.getEClass(), type)) {
         descriptions.add(exported);
       }
     }
     return unmodifiableList(descriptions);
   }
+
+  private boolean haveMatchingNames(EClass eClass, Class<? extends EObject> type) {
+    return equal(eClass.getName(), type.getSimpleName());
+  }
 }