Fixed: [Issue 194] Implement ability to navigate to model object given
its qualified name and file path.
diff --git a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/resource/ModelObjectLocationLookup_findModelObjectUri_Test.java b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/resource/ModelObjectLocationLookup_findModelObjectUri_Test.java
new file mode 100644
index 0000000..2511d12
--- /dev/null
+++ b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/resource/ModelObjectLocationLookup_findModelObjectUri_Test.java
@@ -0,0 +1,68 @@
+/*
+ * 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.resource;
+
+import static com.google.eclipse.protobuf.junit.core.IntegrationTestModule.integrationTestModule;
+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.xtext.resource.*;
+import org.eclipse.xtext.resource.impl.ResourceSetBasedResourceDescriptions;
+import org.junit.*;
+
+import com.google.eclipse.protobuf.junit.core.XtextRule;
+import com.google.eclipse.protobuf.protobuf.Enum;
+import com.google.inject.Inject;
+
+/**
+ * Tests for <code>{@link ModelObjectLocationLookup#findModelObjectUri(String, String)}</code>
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class ModelObjectLocationLookup_findModelObjectUri_Test {
+  @Rule public XtextRule xtext = overrideRuntimeModuleWith(integrationTestModule());
+
+  @Inject private ModelObjectLocationLookup lookup;
+
+  // syntax = "proto2";
+  // package com.google.proto;
+  //
+  // enum Type {
+  //   ONE = 1;
+  //   TWO = 2;
+  // }
+  @Test public void should_find_URI_of_model_object_given_its_qualified_name() {
+    XtextResource resource = xtext.resource();
+    addToXtextIndex(resource);
+    URI foundUri = lookup.findModelObjectUri("com.google.proto.Type", resource.getURI().path());
+    Enum anEnum = xtext.find("Type", Enum.class);
+    String fragment = resource.getURIFragment(anEnum);
+    URI expectedUri = resource.getURI().appendFragment(fragment);
+    assertThat(foundUri, equalTo(expectedUri));
+  }
+
+  // syntax = "proto2";
+  // package com.google.proto;
+  //
+  // message Person {}
+  @Test public void should_return_null_if_file_name_is_equal_but_file_path_is_not() {
+    addToXtextIndex(xtext.resource());
+    URI foundUri = lookup.findModelObjectUri("com.google.proto.Person", "/test/src/protos/mytestmodel.proto");
+    assertNull(foundUri);
+  }
+
+  private void addToXtextIndex(XtextResource resource) {
+    IResourceDescriptions xtextIndex = lookup.getXtextIndex();
+    if (xtextIndex instanceof ResourceSetBasedResourceDescriptions) {
+      ((ResourceSetBasedResourceDescriptions) xtextIndex).setContext(resource);
+    }
+  }
+}
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/AbstractTestModule.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/AbstractTestModule.java
new file mode 100644
index 0000000..6e24620
--- /dev/null
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/AbstractTestModule.java
@@ -0,0 +1,22 @@
+/*
+ * 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.junit.core;
+
+import static org.mockito.Mockito.mock;
+
+import com.google.inject.AbstractModule;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public abstract class AbstractTestModule extends AbstractModule {
+  protected <T> void createAndBindMock(Class<T> classToMock) {
+    binder().bind(classToMock).toInstance(mock(classToMock));
+  }
+}
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/IntegrationTestModule.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/IntegrationTestModule.java
index dcb4de6..87216fd 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/IntegrationTestModule.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/IntegrationTestModule.java
@@ -10,7 +10,6 @@
 
 import static com.google.eclipse.protobuf.junit.core.GeneratedProtoFiles.protoFile;
 import static org.eclipse.xtext.util.Strings.isEmpty;
-import static org.mockito.Mockito.mock;
 
 import java.io.File;
 
@@ -19,14 +18,13 @@
 
 import com.google.eclipse.protobuf.protobuf.Import;
 import com.google.eclipse.protobuf.scoping.IFileUriResolver;
-import com.google.inject.AbstractModule;
 
 /**
  * Guice module for unit testing.
  *
  * @author alruiz@google.com (Alex Ruiz)
  */
-public class IntegrationTestModule extends AbstractModule {
+public class IntegrationTestModule extends AbstractTestModule {
   public static IntegrationTestModule integrationTestModule() {
     return new IntegrationTestModule();
   }
@@ -35,7 +33,7 @@
 
   @Override protected void configure() {
     binder().bind(IFileUriResolver.class).to(FileUriResolver.class);
-    binder().bind(EReference.class).toInstance(mock(EReference.class));
+    createAndBindMock(EReference.class);
   }
 
   private static class FileUriResolver implements IFileUriResolver {
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/OverrideRuntimeModuleSetup.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/OverrideRuntimeModuleSetup.java
index 251358f..4b4c065 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/OverrideRuntimeModuleSetup.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/OverrideRuntimeModuleSetup.java
@@ -8,24 +8,27 @@
  */
 package com.google.eclipse.protobuf.junit.core;
 
-import static org.eclipse.xtext.util.Modules2.mixin;
+import static java.util.Arrays.copyOf;
 
 import com.google.eclipse.protobuf.*;
 import com.google.inject.*;
-
+import com.google.inject.util.Modules;
 
 /**
  * @author alruiz@google.com (Alex Ruiz)
  */
 public class OverrideRuntimeModuleSetup extends ProtobufStandaloneSetup {
-  private final Module module;
+  private final Module[] modules;
 
-  OverrideRuntimeModuleSetup(Module module) {
-    this.module = module;
+  OverrideRuntimeModuleSetup(Module[] modules) {
+    this.modules = copyOf(modules, modules.length);
   }
 
   @Override public Injector createInjector() {
-    Module mixin = mixin(new ProtobufRuntimeModule(), module);
-    return Guice.createInjector(mixin);
+    Module current = new ProtobufRuntimeModule();
+    for (Module module : modules) {
+      current = Modules.override(current).with(module);
+    }
+    return Guice.createInjector(current);
   }
 }
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/XtextRule.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/XtextRule.java
index 2eacb82..9679981 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/XtextRule.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/XtextRule.java
@@ -45,8 +45,8 @@
   private XtextResource resource;
   private Finder finder;
 
-  public static XtextRule overrideRuntimeModuleWith(Module testModule) {
-    return createWith(new OverrideRuntimeModuleSetup(testModule));
+  public static XtextRule overrideRuntimeModuleWith(Module...testModules) {
+    return createWith(new OverrideRuntimeModuleSetup(testModules));
   }
 
   public static XtextRule createWith(ISetup setup) {
@@ -79,7 +79,7 @@
 
   public void parseText(String text) {
     boolean ignoreSyntaxErrors = shouldIgnoreSyntaxErrorsIn(text);
-    resource = resourceFrom(new StringInputStream(text));
+    resource = createResourceFrom(new StringInputStream(text));
     IParseResult parseResult = resource.getParseResult();
     root = (Protobuf) parseResult.getRootASTElement();
     if (ignoreSyntaxErrors) {
@@ -103,15 +103,15 @@
     return text.startsWith("// ignore errors");
   }
 
-  private XtextResource resourceFrom(InputStream input) {
-    return resourceFrom(input, createURI("mytestmodel.proto"));
+  private XtextResource createResourceFrom(InputStream input) {
+    return createResourceFrom(input, createURI("file://localhost/project/src/protos/mytestmodel.proto"));
   }
 
-  private XtextResource resourceFrom(InputStream input, URI uri) {
-    XtextResourceSet set = getInstanceOf(XtextResourceSet.class);
-    set.setClasspathURIContext(getClass());
+  private XtextResource createResourceFrom(InputStream input, URI uri) {
+    XtextResourceSet resourceSet = getInstanceOf(XtextResourceSet.class);
+    resourceSet.setClasspathURIContext(getClass());
     XtextResource resource = (XtextResource) getInstanceOf(IResourceFactory.class).createResource(uri);
-    set.getResources().add(resource);
+    resourceSet.getResources().add(resource);
     try {
       resource.load(input, null);
     } catch (IOException e) {
@@ -129,6 +129,10 @@
     return injector.getInstance(type);
   }
 
+  public XtextResource resource() {
+    return resource;
+  }
+
   public Protobuf root() {
     return root;
   }
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/editor/ModelObjectDefinitionNavigator_navigateToDefinition_Test.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/editor/ModelObjectDefinitionNavigator_navigateToDefinition_Test.java
new file mode 100644
index 0000000..642f41b
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/editor/ModelObjectDefinitionNavigator_navigateToDefinition_Test.java
@@ -0,0 +1,59 @@
+/*
+ * 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.ui.editor;
+
+import static com.google.eclipse.protobuf.junit.core.UnitTestModule.unitTestModule;
+import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith;
+import static org.eclipse.emf.common.util.URI.createURI;
+import static org.mockito.Mockito.*;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.xtext.ui.editor.IURIEditorOpener;
+import org.junit.*;
+
+import com.google.eclipse.protobuf.junit.core.*;
+import com.google.eclipse.protobuf.resource.ModelObjectLocationLookup;
+import com.google.inject.Inject;
+
+/**
+ * Tests for <code>{@link ModelObjectDefinitionNavigator#navigateToDefinition(String, String)}</code>.
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class ModelObjectDefinitionNavigator_navigateToDefinition_Test {
+  @Rule public XtextRule xtext = overrideRuntimeModuleWith(unitTestModule(), new TestModule());
+
+  @Inject private ModelObjectLocationLookup locationLookup;
+  @Inject private IURIEditorOpener editorOpener;
+  @Inject private ModelObjectDefinitionNavigator navigator;
+
+  @Test public void should_navigate_to_model_object_if_URI_is_found() {
+    URI uri = createURI("file://localhost/project/src/protos/test.proto");
+    String qualifiedName = "com.google.proto.Type";
+    String filePath = "/src/protos/test.proto";
+    when(locationLookup.findModelObjectUri(qualifiedName, filePath)).thenReturn(uri);
+    navigator.navigateToDefinition(qualifiedName, filePath);
+    verify(editorOpener).open(uri, true);
+  }
+
+  @Test public void should_not_navigate_to_model_object_if_URI_is_not_found() {
+    String qualifiedName = "com.google.proto.Person";
+    String filePath = "/src/protos/test.proto";
+    when(locationLookup.findModelObjectUri(qualifiedName, filePath)).thenReturn(null);
+    navigator.navigateToDefinition(qualifiedName, filePath);
+    verifyZeroInteractions(editorOpener);
+  }
+
+  private static class TestModule extends AbstractTestModule {
+    @Override protected void configure() {
+      createAndBindMock(ModelObjectLocationLookup.class);
+      createAndBindMock(IURIEditorOpener.class);
+    }
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ModelObjectDefinitionNavigator.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ModelObjectDefinitionNavigator.java
new file mode 100644
index 0000000..b90ad50
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ModelObjectDefinitionNavigator.java
@@ -0,0 +1,38 @@
+/*
+ * 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.ui.editor;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.xtext.ui.editor.IURIEditorOpener;
+
+import com.google.eclipse.protobuf.resource.ModelObjectLocationLookup;
+import com.google.inject.Inject;
+
+/**
+ * Navigates to the definition of a model object, opening necessary files if necessary.
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class ModelObjectDefinitionNavigator {
+  @Inject private ModelObjectLocationLookup locationLookup;
+  @Inject private IURIEditorOpener editorOpener;
+
+  /**
+   * Navigates to the definition of the model object whose qualified name matches the given one. This method will open
+   * the file containing the model object definition if necessary.
+   * @param qualifiedNameAsText the qualified name to match.
+   * @param filePath the path and name of the file where to perform the lookup. It should not include the host.
+    */
+  public void navigateToDefinition(String qualifiedNameAsText, String filePath) {
+    URI uri = locationLookup.findModelObjectUri(qualifiedNameAsText, filePath);
+    if (uri != null) {
+      editorOpener.open(uri, true);
+    }
+  }
+}
diff --git a/com.google.eclipse.protobuf/META-INF/MANIFEST.MF b/com.google.eclipse.protobuf/META-INF/MANIFEST.MF
index fa1ee42..00824d4 100644
--- a/com.google.eclipse.protobuf/META-INF/MANIFEST.MF
+++ b/com.google.eclipse.protobuf/META-INF/MANIFEST.MF
@@ -32,6 +32,7 @@
  com.google.eclipse.protobuf.protobuf,

  com.google.eclipse.protobuf.protobuf.impl,

  com.google.eclipse.protobuf.protobuf.util,

+ com.google.eclipse.protobuf.resource,

  com.google.eclipse.protobuf.scoping,

  com.google.eclipse.protobuf.serializer,

  com.google.eclipse.protobuf.services,

diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/resource/ModelObjectLocationLookup.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/resource/ModelObjectLocationLookup.java
new file mode 100644
index 0000000..05d17df
--- /dev/null
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/resource/ModelObjectLocationLookup.java
@@ -0,0 +1,54 @@
+/*
+ * 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.resource;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.xtext.naming.*;
+import org.eclipse.xtext.resource.*;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.inject.Inject;
+
+/**
+ * Looks up the location of model objects in the Xtext index.
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class ModelObjectLocationLookup {
+  @Inject private IResourceDescriptions xtextIndex;
+  @Inject private IQualifiedNameConverter fqnConverter;
+
+  /**
+   * Finds the URI of a model object whose qualified name matches the given one.
+   * @param qualifiedNameAsText the qualified name to match.
+   * @param filePath the path and name of the file where to perform the lookup. It should not include the host.
+   * @return the URI  of a model object whose qualified name matches the given one, or {@code null} if a matching model
+   * object cannot be found.
+   */
+  public URI findModelObjectUri(String qualifiedNameAsText, String filePath) {
+    QualifiedName qualifiedName = fqnConverter.toQualifiedName(qualifiedNameAsText);
+    for (IResourceDescription resourceDescription : xtextIndex.getAllResourceDescriptions()) {
+      URI resourceUri = resourceDescription.getURI();
+      if (filePath.equals(resourceUri.path())) {
+        // we found the resource we are looking for.
+        for (IEObjectDescription exported : resourceDescription.getExportedObjects()) {
+          if (!exported.getEObjectOrProxy().eIsProxy() && qualifiedName.equals(exported.getQualifiedName())) {
+            return exported.getEObjectURI();
+          }
+        }
+        break;
+      }
+    }
+    return null;
+  }
+
+  @VisibleForTesting IResourceDescriptions getXtextIndex() {
+    return xtextIndex;
+  }
+}