Major cleanup of protoc-invocation-related code.
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtoDescriptorPathFinder_findRootOf_Test.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtoDescriptorPathFinder_findRootOf_Test.java
deleted file mode 100644
index 81f8409..0000000
--- a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtoDescriptorPathFinder_findRootOf_Test.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.builder.protoc;
-
-import static org.hamcrest.core.IsEqual.equalTo;
-import static org.junit.Assert.*;
-import static org.junit.rules.ExpectedException.none;
-
-import org.junit.*;
-import org.junit.rules.ExpectedException;
-
-/**
- * Tests for <code>{@link ProtoDescriptorPathFinder#findRootOf(String)}</code>.
- *
- * @author alruiz@google.com (Alex Ruiz)
- */
-public class ProtoDescriptorPathFinder_findRootOf_Test {
-  private static ProtoDescriptorPathFinder finder;
-
-  @BeforeClass public static void setUpOnce() {
-    finder = new ProtoDescriptorPathFinder("/");
-  }
-
-  @Rule public ExpectedException thrown = none();
-
-  @Test public void should_return_null_if_path_is_null() {
-    assertNull(finder.findRootOf(null));
-  }
-
-  @Test public void should_return_null_if_path_is_empty() {
-    assertNull(finder.findRootOf(""));
-  }
-
-  @Test public void should_throw_error_if_path_does_not_contain_descriptor_FQN() {
-    thrown.expect(IllegalArgumentException.class);
-    thrown.expectMessage("Path '/usr/local/include' does not contain '/google/protobuf/descriptor.proto'");
-    finder.findRootOf("/usr/local/include");
-  }
-
-  @Test public void should_find_import_root_of_descriptor() {
-    String filePath = "/usr/local/include/google/protobuf/descriptor.proto";
-    assertThat(finder.findRootOf(filePath), equalTo("/usr/local/include"));
-  }
-}
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/command/DescriptorPathProtocOption_appendOptionToCommand_Test.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/command/DescriptorPathProtocOption_appendOptionToCommand_Test.java
new file mode 100644
index 0000000..8bba92d
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/command/DescriptorPathProtocOption_appendOptionToCommand_Test.java
@@ -0,0 +1,70 @@
+/*
+ * 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.builder.protoc.command;
+
+import static org.junit.rules.ExpectedException.none;
+import static org.mockito.Mockito.*;
+
+import org.junit.*;
+import org.junit.rules.ExpectedException;
+
+import com.google.eclipse.protobuf.ui.preferences.StringPreference;
+import com.google.eclipse.protobuf.ui.preferences.compiler.core.CompilerPreferences;
+
+/**
+ * Tests for <code>{@link DescriptorPathProtocOption#appendOptionToCommand(ProtocCommand)}</code>.
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class DescriptorPathProtocOption_appendOptionToCommand_Test {
+  @Rule public ExpectedException thrown = none();
+
+  private StringPreference descriptorPath;
+  private CompilerPreferences preferences;
+  private ProtocCommand command;
+  private DescriptorPathProtocOption option;
+
+  @Before public void setUp() {
+    descriptorPath = mock(StringPreference.class);
+    preferences = mock(CompilerPreferences.class);
+    command = mock(ProtocCommand.class);
+    option = new DescriptorPathProtocOption(preferences, "/");
+  }
+
+  @Test public void should_not_append_to_command_if_descriptor_path_is_null() {
+    expectDescriptorPathToBeEqualTo(null);
+    option.appendOptionToCommand(command);
+    verifyZeroInteractions(command);
+  }
+
+  @Test public void should_not_append_to_command_if_descriptor_path_is_empty() {
+    expectDescriptorPathToBeEqualTo("");
+    option.appendOptionToCommand(command);
+    verifyZeroInteractions(command);
+  }
+
+  @Test public void should_throw_error_if_descriptor_path_does_not_contain_descriptor_FQN() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Path '/usr/local/include' does not contain '/google/protobuf/descriptor.proto'");
+    expectDescriptorPathToBeEqualTo("/usr/local/include");
+    option.appendOptionToCommand(command);
+    verifyZeroInteractions(command);
+  }
+
+  @Test public void should_append_path_of_descriptor_to_command() {
+    expectDescriptorPathToBeEqualTo("/usr/local/include/google/protobuf/descriptor.proto");
+    option.appendOptionToCommand(command);
+    verify(command).appendOption("proto_path", "/usr/local/include");
+  }
+
+  private void expectDescriptorPathToBeEqualTo(String value) {
+    when(preferences.descriptorPath()).thenReturn(descriptorPath);
+    when(descriptorPath.getValue()).thenReturn(value);
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/CompositeOutputParser_parseAndAddMarkerIfNecessary_Test.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/output/CompositeOutputParser_parseAndAddMarkerIfNecessary_Test.java
similarity index 97%
rename from com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/CompositeOutputParser_parseAndAddMarkerIfNecessary_Test.java
rename to com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/output/CompositeOutputParser_parseAndAddMarkerIfNecessary_Test.java
index b0f3228..d5ede0c 100644
--- a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/CompositeOutputParser_parseAndAddMarkerIfNecessary_Test.java
+++ b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/output/CompositeOutputParser_parseAndAddMarkerIfNecessary_Test.java
@@ -6,7 +6,7 @@
  *
  * http://www.eclipse.org/legal/epl-v10.html
  */
-package com.google.eclipse.protobuf.ui.builder.protoc;
+package com.google.eclipse.protobuf.ui.builder.protoc.output;
 
 import static org.mockito.Mockito.*;
 
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtocMarkerFactory_createErrorIfNecessary_Test.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/output/ProtocMarkerFactory_createErrorIfNecessary_Test.java
similarity index 97%
rename from com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtocMarkerFactory_createErrorIfNecessary_Test.java
rename to com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/output/ProtocMarkerFactory_createErrorIfNecessary_Test.java
index 0e21e1c..b798fe7 100644
--- a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtocMarkerFactory_createErrorIfNecessary_Test.java
+++ b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/output/ProtocMarkerFactory_createErrorIfNecessary_Test.java
@@ -6,7 +6,7 @@
  *
  * http://www.eclipse.org/legal/epl-v10.html
  */
-package com.google.eclipse.protobuf.ui.builder.protoc;
+package com.google.eclipse.protobuf.ui.builder.protoc.output;
 
 import static com.google.eclipse.protobuf.junit.stubs.resources.MarkerStub.error;
 import static com.google.eclipse.protobuf.ui.validation.ProtobufResourceUIValidatorExtension.EDITOR_CHECK;
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/RegexOutputParser_parseAndAddMarkerIfNecessary_withLineNumber_Test.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/output/RegexOutputParser_parseAndAddMarkerIfNecessary_withLineNumber_Test.java
similarity index 95%
rename from com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/RegexOutputParser_parseAndAddMarkerIfNecessary_withLineNumber_Test.java
rename to com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/output/RegexOutputParser_parseAndAddMarkerIfNecessary_withLineNumber_Test.java
index 841cbb8..793a984 100644
--- a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/RegexOutputParser_parseAndAddMarkerIfNecessary_withLineNumber_Test.java
+++ b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/output/RegexOutputParser_parseAndAddMarkerIfNecessary_withLineNumber_Test.java
@@ -6,7 +6,7 @@
  *
  * http://www.eclipse.org/legal/epl-v10.html
  */
-package com.google.eclipse.protobuf.ui.builder.protoc;
+package com.google.eclipse.protobuf.ui.builder.protoc.output;
 
 import static org.mockito.Mockito.*;
 
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/RegexOutputParser_parseAndAddMarkerIfNecessary_withoutLineNumber_Test.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/output/RegexOutputParser_parseAndAddMarkerIfNecessary_withoutLineNumber_Test.java
similarity index 95%
rename from com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/RegexOutputParser_parseAndAddMarkerIfNecessary_withoutLineNumber_Test.java
rename to com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/output/RegexOutputParser_parseAndAddMarkerIfNecessary_withoutLineNumber_Test.java
index 7602ba6..31153fa 100644
--- a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/RegexOutputParser_parseAndAddMarkerIfNecessary_withoutLineNumber_Test.java
+++ b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/builder/protoc/output/RegexOutputParser_parseAndAddMarkerIfNecessary_withoutLineNumber_Test.java
@@ -6,7 +6,7 @@
  *
  * http://www.eclipse.org/legal/epl-v10.html
  */
-package com.google.eclipse.protobuf.ui.builder.protoc;
+package com.google.eclipse.protobuf.ui.builder.protoc.output;
 
 import static org.mockito.Mockito.*;
 
diff --git a/com.google.eclipse.protobuf.ui/plugin.xml b/com.google.eclipse.protobuf.ui/plugin.xml
index 54a8999..ae19672 100644
--- a/com.google.eclipse.protobuf.ui/plugin.xml
+++ b/com.google.eclipse.protobuf.ui/plugin.xml
@@ -248,7 +248,7 @@
   </extension>
   <extension point="org.eclipse.xtext.builder.participant">
     <participant
-      class="com.google.eclipse.protobuf.ui.ProtobufExecutableExtensionFactory:com.google.eclipse.protobuf.ui.builder.protoc.ProtobufBuildParticipant">
+      class="com.google.eclipse.protobuf.ui.ProtobufExecutableExtensionFactory:com.google.eclipse.protobuf.ui.builder.protoc.core.ProtobufBuildParticipant">
     </participant>
   </extension>
   <extension
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/OutputDirectories.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/OutputDirectories.java
deleted file mode 100644
index c42cec6..0000000
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/OutputDirectories.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.builder.protoc;
-
-import org.eclipse.core.resources.IProject;
-import org.eclipse.core.runtime.CoreException;
-
-import com.google.eclipse.protobuf.ui.preferences.compiler.core.CompilerPreferences;
-
-/**
- * @author alruiz@google.com (Alex Ruiz)
- */
-class OutputDirectories {
-  private final OutputDirectory java;
-  private final OutputDirectory cpp;
-  private final OutputDirectory python;
-
-  OutputDirectories(IProject project, CompilerPreferences preferences) throws CoreException {
-    java = new OutputDirectory(project, preferences.javaCodeGenerationEnabled(), preferences.javaOutputDirectory());
-    cpp = new OutputDirectory(project, preferences.cppCodeGenerationEnabled(), preferences.cppOutputDirectory());
-    python = new OutputDirectory(project, preferences.pythonCodeGenerationEnabled(),
-        preferences.pythonOutputDirectory());
-  }
-
-  OutputDirectory java() {
-    return java;
-  }
-
-  OutputDirectory cpp() {
-    return cpp;
-  }
-
-  OutputDirectory python() {
-    return python;
-  }
-}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/OutputDirectory.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/OutputDirectory.java
deleted file mode 100644
index 75cd929..0000000
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/OutputDirectory.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.builder.protoc;
-
-import static com.google.eclipse.protobuf.ui.util.Paths.segmentsOf;
-import static org.eclipse.core.runtime.IPath.SEPARATOR;
-
-import org.eclipse.core.resources.*;
-import org.eclipse.core.runtime.*;
-
-import com.google.eclipse.protobuf.ui.preferences.*;
-
-/**
- * @author alruiz@google.com (Alex Ruiz)
- */
-class OutputDirectory {
-  private static final NullProgressMonitor NO_MONITOR = new NullProgressMonitor();
-
-  private final boolean enabled;
-  private final IFolder location;
-
-  OutputDirectory(IProject project, BooleanPreference codeGenerationEnabled, StringPreference outputDirectory)
-      throws CoreException {
-    enabled = codeGenerationEnabled.getValue();
-    location = findOrCreateLocation(project, outputDirectory.getValue());
-  }
-
-  private IFolder findOrCreateLocation(IProject project, String directoryName) throws CoreException {
-    IFolder directory = null;
-    if (enabled) {
-      StringBuilder path = new StringBuilder();
-      for (String segment : segmentsOf(directoryName)) {
-        path.append(segment);
-        directory = project.getFolder(path.toString());
-        if (!directory.exists()) {
-          directory.create(true, true, NO_MONITOR);
-        }
-        path.append(SEPARATOR);
-      }
-    }
-    return directory;
-  }
-
-  boolean isEnabled() {
-    return enabled;
-  }
-
-  IFolder getLocation() {
-    return location;
-  }
-}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtoDescriptorPathFinder.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtoDescriptorPathFinder.java
deleted file mode 100644
index 21262e1..0000000
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtoDescriptorPathFinder.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.builder.protoc;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static java.io.File.separator;
-import static org.eclipse.xtext.util.Strings.*;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.inject.Singleton;
-
-/**
- * @author alruiz@google.com (Alex Ruiz)
- */
-@Singleton class ProtoDescriptorPathFinder {
-  private final String descriptorFqn;
-
-  ProtoDescriptorPathFinder() {
-    this(separator);
-  }
-
-  @VisibleForTesting ProtoDescriptorPathFinder(String fileSeparator) {
-    descriptorFqn = concat(fileSeparator, newArrayList("", "google", "protobuf", "descriptor.proto"));
-  }
-
-  String findRootOf(String descriptorFilePath) {
-    if (isEmpty(descriptorFilePath)) {
-      return null;
-    }
-    int indexOfDescriptorFqn = descriptorFilePath.indexOf(descriptorFqn);
-    if (indexOfDescriptorFqn == -1) {
-      String format = "Path '%s' does not contain '%s'";
-      throw new IllegalArgumentException(String.format(format, descriptorFilePath, descriptorFqn));
-    }
-    return descriptorFilePath.substring(0, indexOfDescriptorFqn);
-  }
-}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtobufBuildParticipant.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtobufBuildParticipant.java
deleted file mode 100644
index ed4e295..0000000
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtobufBuildParticipant.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * 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.builder.protoc;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static com.google.common.io.Closeables.closeQuietly;
-import static com.google.eclipse.protobuf.ui.builder.protoc.ConsolePrinter.createAndDisplayConsole;
-import static com.google.eclipse.protobuf.ui.exception.CoreExceptions.error;
-import static com.google.eclipse.protobuf.ui.preferences.compiler.core.CompilerPreferences.compilerPreferences;
-import static com.google.eclipse.protobuf.ui.util.CommaSeparatedValues.splitCsv;
-import static java.util.Collections.*;
-import static org.eclipse.core.resources.IResource.DEPTH_INFINITE;
-
-import java.io.*;
-import java.util.List;
-
-import org.eclipse.core.resources.*;
-import org.eclipse.core.runtime.*;
-import org.eclipse.emf.common.util.URI;
-import org.eclipse.xtext.builder.IXtextBuilderParticipant;
-import org.eclipse.xtext.resource.IResourceDescription;
-import org.eclipse.xtext.resource.IResourceDescription.Delta;
-import org.eclipse.xtext.ui.editor.preferences.IPreferenceStoreAccess;
-
-import com.google.eclipse.protobuf.ui.preferences.compiler.core.CompilerPreferences;
-import com.google.eclipse.protobuf.ui.preferences.paths.core.DirectoryPath;
-import com.google.eclipse.protobuf.ui.preferences.paths.core.PathsPreferences;
-import com.google.inject.Inject;
-
-/**
- * Calls protoc to generate Java, C++ or Python code from .proto files.
- *
- * @author alruiz@google.com (Alex Ruiz)
- */
-public class ProtobufBuildParticipant implements IXtextBuilderParticipant {
-  @Inject private ProtocCommandFactory commandFactory;
-  @Inject private ProtocOutputParser outputParser;
-  @Inject private ProtoDescriptorPathFinder protoDescriptorPathFinder;
-  @Inject private IPreferenceStoreAccess storeAccess;
-
-  @Override public void build(IBuildContext context, IProgressMonitor monitor) throws CoreException {
-    IProject project = context.getBuiltProject();
-    CompilerPreferences preferences = compilerPreferences(storeAccess, project);
-    if (!preferences.compileProtoFiles().getValue()) {
-      return;
-    }
-    List<Delta> deltas = context.getDeltas();
-    if (deltas.isEmpty()) {
-      return;
-    }
-    OutputDirectories outputDirectories = new OutputDirectories(project, preferences);
-    String descriptorPath = descriptorPath(preferences);
-    List<String> importRoots = importRoots(project);
-    for (Delta d : deltas) {
-      IFile source = protoFile(d.getNew(), project);
-      if (source == null) {
-        continue;
-      }
-      if (importRoots.isEmpty()) {
-        importRoots = singleImportRoot(source);
-      }
-      generateSingleProto(source, protocPath(preferences), importRoots, descriptorPath, outputDirectories);
-    }
-    if (preferences.refreshResources().getValue()) {
-      boolean refreshProject = preferences.refreshProject().getValue();
-      refresh(project, outputDirectories, refreshProject, monitor);
-    }
-  }
-
-  private String descriptorPath(CompilerPreferences preferences) {
-    return protoDescriptorPathFinder.findRootOf(preferences.descriptorPath().getValue());
-  }
-
-  private List<String> importRoots(IProject project) {
-    List<String> paths = newArrayList();
-    PathsPreferences preferences = new PathsPreferences(storeAccess, project);
-    if (preferences.filesInMultipleDirectories().getValue()) {
-      String directoryPaths = preferences.directoryPaths().getValue();
-      for (String importRoot : splitCsv(directoryPaths)) {
-        DirectoryPath path = DirectoryPath.parse(importRoot, project);
-        String location = path.absolutePathInFileSystem();
-        if (location != null) {
-          paths.add(location);
-        }
-      }
-      return unmodifiableList(paths);
-    }
-    return emptyList();
-  }
-
-  private IFile protoFile(IResourceDescription resource, IProject project) {
-    String path = filePathIfIsProtoFile(resource);
-    return (path == null) ? null : project.getWorkspace().getRoot().getFile(new Path(path));
-  }
-
-  private String filePathIfIsProtoFile(IResourceDescription resource) {
-    if (resource == null) {
-      return null;
-    }
-    URI uri = resource.getURI();
-    if (!uri.fileExtension().equals("proto"))
-    {
-      return null;
-    }
-    if (uri.scheme() == null) {
-      return uri.toFileString();
-    }
-    StringBuilder b = new StringBuilder();
-    int segmentCount = uri.segmentCount();
-    for (int i = 1; i < segmentCount; i++)
-     {
-      b.append("/").append(uri.segment(i));
-    }
-    return b.length() == 0 ? null : b.toString();
-  }
-
-  private List<String> singleImportRoot(IFile source) {
-    IProject project = source.getProject();
-    File projectFile = project.getLocation().toFile();
-    File current = source.getLocation().toFile();
-    while (!current.getParentFile().equals(projectFile)) {
-      current = current.getParentFile();
-    }
-    return singletonList(current.toString());
-  }
-
-  String protocPath(CompilerPreferences preferences) {
-    if (preferences.useProtocInSystemPath().getValue()) {
-      return "protoc";
-    }
-    return preferences.protocPath().getValue();
-  }
-
-  private void generateSingleProto(IFile source, String protocPath, List<String> importRoots, String descriptorPath,
-      OutputDirectories outputDirectories) throws CoreException {
-    String command = commandFactory.protocCommand(source, protocPath, importRoots, descriptorPath, outputDirectories);
-    ConsolePrinter console = null;
-    try {
-      console = createAndDisplayConsole();
-      console.printCommand(command);
-      Process process = Runtime.getRuntime().exec(command);
-      processStream(process.getErrorStream(), source, console);
-      process.destroy();
-    } catch (Throwable e) {
-      e.printStackTrace();
-      throw error(e);
-    } finally {
-      if (console != null) {
-        console.close();
-      }
-    }
-  }
-
-  private void processStream(InputStream stream, IFile source, ConsolePrinter console) throws Throwable {
-    InputStreamReader reader = null;
-    try {
-      reader = new InputStreamReader(stream);
-      BufferedReader bufferedReader = new BufferedReader(reader);
-      String line = null;
-      ProtocMarkerFactory markerFactory = new ProtocMarkerFactory(source);
-      while ((line = bufferedReader.readLine()) != null) {
-        outputParser.parseAndAddMarkerIfNecessary(line, markerFactory);
-        console.printProtocOutput(line);
-      }
-    } finally {
-      closeQuietly(reader);
-    }
-  }
-
-  private void refresh(IProject project, OutputDirectories outputDirectories, boolean refreshProject,
-      IProgressMonitor monitor) throws CoreException {
-    if (refreshProject) {
-      project.refreshLocal(DEPTH_INFINITE, monitor);
-      return;
-    }
-    refresh(outputDirectories.java(), monitor);
-    refresh(outputDirectories.cpp(), monitor);
-    refresh(outputDirectories.python(), monitor);
-  }
-
-  private void refresh(OutputDirectory directory, IProgressMonitor monitor) throws CoreException {
-    if (directory.isEnabled()) {
-      IFolder location = directory.getLocation();
-      location.refreshLocal(DEPTH_INFINITE, monitor);
-    }
-  }
-}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtocCommandFactory.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtocCommandFactory.java
deleted file mode 100644
index fa53cb4..0000000
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtocCommandFactory.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.builder.protoc;
-
-import static com.google.eclipse.protobuf.util.CommonWords.space;
-import static org.eclipse.xtext.util.Strings.isEmpty;
-
-import java.util.List;
-
-import org.eclipse.core.resources.*;
-
-/**
- * @author alruiz@google.com (Alex Ruiz)
- */
-class ProtocCommandFactory {
-  String protocCommand(IFile protoFile, String protocPath, List<String> importRoots, String descriptorPath,
-      OutputDirectories outputDirectories) {
-    StringBuilder command = new StringBuilder();
-    command.append(protocPath).append(space());
-    for (String importRoot : importRoots) {
-      command.append("-I=").append(importRoot).append(space());
-    }
-    if (!isEmpty(descriptorPath)) {
-      command.append("--proto_path=").append(descriptorPath).append(space());
-    }
-    addOutputDirectory(outputDirectories.java(), "java", command);
-    addOutputDirectory(outputDirectories.cpp(), "cpp", command);
-    addOutputDirectory(outputDirectories.python(), "python", command);
-    command.append(protoFile.getLocation().toOSString());
-    return command.toString();
-  }
-
-  private void addOutputDirectory(OutputDirectory outputDirectory, String code, StringBuilder command) {
-    if (!outputDirectory.isEnabled()) {
-      return;
-    }
-    command.append("--").append(code).append("_out=");
-    IFolder directory = outputDirectory.getLocation();
-    command.append(directory.getLocation().toOSString()).append(space());
-  }
-}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtocOutputParser.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtocOutputParser.java
deleted file mode 100644
index 7b16351..0000000
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtocOutputParser.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.builder.protoc;
-
-import org.eclipse.core.runtime.CoreException;
-
-import com.google.inject.ImplementedBy;
-
-/**
- * @author alruiz@google.com (Alex Ruiz)
- */
-@ImplementedBy(CompositeOutputParser.class)
-interface ProtocOutputParser {
-  boolean parseAndAddMarkerIfNecessary(String line, ProtocMarkerFactory markerFactory) throws CoreException;
-}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/CppProtocOption.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/CppProtocOption.java
new file mode 100644
index 0000000..83bc3ff
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/CppProtocOption.java
@@ -0,0 +1,62 @@
+/*
+ * 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.builder.protoc.command;
+
+import static com.google.eclipse.protobuf.ui.builder.protoc.command.IResources.*;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.CoreException;
+
+import com.google.eclipse.protobuf.ui.preferences.compiler.core.CompilerPreferences;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+class CppProtocOption implements OutputDirectoryProtocOption {
+  private final CompilerPreferences preferences;
+  private final IProject project;
+
+  private boolean initialized;
+  private boolean enabled;
+  private IFolder outputDirectory;
+  private String outputDirectoryLocation;
+
+  CppProtocOption(CompilerPreferences preferences, IProject project) {
+    this.preferences = preferences;
+    this.project = project;
+  }
+
+  @Override public void appendOptionToCommand(ProtocCommand command) throws CoreException {
+    ensureIsInitialized();
+    if (enabled) {
+      command.appendOption("cpp_out", outputDirectoryLocation);
+    }
+  }
+
+  @Override public IFolder outputDirectory() throws CoreException {
+    ensureIsInitialized();
+    return outputDirectory;
+  }
+
+  private void ensureIsInitialized() throws CoreException {
+    if (!initialized) {
+      initialize();
+    }
+  }
+
+  private void initialize() throws CoreException {
+    initialized = true;
+    enabled = preferences.cppCodeGenerationEnabled().getValue();
+    if (enabled) {
+      String directoryName = preferences.cppOutputDirectory().getValue();
+      outputDirectory = findOrCreateDirectory(directoryName, project);
+      outputDirectoryLocation = locationOf(outputDirectory);
+    }
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/DescriptorPathProtocOption.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/DescriptorPathProtocOption.java
new file mode 100644
index 0000000..95e86e9
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/DescriptorPathProtocOption.java
@@ -0,0 +1,58 @@
+/*
+ * 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.builder.protoc.command;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static java.io.File.separator;
+import static org.eclipse.xtext.util.Strings.*;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.eclipse.protobuf.ui.preferences.compiler.core.CompilerPreferences;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+class DescriptorPathProtocOption implements ProtocOption {
+  private final CompilerPreferences preferences;
+  private final String descriptorFqn;
+
+  private boolean initialized;
+  private String descriptorPath;
+
+  DescriptorPathProtocOption(CompilerPreferences preferences) {
+    this(preferences, separator);
+  }
+
+  @VisibleForTesting DescriptorPathProtocOption(CompilerPreferences preferences, String pathSeparator) {
+    this.preferences = preferences;
+    descriptorFqn = concat(pathSeparator, newArrayList("", "google", "protobuf", "descriptor.proto"));
+  }
+
+  @Override public void appendOptionToCommand(ProtocCommand command) {
+    if (!initialized) {
+      initialize();
+    }
+    if (!isEmpty(descriptorPath)) {
+      command.appendOption("proto_path", descriptorPath);
+    }
+  }
+
+  private void initialize() {
+    initialized = true;
+    String fullPath = preferences.descriptorPath().getValue();
+    if (!isEmpty(fullPath)) {
+      int indexOfDescriptorFqn = fullPath.indexOf(descriptorFqn);
+      if (indexOfDescriptorFqn == -1) {
+        String format = "Path '%s' does not contain '%s'";
+        throw new IllegalArgumentException(String.format(format, fullPath, descriptorFqn));
+      }
+      descriptorPath = fullPath.substring(0, indexOfDescriptorFqn);
+    }
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/IResources.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/IResources.java
new file mode 100644
index 0000000..83340ae
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/IResources.java
@@ -0,0 +1,42 @@
+/*
+ * 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.builder.protoc.command;
+
+import static com.google.eclipse.protobuf.ui.util.Paths.segmentsOf;
+import static org.eclipse.core.runtime.IPath.SEPARATOR;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+final class IResources {
+  private static final NullProgressMonitor NO_MONITOR = new NullProgressMonitor();
+
+  static String locationOf(IFolder directory) {
+    return directory.getLocation().toOSString();
+  }
+
+  static IFolder findOrCreateDirectory(String directoryName, IProject project) throws CoreException {
+    IFolder directory = null;
+    StringBuilder path = new StringBuilder();
+    for (String segment : segmentsOf(directoryName)) {
+      path.append(segment);
+      directory = project.getFolder(path.toString());
+      if (!directory.exists()) {
+        directory.create(true, true, NO_MONITOR);
+      }
+      path.append(SEPARATOR);
+    }
+    return directory;
+  }
+
+  private IResources() {}
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/ImportRootsProtocOption.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/ImportRootsProtocOption.java
new file mode 100644
index 0000000..902e799
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/ImportRootsProtocOption.java
@@ -0,0 +1,85 @@
+/*
+ * 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.builder.protoc.command;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.eclipse.protobuf.ui.preferences.paths.core.DirectoryPath.parse;
+import static com.google.eclipse.protobuf.ui.util.CommaSeparatedValues.splitCsv;
+import static java.util.Collections.emptyList;
+import static org.eclipse.xtext.util.Strings.isEmpty;
+
+import java.io.File;
+import java.util.List;
+
+import org.eclipse.core.resources.*;
+
+import com.google.eclipse.protobuf.ui.preferences.paths.core.*;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+class ImportRootsProtocOption {
+  private final PathsPreferences preferences;
+  private final IProject project;
+
+  private boolean initialized;
+  private List<String> importRoots;
+
+  ImportRootsProtocOption(PathsPreferences preferences, IProject project) {
+    this.preferences = preferences;
+    this.project = project;
+  }
+
+  public void appendOptionToCommand(ProtocCommand command, IFile protoFile) {
+    if (!initialized) {
+      initialize();
+    }
+    if (!importRoots.isEmpty()) {
+      for (String importRoot : importRoots) {
+        appendToCommand(command, importRoot);
+      }
+      return;
+    }
+    appendToCommand(command, singleImportRoot(protoFile));
+  }
+
+  private void initialize() {
+    initialized = true;
+    if (!preferences.filesInMultipleDirectories().getValue()) {
+      importRoots =  emptyList();
+      return;
+    }
+    importRoots = newArrayList();
+    String directoryPaths = preferences.directoryPaths().getValue();
+    for (String importRoot : splitCsv(directoryPaths)) {
+      DirectoryPath path = parse(importRoot, project);
+      String location = path.absolutePathInFileSystem();
+      if (!isEmpty(location)) {
+        importRoots.add(location);
+      }
+    }
+  }
+
+  private String singleImportRoot(IFile protoFile) {
+    File projectFile = toFile(project);
+    File currentFile = toFile(protoFile);
+    while (!currentFile.getParentFile().equals(projectFile)) {
+      currentFile = currentFile.getParentFile();
+    }
+    return currentFile.toString();
+  }
+
+  private File toFile(IResource resource) {
+    return resource.getLocation().toFile();
+  }
+
+  private void appendToCommand(ProtocCommand command, String importRoot) {
+    command.appendOption("proto_path", importRoot);
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/JavaProtocOption.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/JavaProtocOption.java
new file mode 100644
index 0000000..9b276de
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/JavaProtocOption.java
@@ -0,0 +1,62 @@
+/*
+ * 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.builder.protoc.command;
+
+import static com.google.eclipse.protobuf.ui.builder.protoc.command.IResources.*;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.CoreException;
+
+import com.google.eclipse.protobuf.ui.preferences.compiler.core.CompilerPreferences;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+class JavaProtocOption implements OutputDirectoryProtocOption {
+  private final CompilerPreferences preferences;
+  private final IProject project;
+
+  private boolean initialized;
+  private boolean enabled;
+  private IFolder outputDirectory;
+  private String outputDirectoryLocation;
+
+  JavaProtocOption(CompilerPreferences preferences, IProject project) {
+    this.preferences = preferences;
+    this.project = project;
+  }
+
+  @Override public void appendOptionToCommand(ProtocCommand command) throws CoreException {
+    ensureIsInitialized();
+    if (enabled) {
+      command.appendOption("java_out", outputDirectoryLocation);
+    }
+  }
+
+  @Override public IFolder outputDirectory() throws CoreException {
+    ensureIsInitialized();
+    return outputDirectory;
+  }
+
+  private void ensureIsInitialized() throws CoreException {
+    if (!initialized) {
+      initialize();
+    }
+  }
+
+  private void initialize() throws CoreException {
+    initialized = true;
+    enabled = preferences.javaCodeGenerationEnabled().getValue();
+    if (enabled) {
+      String directoryName = preferences.javaOutputDirectory().getValue();
+      outputDirectory = findOrCreateDirectory(directoryName, project);
+      outputDirectoryLocation = locationOf(outputDirectory);
+    }
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/OutputDirectoryProtocOption.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/OutputDirectoryProtocOption.java
new file mode 100644
index 0000000..3f8b965
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/OutputDirectoryProtocOption.java
@@ -0,0 +1,19 @@
+/*
+ * 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.builder.protoc.command;
+
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public interface OutputDirectoryProtocOption extends ProtocOption {
+  IFolder outputDirectory() throws CoreException;
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/ProtocCommand.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/ProtocCommand.java
new file mode 100644
index 0000000..f44d26b
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/ProtocCommand.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.builder.protoc.command;
+
+import static com.google.eclipse.protobuf.util.CommonWords.space;
+
+/**
+ * The command used to call protoc to compile a single .proto file.
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+class ProtocCommand {
+  private final StringBuilder content = new StringBuilder();
+
+  ProtocCommand(String protocPath) {
+    content.append(protocPath).append(space());
+  }
+
+  /**
+   * Appends the given option name and value to the command. So far, the best description of protoc options is
+   * <a href="http://www.discursive.com/books/cjcook/reference/proto-sect-compiling"> this one</a>.
+   * @param name the given option name.
+   * @param value the given option value.
+   */
+  void appendOption(String name, String value) {
+    content.append("--").append(name).append("=").append(value).append(space());
+  }
+
+  @Override public String toString() {
+    return content.toString();
+  };
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/ProtocCommandBuilder.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/ProtocCommandBuilder.java
new file mode 100644
index 0000000..9bf1921
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/ProtocCommandBuilder.java
@@ -0,0 +1,74 @@
+/*
+ * 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.builder.protoc.command;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Collections.unmodifiableList;
+
+import java.util.List;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.CoreException;
+
+import com.google.eclipse.protobuf.ui.preferences.compiler.core.CompilerPreferences;
+import com.google.eclipse.protobuf.ui.preferences.paths.core.PathsPreferences;
+
+/**
+ * Builds the command to call protoc to compile a single .proto file.
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class ProtocCommandBuilder {
+  private final List<ProtocOption> options = newArrayList();
+
+  private final String protocPath;
+  private final ImportRootsProtocOption importRootsProtocOption;
+
+  public ProtocCommandBuilder(CompilerPreferences compilerPreferences, PathsPreferences pathsPreferences,
+      IProject project) {
+    boolean useProtocInSystemPath = compilerPreferences.useProtocInSystemPath().getValue();
+    protocPath = useProtocInSystemPath ? "protoc" : compilerPreferences.protocPath().getValue();
+    options.add(new DescriptorPathProtocOption(compilerPreferences));
+    options.add(new JavaProtocOption(compilerPreferences, project));
+    options.add(new CppProtocOption(compilerPreferences, project));
+    options.add(new PythonProtocOption(compilerPreferences, project));
+    importRootsProtocOption = new ImportRootsProtocOption(pathsPreferences, project);
+  }
+
+  /**
+   * Builds the command to call protoc to compile a single .proto file.
+   * @param protoFile the .proto file.
+   * @return the built command.
+   * @throws CoreException if something goes wrong.
+   */
+  public String buildCommand(IFile protoFile) throws CoreException {
+    ProtocCommand command = new ProtocCommand(protocPath);
+    importRootsProtocOption.appendOptionToCommand(command, protoFile);
+    for (ProtocOption option : options) {
+      option.appendOptionToCommand(command);
+    }
+    return command.toString();
+  }
+
+  /**
+   * Returns the output directories where generated code is stored.
+   * @return the output directories where generated code is stored.
+   * @throws CoreException if something goes wrong.
+   */
+  public List<IFolder> outputDirectories() throws CoreException {
+    List<IFolder> outputDirectories = newArrayList();
+    for (ProtocOption option : options) {
+      if (option instanceof OutputDirectoryProtocOption) {
+        IFolder outputDirectory = ((OutputDirectoryProtocOption) option).outputDirectory();
+        outputDirectories.add(outputDirectory);
+      }
+    }
+    return unmodifiableList(outputDirectories);
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/ProtocOption.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/ProtocOption.java
new file mode 100644
index 0000000..ec2675e
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/ProtocOption.java
@@ -0,0 +1,18 @@
+/*
+ * 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.builder.protoc.command;
+
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+interface ProtocOption {
+  void appendOptionToCommand(ProtocCommand command) throws CoreException;
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/PythonProtocOption.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/PythonProtocOption.java
new file mode 100644
index 0000000..e31b9e5
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/command/PythonProtocOption.java
@@ -0,0 +1,61 @@
+/*
+ * 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.builder.protoc.command;
+
+import static com.google.eclipse.protobuf.ui.builder.protoc.command.IResources.*;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.CoreException;
+
+import com.google.eclipse.protobuf.ui.preferences.compiler.core.CompilerPreferences;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+class PythonProtocOption implements OutputDirectoryProtocOption {
+  private final CompilerPreferences preferences;
+  private final IProject project;
+
+  private boolean initialized;
+  private boolean enabled;
+  private IFolder outputDirectory;
+  private String outputDirectoryLocation;
+
+  PythonProtocOption(CompilerPreferences preferences, IProject project) {
+    this.preferences = preferences;
+    this.project = project;
+  }
+
+  @Override public void appendOptionToCommand(ProtocCommand command) throws CoreException {
+    ensureIsInitialized();
+    if (enabled) {
+      command.appendOption("python_out", outputDirectoryLocation);
+    }
+  }
+
+  @Override public IFolder outputDirectory() throws CoreException {
+    ensureIsInitialized();
+    return outputDirectory;
+  }
+
+  private void ensureIsInitialized() throws CoreException {
+    if (!initialized) {
+      initialize();
+    }
+  }
+  private void initialize() throws CoreException {
+    initialized = true;
+    enabled = preferences.pythonCodeGenerationEnabled().getValue();
+    if (enabled) {
+      String directoryName = preferences.pythonOutputDirectory().getValue();
+      outputDirectory = findOrCreateDirectory(directoryName, project);
+      outputDirectoryLocation = locationOf(outputDirectory);
+    }
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ConsolePrinter.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/core/ConsolePrinter.java
similarity index 96%
rename from com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ConsolePrinter.java
rename to com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/core/ConsolePrinter.java
index beacffd..8e684d1 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ConsolePrinter.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/core/ConsolePrinter.java
@@ -6,7 +6,7 @@
  *
  * http://www.eclipse.org/legal/epl-v10.html
  */
-package com.google.eclipse.protobuf.ui.builder.protoc;
+package com.google.eclipse.protobuf.ui.builder.protoc.core;
 
 import static com.google.common.io.Closeables.closeQuietly;
 import static com.google.eclipse.protobuf.ui.util.Workbenches.activeWorkbenchPage;
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/core/ProtobufBuildParticipant.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/core/ProtobufBuildParticipant.java
new file mode 100644
index 0000000..13ce5bb
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/core/ProtobufBuildParticipant.java
@@ -0,0 +1,138 @@
+/*
+ * 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.builder.protoc.core;
+
+import static com.google.common.io.Closeables.closeQuietly;
+import static com.google.eclipse.protobuf.ui.builder.protoc.core.ConsolePrinter.createAndDisplayConsole;
+import static com.google.eclipse.protobuf.ui.exception.CoreExceptions.error;
+import static com.google.eclipse.protobuf.ui.preferences.compiler.core.CompilerPreferences.compilerPreferences;
+import static org.eclipse.core.resources.IResource.DEPTH_INFINITE;
+
+import java.io.*;
+import java.util.List;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.xtext.builder.IXtextBuilderParticipant;
+import org.eclipse.xtext.resource.*;
+import org.eclipse.xtext.resource.IResourceDescription.Delta;
+import org.eclipse.xtext.ui.editor.preferences.IPreferenceStoreAccess;
+
+import com.google.eclipse.protobuf.ui.builder.protoc.command.ProtocCommandBuilder;
+import com.google.eclipse.protobuf.ui.builder.protoc.output.*;
+import com.google.eclipse.protobuf.ui.preferences.compiler.core.CompilerPreferences;
+import com.google.eclipse.protobuf.ui.preferences.paths.core.PathsPreferences;
+import com.google.inject.Inject;
+
+/**
+ * Calls protoc to generate Java, C++ or Python code from .proto files.
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class ProtobufBuildParticipant implements IXtextBuilderParticipant {
+  @Inject private ProtocOutputParser outputParser;
+  @Inject private IPreferenceStoreAccess storeAccess;
+
+  @Override public void build(IBuildContext context, IProgressMonitor monitor) throws CoreException {
+    List<Delta> deltas = context.getDeltas();
+    if (deltas.isEmpty()) {
+      return;
+    }
+    IProject project = context.getBuiltProject();
+    CompilerPreferences compilerPreferences = compilerPreferences(storeAccess, project);
+    if (!compilerPreferences.compileProtoFiles().getValue()) {
+      return;
+    }
+    PathsPreferences pathsPreferences = new PathsPreferences(storeAccess, project);
+    ProtocCommandBuilder commandBuilder = new ProtocCommandBuilder(compilerPreferences, pathsPreferences, project);
+    for (Delta d : deltas) {
+      IFile protoFile = protoFile(d.getNew(), project);
+      if (protoFile == null) {
+        continue;
+      }
+      generateSingleProto(commandBuilder.buildCommand(protoFile), protoFile);
+    }
+    if (compilerPreferences.refreshResources().getValue()) {
+      boolean refreshProject = compilerPreferences.refreshProject().getValue();
+      refresh(project, commandBuilder.outputDirectories(), refreshProject, monitor);
+    }
+  }
+
+  private IFile protoFile(IResourceDescription resource, IProject project) {
+    String path = filePathIfIsProtoFile(resource);
+    return (path == null) ? null : project.getWorkspace().getRoot().getFile(new Path(path));
+  }
+
+  private String filePathIfIsProtoFile(IResourceDescription resource) {
+    if (resource == null) {
+      return null;
+    }
+    URI uri = resource.getURI();
+    if (!uri.fileExtension().equals("proto"))
+    {
+      return null;
+    }
+    if (uri.scheme() == null) {
+      return uri.toFileString();
+    }
+    StringBuilder b = new StringBuilder();
+    int segmentCount = uri.segmentCount();
+    for (int i = 1; i < segmentCount; i++)
+     {
+      b.append("/").append(uri.segment(i));
+    }
+    return b.length() == 0 ? null : b.toString();
+  }
+
+  private void generateSingleProto(String command, IFile protoFile) throws CoreException {
+    ConsolePrinter console = null;
+    try {
+      console = createAndDisplayConsole();
+      console.printCommand(command);
+      Process process = Runtime.getRuntime().exec(command);
+      processStream(process.getErrorStream(), protoFile, console);
+      process.destroy();
+    } catch (Throwable e) {
+      e.printStackTrace();
+      throw error(e);
+    } finally {
+      if (console != null) {
+        console.close();
+      }
+    }
+  }
+
+  private void processStream(InputStream stream, IFile protoFile, ConsolePrinter console) throws Throwable {
+    InputStreamReader reader = null;
+    try {
+      reader = new InputStreamReader(stream);
+      BufferedReader bufferedReader = new BufferedReader(reader);
+      String line = null;
+      ProtocMarkerFactory markerFactory = new ProtocMarkerFactory(protoFile);
+      while ((line = bufferedReader.readLine()) != null) {
+        outputParser.parseAndAddMarkerIfNecessary(line, markerFactory);
+        console.printProtocOutput(line);
+      }
+    } finally {
+      closeQuietly(reader);
+    }
+  }
+
+  private void refresh(IProject project, List<IFolder> outputDirectories, boolean refreshProject,
+      IProgressMonitor monitor) throws CoreException {
+    if (refreshProject) {
+      project.refreshLocal(DEPTH_INFINITE, monitor);
+      return;
+    }
+    for (IFolder outputDirectory : outputDirectories) {
+      outputDirectory.refreshLocal(DEPTH_INFINITE, monitor);
+    }
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/CompositeOutputParser.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/output/CompositeOutputParser.java
similarity index 96%
rename from com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/CompositeOutputParser.java
rename to com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/output/CompositeOutputParser.java
index bd004e7..66f382c 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/CompositeOutputParser.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/output/CompositeOutputParser.java
@@ -6,7 +6,7 @@
  *
  * http://www.eclipse.org/legal/epl-v10.html
  */
-package com.google.eclipse.protobuf.ui.builder.protoc;
+package com.google.eclipse.protobuf.ui.builder.protoc.output;
 
 import static com.google.common.collect.Lists.newArrayList;
 
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtocMarkerFactory.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/output/ProtocMarkerFactory.java
similarity index 60%
rename from com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtocMarkerFactory.java
rename to com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/output/ProtocMarkerFactory.java
index 1969bc2..a447056 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/ProtocMarkerFactory.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/output/ProtocMarkerFactory.java
@@ -6,7 +6,7 @@
  *
  * http://www.eclipse.org/legal/epl-v10.html
  */
-package com.google.eclipse.protobuf.ui.builder.protoc;
+package com.google.eclipse.protobuf.ui.builder.protoc.output;
 
 import static com.google.eclipse.protobuf.ui.validation.ProtobufResourceUIValidatorExtension.EDITOR_CHECK;
 import static org.eclipse.core.resources.IMarker.*;
@@ -20,24 +20,31 @@
  *
  * @author alruiz@google.com (Alex Ruiz)
  */
-class ProtocMarkerFactory {
+public class ProtocMarkerFactory {
   private static final String PROTOC_CHECK = "com.google.eclipse.protobuf.ui.protocMarker";
 
-  private final IFile file;
+  private final IFile protoFile;
   private final IMarker[] markers;
 
-  ProtocMarkerFactory(IFile file) throws CoreException {
-    this.file = file;
-    file.deleteMarkers(PROTOC_CHECK, true, DEPTH_INFINITE);
-    markers = file.findMarkers(EDITOR_CHECK, true, DEPTH_INFINITE);
+  public ProtocMarkerFactory(IFile protoFile) throws CoreException {
+    this.protoFile = protoFile;
+    protoFile.deleteMarkers(PROTOC_CHECK, true, DEPTH_INFINITE);
+    markers = protoFile.findMarkers(EDITOR_CHECK, true, DEPTH_INFINITE);
   }
 
-  void createErrorIfNecessary(String fileName, int lineNumber, String message) throws CoreException {
-    String location = file.getLocation().toOSString();
+  /**
+   * Creates a new editor marker if the given file name matches the one in this factory.
+   * @param fileName the name of the proto file, obtained from protoc output.
+   * @param lineNumber the line number where to create the editor marker.
+   * @param message the message for the editor marker.
+   * @throws CoreException if something goes wrong.
+   */
+  public void createErrorIfNecessary(String fileName, int lineNumber, String message) throws CoreException {
+    String location = protoFile.getLocation().toOSString();
     if (!location.endsWith(fileName) || containsMarker(message, lineNumber)) {
       return;
     }
-    IMarker marker = file.createMarker(PROTOC_CHECK);
+    IMarker marker = protoFile.createMarker(PROTOC_CHECK);
     marker.setAttribute(SEVERITY, SEVERITY_ERROR);
     marker.setAttribute(MESSAGE, message);
     marker.setAttribute(LINE_NUMBER, lineNumber);
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/output/ProtocOutputParser.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/output/ProtocOutputParser.java
new file mode 100644
index 0000000..b58ec02
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/output/ProtocOutputParser.java
@@ -0,0 +1,30 @@
+/*
+ * 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.builder.protoc.output;
+
+import org.eclipse.core.runtime.CoreException;
+
+import com.google.inject.ImplementedBy;
+
+/**
+ * Parser of protoc output.
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+@ImplementedBy(CompositeOutputParser.class)
+public interface ProtocOutputParser {
+  /**
+   * Parses a single line of protoc output. It may create an editor marker.
+   * @param line the line to process.
+   * @param markerFactory the factory of editor markers.
+   * @return {@code true} if the given line was parsed, {@code false} otherwise.
+   * @throws CoreException if something wrong happens.
+   */
+  boolean parseAndAddMarkerIfNecessary(String line, ProtocMarkerFactory markerFactory) throws CoreException;
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/RegexOutputParser.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/output/RegexOutputParser.java
similarity index 96%
rename from com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/RegexOutputParser.java
rename to com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/output/RegexOutputParser.java
index ec35892..7e063e8 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/RegexOutputParser.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/builder/protoc/output/RegexOutputParser.java
@@ -6,7 +6,7 @@
  *
  * http://www.eclipse.org/legal/epl-v10.html
  */
-package com.google.eclipse.protobuf.ui.builder.protoc;
+package com.google.eclipse.protobuf.ui.builder.protoc.output;
 
 import static java.lang.Integer.parseInt;