Introduce ConditionalStep.

Summary: This is needed for the upcoming predex work.

Test Plan: Sandcastle builds.
diff --git a/src/com/facebook/buck/step/ConditionalStep.java b/src/com/facebook/buck/step/ConditionalStep.java
new file mode 100644
index 0000000..ce35b4c
--- /dev/null
+++ b/src/com/facebook/buck/step/ConditionalStep.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013-present Facebook, Inc.
+ *
+ * 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.facebook.buck.step;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
+
+/** {@link Step} that is run conditionally based on {@code Supplier<Boolean> shouldRunStep}. */
+public class ConditionalStep implements Step {
+
+  private final Supplier<Boolean> shouldRunStep;
+  private final Step step;
+
+  public ConditionalStep(Supplier<Boolean> shouldRunStep, Step step) {
+    this.shouldRunStep = Preconditions.checkNotNull(shouldRunStep);
+    this.step = Preconditions.checkNotNull(step);
+  }
+
+  @Override
+  public int execute(ExecutionContext context) {
+    if (shouldRunStep.get()) {
+      return step.execute(context);
+    } else {
+      return 0;
+    }
+  }
+
+  @Override
+  public String getShortName() {
+    // Use the short name of the underlying Step because StepEvent.getCategory() uses the short
+    // name to group similar step types together so they can be audited for time spent.
+    return step.getShortName();
+  }
+
+  @Override
+  public String getDescription(ExecutionContext context) {
+    return "conditionally: " + step.getDescription(context);
+  }
+}
diff --git a/test/com/facebook/buck/step/ConditionalStepTest.java b/test/com/facebook/buck/step/ConditionalStepTest.java
new file mode 100644
index 0000000..5b2407b
--- /dev/null
+++ b/test/com/facebook/buck/step/ConditionalStepTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2013-present Facebook, Inc.
+ *
+ * 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.facebook.buck.step;
+
+import static org.junit.Assert.assertEquals;
+
+import com.facebook.buck.util.TriState;
+import com.google.common.base.Supplier;
+
+import org.easymock.EasyMockSupport;
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class ConditionalStepTest extends EasyMockSupport {
+
+  @Test
+  public void testExecuteConditionalStepWhenTrue() {
+    // Create a Supplier<Boolean> that returns a value once an external condition has been
+    // satisfied.
+    final AtomicReference<TriState> condition = new AtomicReference<>(
+        TriState.UNSPECIFIED);
+    Supplier<Boolean> conditional = new Supplier<Boolean>() {
+      @Override
+      public Boolean get() {
+        if (!condition.get().isSet()) {
+          throw new IllegalStateException("condition must be satisfied before this is queried.");
+        } else {
+          return condition.get().asBoolean();
+        }
+      }
+    };
+
+    // Create a step to run when the Supplier<Boolean> is true. Exit code is always 37.
+    final AtomicInteger numCalls = new AtomicInteger(0);
+    Step stepToRunWhenSupplierIsTrue = new AbstractExecutionStep("inc") {
+      @Override
+      public int execute(ExecutionContext context) {
+        numCalls.incrementAndGet();
+        return 37;
+      }
+    };
+
+    // Create and execute the ConditionalStep.
+    ConditionalStep conditionalStep = new ConditionalStep(conditional, stepToRunWhenSupplierIsTrue);
+    condition.set(TriState.TRUE);
+    ExecutionContext context = TestExecutionContext.newInstance();
+    int exitCode = conditionalStep.execute(context);
+    assertEquals("stepToRunWhenSupplierIsTrue should have been run once.", 1, numCalls.get());
+    assertEquals("The exit code of stepToRunWhenSupplierIsTrue should be passed through.",
+        37,
+        exitCode);
+    assertEquals("inc", conditionalStep.getShortName());
+    assertEquals("conditionally: inc", conditionalStep.getDescription(context));
+  }
+
+  @Test
+  public void testExecuteConditionalStepWhenFalse() {
+    // Create a Supplier<Boolean> that returns a value once an external condition has been
+    // satisfied.
+    final AtomicReference<TriState> condition = new AtomicReference<>(
+        TriState.UNSPECIFIED);
+    Supplier<Boolean> conditional = new Supplier<Boolean>() {
+      @Override
+      public Boolean get() {
+        if (!condition.get().isSet()) {
+          throw new IllegalStateException("condition must be satisfied before this is queried.");
+        } else {
+          return condition.get().asBoolean();
+        }
+      }
+    };
+
+    // Create a step to run when the Supplier<Boolean> is true. Because the Supplier<Boolean> will
+    // be false, this step should not be run.
+    final AtomicInteger numCalls = new AtomicInteger(0);
+    Step stepToRunWhenSupplierIsTrue = new AbstractExecutionStep("inc") {
+      @Override
+      public int execute(ExecutionContext context) {
+        numCalls.incrementAndGet();
+        throw new IllegalStateException("This step should not be executed.");
+      }
+    };
+
+    // Create and execute the ConditionalStep.
+    ConditionalStep conditionalStep = new ConditionalStep(conditional, stepToRunWhenSupplierIsTrue);
+    condition.set(TriState.FALSE);
+    ExecutionContext context = TestExecutionContext.newInstance();
+    int exitCode = conditionalStep.execute(context);
+    assertEquals("stepToRunWhenSupplierIsTrue should not have been run.", 0, numCalls.get());
+    assertEquals("The exit code should be zero when the conditional is false.", 0, exitCode);
+    assertEquals("inc", conditionalStep.getShortName());
+    assertEquals("conditionally: inc", conditionalStep.getDescription(context));
+  }
+}