/*
 * Copyright 2014-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.fs;

import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.StepExecutionResult;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.nio.file.Path;

public class SymlinkTreeStep implements Step {

  private final ProjectFilesystem filesystem;
  private final Path root;
  private final ImmutableMap<Path, Path> links;

  public SymlinkTreeStep(ProjectFilesystem filesystem, Path root, ImmutableMap<Path, Path> links) {
    this.filesystem = filesystem;
    this.root = root;
    this.links = links;
  }

  @Override
  public String getDescription(ExecutionContext context) {
    return "link tree @ " + root.toString();
  }

  @Override
  public String getShortName() {
    return "link_tree";
  }

  @Override
  public StepExecutionResult execute(ExecutionContext context) {
    for (ImmutableMap.Entry<Path, Path> ent : links.entrySet()) {
      Path target = filesystem.resolve(ent.getValue());
      Path link = filesystem.resolve(root.resolve(ent.getKey()));
      try {
        filesystem.mkdirs(link.getParent());
        filesystem.createSymLink(link, target, true /* force */);
      } catch (IOException e) {
        String msg = String.format("failed creating linking \"%s\" -> \"%s\"", link, target);
        context.logError(e, msg);
        e.printStackTrace(context.getStdErr());
        return StepExecutionResult.ERROR;
      }
    }
    return StepExecutionResult.SUCCESS;
  }

  @Override
  public boolean equals(Object obj) {
    if (!(obj instanceof SymlinkTreeStep)) {
      return false;
    }
    SymlinkTreeStep that = (SymlinkTreeStep) obj;
    return Objects.equal(this.root, that.root) && Objects.equal(this.links, that.links);
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(root, links);
  }
}

@@ -818,16 +818,58 @@
 Result;%0A
+import com.facebook.buck.util.RichStream;%0A
 import c
@@ -899,16 +899,16 @@
 bjects;%0A
-
 import c
@@ -1625,24 +1625,413 @@
  context) %7B%0A
+    for (Path dir :%0A        RichStream.from(links.keySet())%0A            .map(root::resolve)%0A            .map(Path::getParent)%0A            .distinct()%0A            .toOnceIterable()) %7B%0A      try %7B%0A        filesystem.mkdirs(dir);%0A      %7D catch (IOException e) %7B%0A        String msg = String.format(%22failed creating dirs %5C%22%25s%5C%22%22, dir);%0A        throw new RuntimeException(msg, e);%0A      %7D%0A    %7D%0A
     for (Imm
@@ -2222,53 +2222,8 @@
 y %7B%0A
-        filesystem.mkdirs(link.getParent());%0A
     
@@ -2422,122 +2422,42 @@
     
-context.logError(e, msg);%0A        e.printStackTrace(context.getStdErr());%0A        return StepExecutionResult.ERROR
+throw new RuntimeException(msg, e)
 ;%0A  

Make SymlinkTreeStep only mkdirs(p) once for each p