In progress: [ Issue 40 ] Add support for import resolution across multiple folders
https://code.google.com/p/protobuf-dt/issues/detail?id=40

Adding support for using both workspace and file system paths for resolution of imports.
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/preferences/paths/ImportPath_parse_Test.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/preferences/paths/ImportPath_parse_Test.java
new file mode 100644
index 0000000..b5ee365
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/preferences/paths/ImportPath_parse_Test.java
@@ -0,0 +1,34 @@
+/*
+ * 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.preferences.paths;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+
+/**
+ * Tests for <code>{@link ImportPath#parse(String)}</code>
+ * 
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class ImportPath_parse_Test {
+
+  @Test public void should_parse_workspace_path() {
+    ImportPath path = ImportPath.parse("${workspace_loc:/test/src}");
+    assertThat(path.value, equalTo("/test/src"));
+    assertThat(path.isWorkspacePath, equalTo(true));
+  }
+
+  @Test public void should_parse_file_system_path() {
+    ImportPath path = ImportPath.parse("/test/src");
+    assertThat(path.value, equalTo("/test/src"));
+    assertThat(path.isWorkspacePath, equalTo(false));
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/preferences/paths/ImportPath_toString_Test.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/preferences/paths/ImportPath_toString_Test.java
new file mode 100644
index 0000000..8cf2549
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/preferences/paths/ImportPath_toString_Test.java
@@ -0,0 +1,32 @@
+/*
+ * 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.preferences.paths;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+
+/**
+ * Tests for <code>{@link ImportPath#toString()}</code>
+ * 
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class ImportPath_toString_Test {
+
+  @Test public void should_specify_is_workspace_path() {
+    ImportPath path = new ImportPath("/test/src", true);
+    assertThat(path.toString(), equalTo("${workspace_loc:/test/src}"));
+  }
+
+  @Test public void should_return_plain_value_if_it_is_not_workspace_path() {
+    ImportPath path = new ImportPath("/test/src", false);
+    assertThat(path.toString(), equalTo("/test/src"));
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/DirectoryNamesEditor.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/DirectoryNamesEditor.java
index 3d76b77..b56cd4f 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/DirectoryNamesEditor.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/DirectoryNamesEditor.java
@@ -10,6 +10,8 @@
 
 import static com.google.eclipse.protobuf.ui.preferences.paths.Messages.*;
 import static java.util.Arrays.asList;
+import static java.util.Collections.unmodifiableList;
+import static org.eclipse.xtext.util.Strings.isEmpty;
 
 import java.util.*;
 import java.util.List;
@@ -32,14 +34,14 @@
 
   private final EventListeners eventListeners;
 
-  private final Table tblDirectoryNames;
-  private final TableViewer tblDirectoryNamesViewer;
+  private final Table tblDirectoryPaths;
+  private final TableViewer tblDirectoryPathsViewer;
   private final Button btnAdd;
   private final Button btnRemove;
   private final Button btnUp;
   private final Button btnDown;
 
-  private final LinkedList<String> directoryNames = new LinkedList<String>();
+  private final LinkedList<ImportPath> importPaths = new LinkedList<ImportPath>();
   
   private SelectionListener onChangeListener;
 
@@ -50,12 +52,12 @@
     this.eventListeners = eventListeners;
     setLayout(new GridLayout(2, false));
     
-    tblDirectoryNames = new Table(this, SWT.BORDER | SWT.FULL_SELECTION);
-    tblDirectoryNames.setLinesVisible(true);
-    tblDirectoryNames.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
+    tblDirectoryPaths = new Table(this, SWT.BORDER | SWT.FULL_SELECTION);
+    tblDirectoryPaths.setLinesVisible(true);
+    tblDirectoryPaths.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
 
-    tblDirectoryNamesViewer = new TableViewer(tblDirectoryNames);
-    tblDirectoryNamesViewer.setContentProvider(new IStructuredContentProvider() {
+    tblDirectoryPathsViewer = new TableViewer(tblDirectoryPaths);
+    tblDirectoryPathsViewer.setContentProvider(new IStructuredContentProvider() {
       public Object[] getElements(Object inputElement) {
         return (Object[]) inputElement;
       }
@@ -92,7 +94,7 @@
   }
 
   private void addEventListeners() {
-    tblDirectoryNames.addSelectionListener(new SelectionAdapter() {
+    tblDirectoryPaths.addSelectionListener(new SelectionAdapter() {
       @Override public void widgetSelected(SelectionEvent e) {
         enableButtonsDependingOnTableSelection();
       }
@@ -101,7 +103,7 @@
       @Override public void widgetSelected(SelectionEvent e) {
         IncludeDialog dialog = new IncludeDialog(getShell(), includeDirectoryTitle);
         if (dialog.open()) {
-          directoryNames.add(dialog.getEnteredPath());
+          importPaths.add(dialog.getSelectedPath());
           updateTable();
           enableButtonsDependingOnTableSelection();
         }
@@ -109,9 +111,9 @@
     });
     btnRemove.addSelectionListener(new SelectionAdapter() {
       @Override public void widgetSelected(SelectionEvent e) {
-        int index = tblDirectoryNames.getSelectionIndex();
+        int index = tblDirectoryPaths.getSelectionIndex();
         if (index < 0) return;
-        directoryNames.remove(index);
+        importPaths.remove(index);
         updateTable();
         enableButtonsDependingOnTableSelection();
       }
@@ -129,21 +131,22 @@
   }
 
   private void swap(boolean goUp) {
-    int index = tblDirectoryNames.getSelectionIndex();
+    int index = tblDirectoryPaths.getSelectionIndex();
     if (index < 0) return;
     int target = goUp ? index - 1 : index + 1;
-    TableItem[] selection = tblDirectoryNames.getSelection();
-    directoryNames.remove(index);
-    directoryNames.add(target, selection[0].getText());
+    int[] selection = tblDirectoryPaths.getSelectionIndices();
+    ImportPath path = importPaths.get(selection[0]);
+    importPaths.remove(index);
+    importPaths.add(target, path);
     updateTable();
-    tblDirectoryNames.setSelection(target);
+    tblDirectoryPaths.setSelection(target);
     enableButtonsDependingOnTableSelection();
   }
 
   /** {@inheritDoc} */
   @Override public void setEnabled(boolean enabled) {
     super.setEnabled(enabled);
-    tblDirectoryNames.setEnabled(enabled);
+    tblDirectoryPaths.setEnabled(enabled);
     btnAdd.setEnabled(enabled);
     if (enabled) {
       enableButtonsDependingOnTableSelection();
@@ -155,8 +158,8 @@
   }
 
   private void enableButtonsDependingOnTableSelection() {
-    int selectionIndex = tblDirectoryNames.getSelectionIndex();
-    int size = tblDirectoryNames.getItemCount();
+    int selectionIndex = tblDirectoryPaths.getSelectionIndex();
+    int size = tblDirectoryPaths.getItemCount();
     boolean hasSelection = selectionIndex >= 0;
     btnRemove.setEnabled(hasSelection);
     boolean hasElements = size > 1;
@@ -164,21 +167,26 @@
     btnDown.setEnabled(hasElements && hasSelection && selectionIndex < size - 1);
   }
 
-  public List<String> directoryNames() {
-    // return unmodifiableList(asList(tblDirectoryNames.getItems()));
-    return null;
+  public List<String> directoryPaths() {
+    List<String> paths = new ArrayList<String>();
+    for (ImportPath path : importPaths)
+      paths.add(path.toString());
+    return unmodifiableList(paths);
   }
 
-  public void addDirectoryNames(Collection<String> names) {
-    directoryNames.clear();
-    directoryNames.addAll(names);
+  public void addDirectoryPaths(Collection<String> paths) {
+    importPaths.clear();
+    for (String path : paths) {
+      if (isEmpty(path)) continue;
+      importPaths.add(ImportPath.parse(path));
+    }
     updateTable();
   }
   
   private void updateTable() {
-    tblDirectoryNamesViewer.setInput(directoryNames.toArray());
-    if (tblDirectoryNames.getItemCount() > 0 && tblDirectoryNames.getSelectionCount() == 0)
-      tblDirectoryNames.setSelection(0);
+    tblDirectoryPathsViewer.setInput(importPaths.toArray());
+    if (tblDirectoryPaths.getItemCount() > 0 && tblDirectoryPaths.getSelectionCount() == 0)
+      tblDirectoryPaths.setSelection(0);
   }
 
   public void onAddOrRemove(SelectionListener listener) {
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/ImportPath.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/ImportPath.java
new file mode 100644
index 0000000..d6eba1b
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/ImportPath.java
@@ -0,0 +1,40 @@
+/*
+ * 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.preferences.paths;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+class ImportPath {
+
+  private static final Pattern WORKSPACE_PATH_PATTERN = Pattern.compile("\\$\\{workspace_loc:(.*)\\}");
+  
+  final String value;
+  final boolean isWorkspacePath;
+
+  static ImportPath parse(String path) {
+    Matcher matcher = WORKSPACE_PATH_PATTERN.matcher(path);
+    if (matcher.matches()) return new ImportPath(matcher.group(1), true);
+    return new ImportPath(path, false);
+  }
+  
+  ImportPath(String path, boolean isWorkspacePath) {
+    this.value = path;
+    this.isWorkspacePath = isWorkspacePath;
+  }
+  
+  /** {@inheritDoc} */
+  @Override public String toString() {
+    if (!isWorkspacePath) return value;
+    return "${workspace_loc:" + value + "}";
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/IncludeDialog.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/IncludeDialog.java
index f43483d..16b6f65 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/IncludeDialog.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/IncludeDialog.java
@@ -31,8 +31,7 @@
   private Shell shell;
   private boolean result;
   
-  private String enteredPath;
-  private boolean isWorkspacePath;
+  private ImportPath selectedPath;
 
   private Text txtPath;
   private Button btnWorkspace;
@@ -135,8 +134,7 @@
     });
     btnOk.addSelectionListener(new SelectionAdapter() {
       @Override public void widgetSelected(SelectionEvent e) {
-        enteredPath = txtPath.getText().trim();
-        isWorkspacePath = btnIsWorkspacePath.getSelection();
+        selectedPath = new ImportPath(txtPath.getText().trim(), btnIsWorkspacePath.getSelection());
         result = true;
         shell.dispose();
       }
@@ -163,18 +161,10 @@
   }
 
   /**
-   * Returns the path entered/selected by the user.
-   * @return the path entered/selected by the user.
+   * Returns the path selected by the user.
+   * @return the path selected by the user.
    */
-  public String getEnteredPath() {
-    return enteredPath;
-  }
-
-  /**
-   * Indicates whether the path returned by <code>{@link #getEnteredPath()}</code> is a workspace path.
-   * @return {@code true} if the entered path is a workspace path; {@code false} otherwise.
-   */
-  public boolean isWorkspacePath() {
-    return isWorkspacePath;
+  public ImportPath getSelectedPath() {
+    return selectedPath;
   }
 }
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/PathsPreferencePage.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/PathsPreferencePage.java
index 06b5246..ce49fe7 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/PathsPreferencePage.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/PathsPreferencePage.java
@@ -105,7 +105,7 @@
   }
 
   private void checkState() {
-    if (directoryNamesEditor.isEnabled() && directoryNamesEditor.directoryNames().isEmpty()) {
+    if (directoryNamesEditor.isEnabled() && directoryNamesEditor.directoryPaths().isEmpty()) {
       pageIsNowInvalid(errorNoDirectoryNames);
       return;
     }
@@ -133,7 +133,7 @@
   }
 
   private void setDirectoryNames(String directoryNames) {
-    directoryNamesEditor.addDirectoryNames(split(directoryNames, COMMA_DELIMITER));
+    directoryNamesEditor.addDirectoryPaths(split(directoryNames, COMMA_DELIMITER));
   }
 
   private void enableProjectOptions(boolean enabled) {
@@ -153,7 +153,7 @@
   }
 
   private String directoryNames() {
-    return concat(COMMA_DELIMITER, directoryNamesEditor.directoryNames());
+    return concat(COMMA_DELIMITER, directoryNamesEditor.directoryPaths());
   }
 
   /** {@inheritDoc} */
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/SelectDirectoryDialogLauncher.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/SelectDirectoryDialogLauncher.java
index 046c2d9..4acbf87 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/SelectDirectoryDialogLauncher.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/SelectDirectoryDialogLauncher.java
@@ -59,8 +59,7 @@
     if (dialog.open() != OK) return null;
     IResource resource = (IResource) dialog.getFirstResult();
     if (resource == null) return null;
-    StringBuilder b = new StringBuilder();
-    return b.append("${").append("workspace_loc:").append(resource.getFullPath()).append("}").toString();
+    return resource.getFullPath().toString();
   }
   
   public static String showFileSystemFolderDialog(Shell shell, String filterPath) {