In progress: [Issue 95] Add ability to view descriptor.proto in an
editor

Path of descriptor of descriptor.proto gets properly resolved to the
file provided in the plug-in. Added code to create the correct
IEditorInfo when a URI belongs a platform plug-in resource.
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ProtobufUriEditorOpener.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ProtobufUriEditorOpener.java
index 892de15..acdcd3a 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ProtobufUriEditorOpener.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ProtobufUriEditorOpener.java
@@ -8,7 +8,6 @@
  */
 package com.google.eclipse.protobuf.ui.editor;
 
-import static com.google.eclipse.protobuf.ui.util.Resources.URI_SCHEME_FOR_FILES;
 import static org.eclipse.xtext.ui.editor.utils.EditorUtils.getXtextEditor;
 
 import org.apache.log4j.Logger;
@@ -29,19 +28,26 @@
   private static Logger logger = Logger.getLogger(ProtobufUriEditorOpener.class);
 
   @Inject private Resources resources;
-  
+
   /** {@inheritDoc} */
   @Override public IEditorPart open(URI uri, EReference crossReference, int indexInList, boolean select) {
-    if (URI_SCHEME_FOR_FILES.equals(uri.scheme())) {
-      try {
-        IEditorPart editor = resources.openProtoFileInFileSystem(uri.trimFragment());
+    try {
+      IEditorPart editor = editorFor(uri);
+      if (editor != null) {
         selectAndReveal(editor, uri, crossReference, indexInList, select);
         return getXtextEditor(editor);
-      } catch (PartInitException e) {
-        logger.error("Unable to open " + uri.toString(), e);
       }
+    } catch (PartInitException e) {
+      logger.error("Unable to open " + uri.toString(), e);
     }
     return super.open(uri, crossReference, indexInList, select);
   }
 
+  private IEditorPart editorFor(URI uri) throws PartInitException {
+    if (uri.isFile())
+      return resources.openProtoFileInFileSystem(uri.trimFragment());
+    if (uri.isPlatformPlugin())
+      return resources.openProtoFileInPlugin(uri);
+    return null;
+  }
 }
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/hyperlinking/ImportHyperlink.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/hyperlinking/ImportHyperlink.java
index 26c901a..9aef26d 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/hyperlinking/ImportHyperlink.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/hyperlinking/ImportHyperlink.java
@@ -8,8 +8,6 @@
  */
 package com.google.eclipse.protobuf.ui.editor.hyperlinking;
 
-import static com.google.eclipse.protobuf.ui.util.Resources.URI_SCHEME_FOR_FILES;
-
 import org.apache.log4j.Logger;
 import org.eclipse.emf.common.util.URI;
 import org.eclipse.jface.text.IRegion;
@@ -26,7 +24,7 @@
 class ImportHyperlink implements IHyperlink {
 
   private static Logger logger = Logger.getLogger(ImportHyperlink.class);
-  
+
   private final URI importUri;
   private final IRegion region;
   private final Resources resources;
@@ -38,10 +36,16 @@
   }
 
   public void open() {
-    String scheme = importUri.scheme();
     try {
-      if ("platform".equals(scheme)) resources.openProtoFileInPlatform(importUri);
-      if (URI_SCHEME_FOR_FILES.equals(scheme)) resources.openProtoFileInFileSystem(importUri);
+      if (importUri.isPlatformResource()) {
+        resources.openProtoFileInWorkspace(importUri);
+        return;
+      }
+      if (importUri.isPlatformPlugin()) {
+        resources.openProtoFileInPlugin(importUri);
+        return;
+      }
+      if (importUri.isFile()) resources.openProtoFileInFileSystem(importUri);
     } catch (PartInitException e) {
       logger.error("Unable to open " + importUri.toString(), e);
     }
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/hyperlinking/ProtobufHyperlinkDetector.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/hyperlinking/ProtobufHyperlinkDetector.java
index 9bfe90a..b7abcae 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/hyperlinking/ProtobufHyperlinkDetector.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/hyperlinking/ProtobufHyperlinkDetector.java
@@ -60,11 +60,6 @@
         EObject resolved = eObjectAtOffsetHelper.resolveElementAt(resource, region.getOffset());
         if (!(resolved instanceof Import)) return NO_HYPERLINKS;
         Import anImport = (Import) resolved;
-        if (imports.isImportingProtoDescriptor(anImport)) {
-          // TODO add support for opening descriptor.proto from plug-in
-          // Look into NonExistingFileEditorInput
-          return NO_HYPERLINKS;
-        }
         IRegion importUriRegion;
         try {
           importUriRegion = importUriRegion(document, region.getOffset());
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/FileUriResolver.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/FileUriResolver.java
index c509ea5..8353581 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/FileUriResolver.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/FileUriResolver.java
@@ -15,8 +15,11 @@
 import org.eclipse.emf.ecore.resource.Resource;
 
 import com.google.eclipse.protobuf.scoping.IFileUriResolver;
-import com.google.eclipse.protobuf.ui.preferences.pages.paths.*;
+import com.google.eclipse.protobuf.scoping.ProtoDescriptorProvider;
+import com.google.eclipse.protobuf.ui.preferences.pages.paths.PathsPreferences;
+import com.google.eclipse.protobuf.ui.preferences.pages.paths.PathsPreferencesFactory;
 import com.google.eclipse.protobuf.ui.util.Resources;
+import com.google.eclipse.protobuf.util.Imports;
 import com.google.inject.Inject;
 
 /**
@@ -28,6 +31,8 @@
 
   @Inject private PathsPreferencesFactory preferencesFactory;
   @Inject private FileResolverStrategies resolvers;
+  @Inject private Imports imports;
+  @Inject private ProtoDescriptorProvider descriptorProvider;
   @Inject private Resources resources;
 
   /*
@@ -57,6 +62,9 @@
   }
 
   private String resolveUri(String importUri, URI resourceUri) {
+    if (imports.isUnresolvedDescriptorUri(importUri)) {
+      return descriptorProvider.descriptorLocation().toString();
+    }
     IProject project = resources.project(resourceUri);
     if (project == null) project = resources.activeProject();
     if (project == null) throw new IllegalStateException("Unable to find current project");
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Resources.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Resources.java
index 7cb6c9a..1e9b653 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Resources.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Resources.java
@@ -8,13 +8,17 @@
  */
 package com.google.eclipse.protobuf.ui.util;
 
-import org.eclipse.core.filesystem.*;
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
 import org.eclipse.core.resources.*;
-import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
 import org.eclipse.emf.common.util.URI;
 import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.ui.*;
 import org.eclipse.ui.ide.FileStoreEditorInput;
+import org.eclipse.ui.internal.editors.text.EditorsPlugin;
+import org.eclipse.ui.internal.editors.text.NonExistingFileEditorInput;
 import org.eclipse.ui.part.FileEditorInput;
 import org.eclipse.ui.views.navigator.ResourceNavigator;
 
@@ -65,19 +69,19 @@
   }
 
   /**
-   * Opens the .proto file identified by the given URI. The .proto file exists in the workspace.
+   * Opens the .proto file identified by the given URI that exists in the workspace.
    * @param uri the URI of the file to open.
    * @return an open and active editor, or {@code null} if an external editor was opened.
    * @throws PartInitException if the editor cannot be opened or initialized.
    */
-  public IEditorPart openProtoFileInPlatform(URI uri) throws PartInitException {
+  public IEditorPart openProtoFileInWorkspace(URI uri) throws PartInitException {
     IFile file = file(uri);
     IEditorInput editorInput = new FileEditorInput(file);
     return openFile(editorInput);
   }
 
   /**
-   * Opens the .proto file identified by the given URI. The .proto file does not exist in the workspace, therefore is
+   * Opens the .proto file identified by the given URI that does not exist in the workspace, therefore is
    * opened from the file system.
    * @param uri the URI of the file to open.
    * @return an open and active editor, or {@code null} if an external editor was opened.
@@ -89,6 +93,20 @@
     return openFile(editorInput/*"org.eclipse.ui.DefaultTextEditor"*/);
   }
 
+  @SuppressWarnings("restriction")
+  public IEditorPart openProtoFileInPlugin(URI uri) throws PartInitException {
+    IFileStore fileStore = queryFileStore();
+    IEditorInput editorInput = new NonExistingFileEditorInput(fileStore, "descriptor.proto");
+    return openFile(editorInput);
+  }
+
+  @SuppressWarnings("restriction")
+  private IFileStore queryFileStore() {
+    IPath stateLocation = EditorsPlugin.getDefault().getStateLocation();
+    IPath path = stateLocation.append("/_" + new Object().hashCode()); //$NON-NLS-1$
+    return EFS.getLocalFileSystem().getStore(path);
+  }
+
   private IEditorPart openFile(IEditorInput editorInput) throws PartInitException {
     IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
     return page.openEditor(editorInput, "com.google.eclipse.protobuf.Protobuf");
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptor.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptor.java
index f1310a3..e10394d 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptor.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptor.java
@@ -39,9 +39,6 @@
  */
 public class ProtoDescriptor {
 
-  /** Path of proto.descriptor to use in imports. */
-  public static final String PATH = "google/protobuf/descriptor.proto";
-
   private static final Map<String, OptionType> OPTION_DEFINITION_BY_NAME = new HashMap<String, OptionType>();
 
   static {
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptorProvider.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptorProvider.java
index 8cc6b2b..94c7523 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptorProvider.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptorProvider.java
@@ -10,13 +10,14 @@
 
 import static org.eclipse.xtext.util.Strings.isEmpty;
 
-import com.google.eclipse.protobuf.util.ModelNodes;
-import com.google.inject.*;
-
-import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtensionRegistry;
 import org.eclipse.emf.common.util.URI;
 import org.eclipse.xtext.parser.IParser;
 
+import com.google.eclipse.protobuf.util.ModelNodes;
+import com.google.inject.*;
+
 /**
  * Provider of a singleton instance of <code>{@link ProtoDescriptor}</code>.
  *
@@ -32,6 +33,7 @@
   @Inject private IExtensionRegistry registry;
 
   private ProtoDescriptor descriptor;
+  private URI descriptorLocation;
 
   private final Object lock = new Object();
 
@@ -40,17 +42,27 @@
       if (descriptor == null) {
         descriptor = new ProtoDescriptor(parser, descriptorLocation(), nodes);
       }
-      return descriptor;
     }
+    return descriptor;
   }
-  
-  private URI descriptorLocation() {
+
+  public URI descriptorLocation() {
+    synchronized (lock) {
+      if (descriptorLocation == null) descriptorLocation = findDescriptorLocation();
+    }
+    return descriptorLocation;
+  }
+
+  private URI findDescriptorLocation() {
+    String uri = null;
     IConfigurationElement[] config = registry.getConfigurationElementsFor(EXTENSION_ID);
     for (IConfigurationElement e : config) {
       String path = e.getAttribute("path");
       if (isEmpty(path)) continue;
-      return URI.createURI("platform:/plugin/" + e.getContributor().getName() + "/" + path);
+      uri = "platform:/plugin/" + e.getContributor().getName() + "/" + path;
+      break;
     }
-    return URI.createURI("platform:/plugin/com.google.eclipse.protobuf/descriptor.proto");
+    if (uri == null) uri = "platform:/plugin/com.google.eclipse.protobuf/descriptor.proto";
+    return URI.createURI(uri);
   }
 }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportUriResolver.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportUriResolver.java
index aed9f58..2f9aae5 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportUriResolver.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufImportUriResolver.java
@@ -38,11 +38,8 @@
   @Override public String apply(EObject from) {
     if (from instanceof Import) {
       Import anImport = (Import) from;
-      // String originalUri = anImport.getImportURI();
       anImport.setImportURI(resolveImportUri(anImport));
-      String applied = super.apply(from);
-      // anImport.setImportURI(originalUri);
-      return applied;
+      return super.apply(from);
     }
     return super.apply(from);
   }
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 f8f4d35..cb3d5b1 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
@@ -119,7 +119,7 @@
       Class<T> targetType) {
     List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
     for (Import anImport : allImports) {
-      if (imports.isImportingProtoDescriptor(anImport)) {
+      if (imports.hasUnresolvedDescriptorUri(anImport)) {
         descriptions.addAll(allBuiltInTypes(targetType));
         continue;
       }
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 c245f9d..ac9b01b 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
@@ -9,7 +9,6 @@
 package com.google.eclipse.protobuf.util;
 
 import com.google.eclipse.protobuf.protobuf.Import;
-import com.google.eclipse.protobuf.scoping.ProtoDescriptor;
 import com.google.inject.Singleton;
 
 /**
@@ -21,13 +20,24 @@
 public class Imports {
 
   /**
-   * Indicates whether the URL of the given import is equal to the path of descriptor.proto.
+   * Indicates whether the URI of the given import is equal to the path of descriptor.proto
+   * ("google/protobuf/descriptor.proto").
    * @param anImport the import to check.
-   * @return {@code true} if the URL of the given import is equal to the path of descriptor.proto, {@code false}
+   * @return {@code true} if the URI of the given import is equal to the path of descriptor.proto, {@code false}
    * otherwise.
    */
-  public boolean isImportingProtoDescriptor(Import anImport) {
+  public boolean hasUnresolvedDescriptorUri(Import anImport) {
     if (anImport == null) return false;
-    return ProtoDescriptor.PATH.equals(anImport.getImportURI());
+    return isUnresolvedDescriptorUri(anImport.getImportURI());
+  }
+
+  /**
+   * Indicates whether the given import URI is equal to the path of descriptor.proto
+   * ("google/protobuf/descriptor.proto").
+   * @param uri the URI to check.
+   * @return {@code true} if the given import URI is equal to the path of descriptor.proto, {@code false} otherwise.
+   */
+  public boolean isUnresolvedDescriptorUri(String uri) {
+    return "google/protobuf/descriptor.proto".equals(uri);
   }
 }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator.java
index 65833f7..a27e34a 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator.java
@@ -60,8 +60,6 @@
   }
 
   private boolean isResolved(Import anImport) {
-    // global proto file, always available
-    if (imports.isImportingProtoDescriptor(anImport)) return true;
     String importUri = anImport.getImportURI();
     if (!isEmpty(importUri)) {
       URI uri = URI.createURI(importUri);