Library for parsing IntelliJ with Bazel project views

This is the first step towards #44.

Change-Id: Ie1943a701eb42687046a7a2e7a797bc812b1c3da
diff --git a/BUILD b/BUILD
index 6142439..932e07a 100644
--- a/BUILD
+++ b/BUILD
@@ -18,6 +18,7 @@
     visibility = ["//visibility:public"],
     deps = [
         "//java/com/google/devtools/bazel/e4b/command",
+        "//java/com/google/devtools/bazel/e4b/projectviews",
         "@com_google_guava//jar",
     ],
 )
diff --git a/java/com/google/devtools/bazel/e4b/projectviews/BUILD b/java/com/google/devtools/bazel/e4b/projectviews/BUILD
new file mode 100644
index 0000000..710faf0
--- /dev/null
+++ b/java/com/google/devtools/bazel/e4b/projectviews/BUILD
@@ -0,0 +1,11 @@
+java_library(
+    name = "projectviews",
+    srcs = glob(["*.java"]),
+    visibility = [
+        "//:__pkg__",
+        "//javatests/com/google/devtools/bazel/e4b/projectviews:__pkg__",
+    ],
+    deps = [
+        "@com_google_guava//jar",
+    ],
+)
diff --git a/java/com/google/devtools/bazel/e4b/projectviews/Builder.java b/java/com/google/devtools/bazel/e4b/projectviews/Builder.java
new file mode 100644
index 0000000..9733eda
--- /dev/null
+++ b/java/com/google/devtools/bazel/e4b/projectviews/Builder.java
@@ -0,0 +1,155 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.bazel.e4b.projectviews;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.file.Files;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+/** A class to create a {@link ProjectView} */
+public class Builder {
+  private ImmutableList.Builder<String> buildFlags = ImmutableList.builder();
+  private ImmutableList.Builder<String> directories = ImmutableList.builder();
+  private ImmutableList.Builder<String> targets = ImmutableList.builder();
+  private int javaLanguageLevel = 0; // <= 0 means take it from java_toolchain.
+
+  Builder() {}
+
+  public Builder addBuildFlag(String... flag) {
+    buildFlags.add(flag);
+    return this;
+  }
+
+  public Builder addDirectory(String... dir) {
+    directories.add(dir);
+    return this;
+  }
+
+  public Builder addTarget(String... target) {
+    targets.add(target);
+    return this;
+  }
+
+  public Builder setJavaLanguageLevel(int level) {
+    Preconditions.checkArgument(level > 0, "Can only set java language level to a value > 0");
+    Preconditions.checkArgument(javaLanguageLevel == 0, "Java language level was already set");
+    javaLanguageLevel = level;
+    return this;
+  }
+
+  // State object using while parsing a stream
+  private String currentSection = null;
+
+  /**
+   * Parse the project view given in view, following also imports.
+   * 
+   * @throws IOException
+   * @throws ProjectViewParseException
+   */
+  public Builder parseView(File view) throws IOException, ProjectViewParseException {
+    int linenb = 0;
+    for (String line : Files.readAllLines(view.toPath())) {
+      linenb++;
+      parseLine(view.getPath(), view.getParentFile(), line, linenb);
+    }
+    currentSection = null;
+    return this;
+  }
+
+  /**
+   * Parse the project view at the given {@code url}.
+   * 
+   * @throws IOException
+   * @throws ProjectViewParseException
+   */
+  public Builder parseView(URL url) throws IOException, ProjectViewParseException {
+    int linenb = 0;
+    try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) {
+      for (String line : reader.lines().toArray(String[]::new)) {
+        linenb++;
+        parseLine(url.toString(), null, line, linenb);
+      }
+    }
+    currentSection = null;
+    return this;
+  }
+
+  private void parseLine(String fileName, File parentFile, String line, int linenb)
+      throws ProjectViewParseException, IOException {
+    if (line.isEmpty()) {
+      currentSection = null;
+    } else if (line.startsWith("  ")) {
+      if (currentSection == null) {
+        throw new ProjectViewParseException(
+            "Line " + linenb + " of project view " + fileName + " is not in a section");
+      }
+      if (currentSection.equals("directories")) {
+        directories.add(line.substring(2));
+      } else if (currentSection.equals("targets")) {
+        targets.add(line.substring(2));
+      } else if (currentSection.equals("build_flags")) {
+        buildFlags.add(line.substring(2));
+      } // else ignoring other sections
+    } else if (line.startsWith("import ")) {
+      // imports
+      String path = line.substring(7);
+      if (path.startsWith("/")) {
+        parseView(new File(path));
+      } else {
+        parseView(new File(parentFile, path));
+      }
+    } else if (line.contains(":")) {
+      // section declaration
+      line = line.trim();
+      if (line.endsWith(":")) {
+        currentSection = line.substring(0, line.length() - 1);
+        if (currentSection.equals("java_language_level")) {
+          throw new ProjectViewParseException("Line " + linenb + " of project view "
+              + fileName + ": java_language_level cannot be a section name");
+        }
+      } else {
+        int colonIndex = line.indexOf(':');
+        String label = line.substring(0, colonIndex).trim();
+        String value = line.substring(colonIndex + 1).trim();
+        if (label.equals("directories") || label.equals("import") || label.equals("targets")
+            || label.equals("build_flags")) {
+          throw new ProjectViewParseException("Line " + linenb + " of project view "
+              + fileName + ": " + label + " cannot be a label name");
+        }
+        if (label.equals("java_language_level")) {
+          if (!value.matches("^[0-9]+$")) {
+            throw new ProjectViewParseException("Line " + linenb + " of project view "
+                + fileName + ": java_language_level should be an integer.");
+          }
+          javaLanguageLevel = Integer.parseInt(value);
+        }
+      }
+    } else if (!line.trim().startsWith("#")) {
+      throw new ProjectViewParseException(
+          "Project view " + fileName + " contains a syntax error at line " + linenb);
+    }
+  }
+
+  public ProjectView build() {
+    return new ProjectViewImpl(directories.build(), targets.build(), javaLanguageLevel,
+        buildFlags.build());
+  }
+}
diff --git a/java/com/google/devtools/bazel/e4b/projectviews/ProjectView.java b/java/com/google/devtools/bazel/e4b/projectviews/ProjectView.java
new file mode 100644
index 0000000..8b0cf77
--- /dev/null
+++ b/java/com/google/devtools/bazel/e4b/projectviews/ProjectView.java
@@ -0,0 +1,55 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.bazel.e4b.projectviews;
+
+import java.util.List;
+
+/**
+ * This is an interface to defined a project view from the IntelliJ plugin for Bazel
+ * (https://ij.bazel.build) so that project view can be shared between IntelliJ and Eclipse users.
+ *
+ * <p>
+ * See http://ij.bazel.build/docs/project-views.html for the specification of the project view. This
+ * project view support only a subset relevant for the Eclipse plugin.
+ */
+public interface ProjectView {
+  /**
+   * List of directories defined in the {@code directories} section of the project view. These are
+   * the directories to include as source directories.
+   */
+  public List<String> getDirectories();
+
+  /**
+   * List of targets to build defined in the {@code targets} section of the project view.
+   */
+  public List<String> getTargets();
+
+  /**
+   * Return a number (e.g. 7) giving the java version that the IDE should support (section
+   * {@code java_language_level} of the project view).
+   */
+  public int getJavaLanguageLevel();
+
+  /**
+   * List of build flags to pass to Bazel defined in the {@code build_flags} section of the project
+   * view.
+   */
+  public List<String> getBuildFlags();
+
+  /** Returns a builder to construct an project view object. */
+  public static Builder builder() {
+    return new Builder();
+  }
+}
diff --git a/java/com/google/devtools/bazel/e4b/projectviews/ProjectViewImpl.java b/java/com/google/devtools/bazel/e4b/projectviews/ProjectViewImpl.java
new file mode 100644
index 0000000..fc5ad25
--- /dev/null
+++ b/java/com/google/devtools/bazel/e4b/projectviews/ProjectViewImpl.java
@@ -0,0 +1,77 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.bazel.e4b.projectviews;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+
+/** Implementation of the ProjectView object */
+final class ProjectViewImpl implements ProjectView {
+
+  private final ImmutableList<String> directories;
+  private final ImmutableList<String> targets;
+  private final int javaLanguageLevel;
+  private final ImmutableList<String> buildFlags;
+
+  public ProjectViewImpl(List<String> directories, List<String> targets, int javaLanguageLevel,
+      List<String> buildFlags) {
+    this.directories = ImmutableList.copyOf(directories);
+    this.targets = ImmutableList.copyOf(targets);
+    this.javaLanguageLevel = javaLanguageLevel;
+    this.buildFlags = ImmutableList.copyOf(buildFlags);
+  }
+
+  @Override
+  public List<String> getDirectories() {
+    return directories;
+  }
+
+  @Override
+  public List<String> getTargets() {
+    return targets;
+  }
+
+  @Override
+  public int getJavaLanguageLevel() {
+    return javaLanguageLevel;
+  }
+
+  @Override
+  public List<String> getBuildFlags() {
+    return buildFlags;
+  }
+
+  @Override
+  public String toString() {
+    StringBuffer result = new StringBuffer();
+    addList(result, directories, "directories");
+    addList(result, targets, "targets");
+    if (javaLanguageLevel > 0) {
+      result.append("java_language_level: ").append(javaLanguageLevel).append("\n\n");
+    }
+    addList(result, buildFlags, "build_flags");
+    return result.toString();
+  }
+
+  private static void addList(StringBuffer result, List<String> list, String header) {
+    if (!list.isEmpty()) {
+      result.append(header).append(":\n");
+      for (String el : list) {
+        result.append("  ").append(el).append("\n");
+      }
+      result.append("\n");
+    }
+  }
+}
diff --git a/java/com/google/devtools/bazel/e4b/projectviews/ProjectViewParseException.java b/java/com/google/devtools/bazel/e4b/projectviews/ProjectViewParseException.java
new file mode 100644
index 0000000..a3f663d
--- /dev/null
+++ b/java/com/google/devtools/bazel/e4b/projectviews/ProjectViewParseException.java
@@ -0,0 +1,26 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.bazel.e4b.projectviews;
+
+/**
+ * An exception thrown when failing to parse a project view.
+ */
+public class ProjectViewParseException extends Exception {
+  private static final long serialVersionUID = 1L;
+
+  ProjectViewParseException(String msg) {
+    super(msg);
+  }
+}
diff --git a/javatests/com/google/devtools/bazel/e4b/projectviews/BUILD b/javatests/com/google/devtools/bazel/e4b/projectviews/BUILD
new file mode 100644
index 0000000..84b2aac
--- /dev/null
+++ b/javatests/com/google/devtools/bazel/e4b/projectviews/BUILD
@@ -0,0 +1,11 @@
+java_test(
+    name = "ProjectViewTest",
+    srcs = ["ProjectViewTest.java"],
+    resources = ["bazel.projectview"],
+    deps = [
+        "//java/com/google/devtools/bazel/e4b/projectviews",
+        "@com_google_truth//jar",
+        "@org_hamcrest_core//jar",
+        "@org_junit//jar",
+    ],
+)
diff --git a/javatests/com/google/devtools/bazel/e4b/projectviews/ProjectViewTest.java b/javatests/com/google/devtools/bazel/e4b/projectviews/ProjectViewTest.java
new file mode 100644
index 0000000..1d4b3a5
--- /dev/null
+++ b/javatests/com/google/devtools/bazel/e4b/projectviews/ProjectViewTest.java
@@ -0,0 +1,37 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.bazel.e4b.projectviews;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import org.junit.Test;
+
+/** {@link ProjectView}Test */
+public class ProjectViewTest {
+
+  @Test
+  public void testBazelView() throws IOException, ProjectViewParseException, URISyntaxException {
+    ProjectView view = ProjectView.builder()
+        .parseView(ProjectViewTest.class.getResource("bazel.projectview")).build();
+    assertThat(view.getBuildFlags()).isEmpty();
+    assertThat(view.getDirectories()).containsExactly(".");
+    assertThat(view.getJavaLanguageLevel()).isEqualTo(0);
+    assertThat(view.getTargets()).containsAllOf("//src:bazel", "//src/test/...");
+  }
+
+}
diff --git a/javatests/com/google/devtools/bazel/e4b/projectviews/bazel.projectview b/javatests/com/google/devtools/bazel/e4b/projectviews/bazel.projectview
new file mode 100644
index 0000000..9b1c8bf
--- /dev/null
+++ b/javatests/com/google/devtools/bazel/e4b/projectviews/bazel.projectview
@@ -0,0 +1,22 @@
+# Setup IntelliJ for Bazel development using the IntelliJ Bazel Plugin.
+# From github.com/bazelbuild/bazel's scripts/ij.projectview at commit 815bd634df0350a227133244c734b5c3672776ab.
+directories:
+  .
+
+test_sources:
+  src/java_tools/buildjar/javatests/*
+  src/java_tools/junitrunner/javatests/*
+  src/java_tools/singlejar/javatests/*
+  src/test/*
+
+targets:
+  //src:bazel
+  //src/java_tools/buildjar:JavaBuilder
+  //src/java_tools/buildjar:VanillaJavaBuilder
+  //src/java_tools/buildjar/javatests/...
+  //src/java_tools/junitrunner/java/com/google/testing/junit/runner:Runner
+  //src/java_tools/junitrunner/javatests/...
+  //src/java_tools/singlejar:SingleJar
+  //src/java_tools/singlejar:tests
+  //src/test/...
+  //src/tools/remote_worker
\ No newline at end of file