/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * 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.skylark.io.impl;

import com.facebook.buck.core.filesystems.AbsPath;
import com.facebook.buck.core.filesystems.ForwardRelPath;
import com.facebook.buck.io.watchman.ProjectWatch;
import com.facebook.buck.io.watchman.Watchman;
import com.facebook.buck.io.watchman.WatchmanClient;
import com.facebook.buck.io.watchman.WatchmanQuery;
import com.facebook.buck.io.watchman.WatchmanQueryFailedException;
import com.facebook.buck.skylark.io.Globber;
import com.facebook.buck.skylark.io.GlobberFactory;
import com.facebook.buck.util.environment.Platform;
import com.facebook.buck.util.types.Either;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;

/** Provides instances of {@link com.facebook.buck.skylark.io.impl.HybridGlobber}. */
public class HybridGlobberFactory implements GlobberFactory {
  private static final long TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(10);
  private static final long WARN_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(1);

  private final WatchmanClient watchmanClient;
  private final AbsPath projectRoot;
  private final ImmutableMap<AbsPath, ProjectWatch> projectWatches;

  private HybridGlobberFactory(
      WatchmanClient watchmanClient,
      AbsPath projectRoot,
      ImmutableMap<AbsPath, ProjectWatch> projectWatches) {
    this.watchmanClient = watchmanClient;
    this.projectRoot = projectRoot;
    this.projectWatches = projectWatches;
  }

  /** Keeps relevant results of watch-project operation */
  private static class WatchProjectResult {
    private final String watchRoot;
    private final String relativePath;

    WatchProjectResult(String watchRoot, String relativePath) {
      this.watchRoot = watchRoot;
      this.relativePath = relativePath;
    }
  }

  /**
   * Ask Watchman to get the final path for a file, following links (and ReparsePoints on Windows).
   *
   * @param filePath the path for which we want to get the final path.
   * @return the inal path from Watchman for {@code filePath}, if Watchman is possible to calculate
   *     and {@code Optional.empty()} otherwise.
   * @throws IOException
   * @throws InterruptedException
   */
  public Either<WatchProjectResult, WatchmanClient.Timeout> getWatchmanRelativizedFinalPath(
      AbsPath filePath) throws IOException, InterruptedException, WatchmanQueryFailedException {
    return watchmanClient
        .queryWithTimeout(
            TIMEOUT_NANOS, WARN_TIMEOUT_NANOS, WatchmanQuery.watchProject(filePath.toString()))
        .mapLeft(
            result -> {
              String watchRoot = result.getWatch();
              String relativePath = result.getRelativePath();
              return new WatchProjectResult(
                  Objects.requireNonNull(watchRoot), Objects.requireNonNull(relativePath));
            });
  }

  @Override
  public AbsPath getRoot() {
    return projectRoot;
  }

  @Override
  public Globber create(ForwardRelPath basePathRel) {
    AbsPath basePath = projectRoot.resolve(basePathRel);
    AbsPath cellPath = projectRoot;
    String watchRoot = cellPath.toString();
    @Nullable ProjectWatch projectWatch = projectWatches.get(cellPath);
    if (projectWatch != null) {
      watchRoot = projectWatch.getWatchRoot();
    }
    String relativeRoot = null;
    try {
      relativeRoot = cellPath.relativize(basePath).toString();
    } catch (IllegalArgumentException e) {
      if (Platform.detect() == Platform.WINDOWS) {
        // It is possible that on Windows we have a base root that is going through a different
        // drive (ReparsePoint-ed path).
        // In such case the basePath and watchRoot could point to different drives. Relativization
        // on different drives results in an exception.
        // In such case, ask Watchman to relativize the ReparsePoint path to the project root.
        // If exception is thrown while getting the final path from Watchman or relativizing the
        // path, report the original exception.
        try {
          Either<WatchProjectResult, WatchmanClient.Timeout> watchmanRelativizedPaths =
              getWatchmanRelativizedFinalPath(basePath);
          if (watchmanRelativizedPaths.isLeft()) {
            WatchProjectResult result = watchmanRelativizedPaths.getLeft();
            watchRoot = result.watchRoot;
            relativeRoot = result.relativePath;
          }
        } catch (IOException | InterruptedException | WatchmanQueryFailedException e1) {
          throw e;
        }
      }
    }
    return new HybridGlobber(
        NativeGlobber.create(basePath),
        WatchmanGlobber.create(watchmanClient, relativeRoot, watchRoot));
  }

  @Override
  public void close() throws IOException {}

  public static HybridGlobberFactory using(Watchman watchman, AbsPath projectRoot) {
    return new HybridGlobberFactory(
        watchman.getPooledClient(), projectRoot, watchman.getProjectWatches());
  }
}

@@ -944,293 +944,78 @@
 uck.
-io.watchman.WatchmanQuery;%0Aimport com.facebook.buck.io.watchman.WatchmanQueryFailedException;%0Aimport com.facebook.buck.skylark.io.Globber;%0Aimport com.facebook.buck.skylark.io.GlobberFactory;%0Aimport com.facebook.buck.util.environment.Platform;%0Aimport com.facebook.buck.util.types.Either
+skylark.io.Globber;%0Aimport com.facebook.buck.skylark.io.GlobberFactory
 ;%0Aim
@@ -1091,72 +1091,8 @@
 on;%0A
-import java.util.Objects;%0Aimport java.util.concurrent.TimeUnit;%0A
 impo
@@ -1273,160 +1273,8 @@
 ry %7B
-%0A  private static final long TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(10);%0A  private static final long WARN_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(1);
 %0A%0A  
@@ -1709,1385 +1709,8 @@
  %7D%0A%0A
-  /** Keeps relevant results of watch-project operation */%0A  private static class WatchProjectResult %7B%0A    private final String watchRoot;%0A    private final String relativePath;%0A%0A    WatchProjectResult(String watchRoot, String relativePath) %7B%0A      this.watchRoot = watchRoot;%0A      this.relativePath = relativePath;%0A    %7D%0A  %7D%0A%0A  /**%0A   * Ask Watchman to get the final path for a file, following links (and ReparsePoints on Windows).%0A   *%0A   * @param filePath the path for which we want to get the final path.%0A   * @return the inal path from Watchman for %7B@code filePath%7D, if Watchman is possible to calculate%0A   *     and %7B@code Optional.empty()%7D otherwise.%0A   * @throws IOException%0A   * @throws InterruptedException%0A   */%0A  public Either%3CWatchProjectResult, WatchmanClient.Timeout%3E getWatchmanRelativizedFinalPath(%0A      AbsPath filePath) throws IOException, InterruptedException, WatchmanQueryFailedException %7B%0A    return watchmanClient%0A        .queryWithTimeout(%0A            TIMEOUT_NANOS, WARN_TIMEOUT_NANOS, WatchmanQuery.watchProject(filePath.toString()))%0A        .mapLeft(%0A            result -%3E %7B%0A              String watchRoot = result.getWatch();%0A              String relativePath = result.getRelativePath();%0A              return new WatchProjectResult(%0A                  Objects.requireNonNull(watchRoot), Objects.requireNonNull(relativePath));%0A            %7D);%0A  %7D%0A%0A
   @O
@@ -1841,24 +1841,30 @@
 hRel) %7B%0A    
+final 
 AbsPath base
@@ -1904,24 +1904,30 @@
 thRel);%0A    
+final 
 AbsPath cell
@@ -2177,1232 +2177,31 @@
 t = 
-null;%0A    try %7B%0A      relativeRoot = cellPath.relativize(basePath).toString();%0A    %7D catch (IllegalArgumentException e) %7B%0A      if (Platform.detect() == Platform.WINDOWS) %7B%0A        // It is possible that on Windows we have a base root that is going through a different%0A        // drive (ReparsePoint-ed path).%0A        // In such case the basePath and watchRoot could point to different drives. Relativization%0A        // on different drives results in an exception.%0A        // In such case, ask Watchman to relativize the ReparsePoint path to the project root.%0A        // If exception is thrown while getting the final path from Watchman or relativizing the%0A        // path, report the original exception.%0A        try %7B%0A          Either%3CWatchProjectResult, WatchmanClient.Timeout%3E watchmanRelativizedPaths =%0A              getWatchmanRelativizedFinalPath(basePath);%0A          if (watchmanRelativizedPaths.isLeft()) %7B%0A            WatchProjectResult result = watchmanRelativizedPaths.getLeft();%0A            watchRoot = result.watchRoot;%0A            relativeRoot = result.relativePath;%0A          %7D%0A        %7D catch (IOException %7C InterruptedException %7C WatchmanQueryFailedException e1) %7B%0A          throw e;%0A        %7D%0A      %7D%0A    %7D
+basePathRel.toString();
 %0A   

Remove windows-specific path relativization from HybridGlobberFactory