/*
 * Copyright 2016-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.parser;

import com.facebook.buck.core.cell.Cell;
import com.facebook.buck.core.exceptions.HumanReadableException;
import com.facebook.buck.core.model.BuildTarget;
import com.facebook.buck.core.model.UnflavoredBuildTarget;
import com.facebook.buck.core.model.impl.ImmutableUnflavoredBuildTarget;
import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.io.file.MorePaths;
import com.facebook.buck.io.watchman.Watchman;
import com.facebook.buck.parser.PipelineNodeCache.Cache;
import com.facebook.buck.parser.exceptions.BuildTargetException;
import com.facebook.buck.parser.exceptions.NoSuchBuildTargetException;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;

public class RawNodeParsePipeline extends ParsePipeline<Map<String, Object>> {

  private final BuckEventBus eventBus;
  private final PipelineNodeCache<Path, ImmutableSet<Map<String, Object>>> cache;
  private final ListeningExecutorService executorService;
  private final ProjectBuildFileParserPool projectBuildFileParserPool;
  private final Watchman watchman;

  public RawNodeParsePipeline(
      Cache<Path, ImmutableSet<Map<String, Object>>> cache,
      ProjectBuildFileParserPool projectBuildFileParserPool,
      ListeningExecutorService executorService,
      BuckEventBus eventBus,
      Watchman watchman) {
    this.eventBus = eventBus;
    this.executorService = executorService;
    this.cache = new PipelineNodeCache<>(cache);
    this.projectBuildFileParserPool = projectBuildFileParserPool;
    this.watchman = watchman;
  }

  /**
   * @param cellRoot root path to the cell the rule is defined in.
   * @param map the map of values that define the rule.
   * @param rulePathForDebug path to the build file the rule is defined in, only used for debugging.
   * @return the build target defined by the rule.
   */
  public static UnflavoredBuildTarget parseBuildTargetFromRawRule(
      Path cellRoot, Optional<String> cellName, Map<String, Object> map, Path rulePathForDebug) {
    String basePath = (String) map.get("buck.base_path");
    String name = (String) map.get("name");
    if (basePath == null || name == null) {
      throw new IllegalStateException(
          String.format(
              "Attempting to parse build target from malformed raw data in %s: %s.",
              rulePathForDebug, Joiner.on(",").withKeyValueSeparator("->").join(map)));
    }
    Path otherBasePath = cellRoot.relativize(MorePaths.getParentOrEmpty(rulePathForDebug));
    if (!otherBasePath.equals(otherBasePath.getFileSystem().getPath(basePath))) {
      throw new IllegalStateException(
          String.format(
              "Raw data claims to come from [%s], but we tried rooting it at [%s].",
              basePath, otherBasePath));
    }
    return ImmutableUnflavoredBuildTarget.builder()
        .setBaseName(UnflavoredBuildTarget.BUILD_TARGET_PREFIX + basePath)
        .setShortName(name)
        .setCellPath(cellRoot)
        .setCell(cellName)
        .build();
  }

  @Override
  public ListenableFuture<ImmutableSet<Map<String, Object>>> getAllNodesJob(
      Cell cell, Path buildFile) throws BuildTargetException {

    if (shuttingDown()) {
      return Futures.immediateCancelledFuture();
    }

    return cache.getJobWithCacheLookup(
        cell,
        buildFile,
        () -> {
          if (shuttingDown()) {
            return Futures.immediateCancelledFuture();
          }

          return Futures.transform(
              projectBuildFileParserPool.getBuildFileManifest(
                  eventBus, cell, watchman, buildFile, executorService),
              buildFileManifest -> buildFileManifest.toRawNodes(),
              executorService);
        },
        eventBus);
  }

  @Override
  public ListenableFuture<Map<String, Object>> getNodeJob(Cell cell, BuildTarget buildTarget)
      throws BuildTargetException {
    return Futures.transformAsync(
        getAllNodesJob(cell, cell.getAbsolutePathToBuildFile(buildTarget)),
        input -> {
          Path pathToCheck = buildTarget.getBasePath();
          if (cell.getFilesystem().isIgnored(pathToCheck)) {
            throw new HumanReadableException(
                "Content of '%s' cannot be built because it is defined in an ignored directory.",
                pathToCheck);
          }

          for (Map<String, Object> rawNode : input) {
            Object shortName = rawNode.get("name");
            if (buildTarget.getShortName().equals(shortName)) {
              return Futures.immediateFuture(rawNode);
            }
          }
          throw NoSuchBuildTargetException.createForMissingBuildRule(
              buildTarget, cell.getAbsolutePathToBuildFile(buildTarget));
        },
        executorService);
  }

  @Override
  public void close() {
    super.close();
    projectBuildFileParserPool.close();
  }
}

@@ -1586,16 +1586,50 @@
 ptional;
+%0Aimport javax.annotation.Nullable;
 %0A%0Apublic
@@ -2920,24 +2920,34 @@
 ebug) %7B%0A    
+@Nullable 
 String baseP
@@ -2988,24 +2988,34 @@
 path%22);%0A    
+@Nullable 
 String name 

Add missing @Nullable annotations.