In progress: [ Issue 45 ] Protobuf-dt should be able to open files outside workspace
https://code.google.com/p/protobuf-dt/issues/detail?id=45

Initial work. Protobuf-dt now can open files outside the workspace. Outline View is working. Hyperlinking is broken.
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/ProtobufUiModule.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/ProtobufUiModule.java
index a006703..c7b82d9 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/ProtobufUiModule.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/ProtobufUiModule.java
@@ -14,12 +14,14 @@
 import org.eclipse.ui.plugin.AbstractUIPlugin;
 import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
 import org.eclipse.xtext.ui.editor.IXtextEditorCallback;
+import org.eclipse.xtext.ui.editor.model.XtextDocumentProvider;
 import org.eclipse.xtext.ui.editor.outline.actions.IOutlineContribution;
 import org.eclipse.xtext.ui.editor.preferences.IPreferenceStoreInitializer;
 
 import com.google.eclipse.protobuf.scoping.IFileUriResolver;
 import com.google.eclipse.protobuf.ui.builder.AutoAddNatureEditorCallback;
-import com.google.eclipse.protobuf.ui.editor.ProtobufHyperlinkDetector;
+import com.google.eclipse.protobuf.ui.editor.hyperlinking.ProtobufHyperlinkDetector;
+import com.google.eclipse.protobuf.ui.editor.model.ProtobufDocumentProvider;
 import com.google.eclipse.protobuf.ui.outline.LinkWithEditor;
 import com.google.eclipse.protobuf.ui.outline.ProtobufOutlinePage;
 import com.google.eclipse.protobuf.ui.preferences.compiler.CompilerPreferenceStoreInitializer;
@@ -34,6 +36,8 @@
  */
 public class ProtobufUiModule extends AbstractProtobufUiModule {
 
+  public static final String PLUGIN_ID = "com.google.eclipse.protobuf.ui";
+
   public ProtobufUiModule(AbstractUIPlugin plugin) {
     super(plugin);
   }
@@ -73,4 +77,8 @@
   public void configureFileUriResolver(Binder binder) {
     binder.bind(IFileUriResolver.class).to(FileUriResolver.class);
   }
+
+  public void configureDocumentProvider(Binder binder) {
+    binder.bind(XtextDocumentProvider.class).to(ProtobufDocumentProvider.class);
+  }
 }
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ImportHyperlink.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/hyperlinking/ImportHyperlink.java
similarity index 92%
rename from com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ImportHyperlink.java
rename to com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/hyperlinking/ImportHyperlink.java
index ffcd9c4..93c741d 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ImportHyperlink.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/hyperlinking/ImportHyperlink.java
@@ -6,7 +6,7 @@
  *
  * http://www.eclipse.org/legal/epl-v10.html
  */
-package com.google.eclipse.protobuf.ui.editor;
+package com.google.eclipse.protobuf.ui.editor.hyperlinking;
 
 import org.eclipse.core.filesystem.EFS;
 import org.eclipse.core.filesystem.IFileStore;
@@ -53,7 +53,7 @@
   private void openFromFileSystem() {
     IFileStore fileStore = EFS.getLocalFileSystem().getStore(new Path(importUri.toFileString()));
     IEditorInput editorInput = new FileStoreEditorInput(fileStore);
-    openFile(editorInput, "org.eclipse.ui.DefaultTextEditor");
+    openFile(editorInput, "com.google.eclipse.protobuf.Protobuf"/*"org.eclipse.ui.DefaultTextEditor"*/);
   }
 
   private void openFile(IEditorInput editorInput, String editorId) {
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ProtobufHyperlinkDetector.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/hyperlinking/ProtobufHyperlinkDetector.java
similarity index 98%
rename from com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ProtobufHyperlinkDetector.java
rename to com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/hyperlinking/ProtobufHyperlinkDetector.java
index 729c483..59a0775 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ProtobufHyperlinkDetector.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/hyperlinking/ProtobufHyperlinkDetector.java
@@ -6,7 +6,7 @@
  *
  * http://www.eclipse.org/legal/epl-v10.html
  */
-package com.google.eclipse.protobuf.ui.editor;
+package com.google.eclipse.protobuf.ui.editor.hyperlinking;
 
 import static org.eclipse.emf.common.util.URI.createURI;
 
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/model/ProtobufDocumentProvider.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/model/ProtobufDocumentProvider.java
new file mode 100644
index 0000000..fc3dad8
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/model/ProtobufDocumentProvider.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2011 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.model;
+
+import static com.google.eclipse.protobuf.ui.ProtobufUiModule.PLUGIN_ID;
+import static org.eclipse.core.runtime.IStatus.ERROR;
+import static org.eclipse.emf.common.util.URI.createURI;
+import static org.eclipse.xtext.validation.CheckMode.FAST_ONLY;
+
+import java.io.*;
+import java.net.URI;
+
+import org.eclipse.core.filesystem.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.source.IAnnotationModel;
+import org.eclipse.ui.IURIEditorInput;
+import org.eclipse.ui.ide.FileStoreEditorInput;
+import org.eclipse.xtext.parser.IParseResult;
+import org.eclipse.xtext.parser.IParser;
+import org.eclipse.xtext.resource.XtextResource;
+import org.eclipse.xtext.ui.editor.model.XtextDocument;
+import org.eclipse.xtext.ui.editor.model.XtextDocumentProvider;
+import org.eclipse.xtext.ui.editor.quickfix.IssueResolutionProvider;
+import org.eclipse.xtext.ui.editor.validation.AnnotationIssueProcessor;
+import org.eclipse.xtext.ui.editor.validation.ValidationJob;
+import org.eclipse.xtext.util.StringInputStream;
+import org.eclipse.xtext.validation.IResourceValidator;
+
+import com.google.eclipse.protobuf.ui.util.Closeables;
+import com.google.inject.Inject;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class ProtobufDocumentProvider extends XtextDocumentProvider {
+
+  private static final String ENCODING = "UTF-8";
+  
+  @Inject private Closeables closeables;
+  @Inject private IssueResolutionProvider issueResolutionProvider;
+  @Inject private IParser parser;
+  @Inject private IResourceValidator resourceValidator;
+  
+  @Override protected ElementInfo createElementInfo(Object element) throws CoreException {
+    if (element instanceof FileStoreEditorInput) {
+      FileStoreEditorInput input = (FileStoreEditorInput) element;
+      IDocument document = null;
+      IStatus status = null;
+      try {
+        document = createDocument(element);
+      } catch (CoreException e) {
+        handleCoreException(e, "ProtobufDocumentProvider.createElementInfo");
+        document = createEmptyDocument();
+        status = e.getStatus();
+      }
+      IFileStore store = EFS.getLocalFileSystem().getStore(fileFrom(input).toURI());
+      IFileInfo fileInfo = store.fetchInfo();
+      IAnnotationModel model = createAnnotationModel(element);
+      // new FileSynchronizer(input).install();
+      FileInfo info = new FileInfo(document, model, null);
+      info.fModificationStamp = fileInfo.getLastModified();
+      info.fStatus = status;
+      info.fEncoding = ENCODING;
+      cacheEncodingState(element);
+      XtextDocument xtextDocument = (XtextDocument) document;
+      AnnotationIssueProcessor annotationIssueProcessor = new AnnotationIssueProcessor(xtextDocument, model,
+          issueResolutionProvider);
+      ValidationJob job = new ValidationJob(resourceValidator, xtextDocument, annotationIssueProcessor, FAST_ONLY);
+      xtextDocument.setValidationJob(job);
+      return info;
+    }
+    return super.createElementInfo(element);
+  }
+
+  /** {@inheritDoc} */
+  @Override protected IDocument createDocument(Object element) throws CoreException {
+    if (element instanceof FileStoreEditorInput) {
+      XtextDocument document = createEmptyDocument();
+      FileStoreEditorInput input = (FileStoreEditorInput) element;
+      File file = fileFrom(input);
+      XtextResource resource = new XtextResource(createURI(file.toURI().toString()));
+      try {
+        String contents = contentsOf(file);
+        document.set(contents);
+        IParseResult result = parser.parse(new InputStreamReader(new StringInputStream(contents), ENCODING));
+        resource.getContents().add(result.getRootASTElement());
+        document.setInput(resource);
+        return document;
+      } catch (Throwable t) {
+        if (t instanceof CoreException) throw (CoreException) t;
+        throw wrapWithCoreException(t);
+      }
+    }
+    return super.createDocument(element);
+  }
+  
+  private File fileFrom(IURIEditorInput input) {
+    URI uri = input.getURI();
+    String scheme = uri.getScheme();
+    if (scheme != "file") {
+      String cleanUri = uri.toString().replaceFirst(scheme, "file");
+      uri = URI.create(cleanUri);
+    }
+    return new File(uri);
+  }
+
+  private String contentsOf(File file) throws CoreException {
+    Reader reader = null;
+    InputStream contentStream = null;
+    try {
+      contentStream = new FileInputStream(file);
+      reader = new BufferedReader(new InputStreamReader(contentStream, ENCODING), DEFAULT_FILE_SIZE);
+      StringBuilder contents = new StringBuilder(DEFAULT_FILE_SIZE);
+      char[] buffer = new char[2048];
+      int character = reader.read(buffer);
+      while (character > 0) {
+        contents.append(buffer, 0, character);
+        character = reader.read(buffer);
+      }
+      return contents.toString();
+    } catch (IOException e) {
+      throw wrapWithCoreException(e);
+    } finally {
+      if (!closeables.close(reader)) closeables.close(contentStream);
+    }
+  }
+  
+  private CoreException wrapWithCoreException(Throwable cause) {
+    return new CoreException(new Status(ERROR, PLUGIN_ID, messageOf(cause), cause));
+  }
+  
+  private String messageOf(Throwable t) {
+    String message = t.getMessage();
+    return (message != null) ? message : "";
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/DirectorySelectionDialogs.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/DirectorySelectionDialogs.java
index 02460b7..c9aa6e2 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/DirectorySelectionDialogs.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/DirectorySelectionDialogs.java
@@ -8,6 +8,7 @@
  */
 package com.google.eclipse.protobuf.ui.preferences.paths;
 
+import static com.google.eclipse.protobuf.ui.ProtobufUiModule.PLUGIN_ID;
 import static com.google.eclipse.protobuf.ui.swt.Messages.*;
 import static org.eclipse.core.runtime.IStatus.ERROR;
 import static org.eclipse.core.runtime.Status.OK_STATUS;
@@ -17,9 +18,12 @@
 import org.eclipse.core.resources.*;
 import org.eclipse.core.runtime.*;
 import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.*;
-import org.eclipse.ui.dialogs.*;
-import org.eclipse.ui.model.*;
+import org.eclipse.swt.widgets.DirectoryDialog;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
+import org.eclipse.ui.dialogs.ISelectionStatusValidator;
+import org.eclipse.ui.model.WorkbenchContentProvider;
+import org.eclipse.ui.model.WorkbenchLabelProvider;
 import org.eclipse.ui.views.navigator.ResourceComparator;
 
 /**
@@ -29,8 +33,6 @@
  */
 class DirectorySelectionDialogs {
 
-  private static final String PLUGIN_ID = "com.google.eclipse.protobuf.ui";
-
   static String showWorkspaceDirectoryDialog(Shell shell, String initialPath) {
     return showWorkspaceDirectoryDialog(shell, initialPath, null);
   }
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Closeables.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Closeables.java
new file mode 100644
index 0000000..4abef77
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Closeables.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2011 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.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+import com.google.inject.Singleton;
+
+/**
+ * Utility methods related to <code>{@link Closeable}</code>.
+ * 
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+@Singleton
+public class Closeables {
+
+  public boolean close(Closeable c) {
+    if (c == null) return false;
+    try {
+      c.close();
+    } catch (IOException ignored) {}
+    return true;
+  }
+}