/*
 * 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.event.BuckEventBus;
import com.facebook.buck.log.Logger;
import com.facebook.buck.parser.api.BuildFileManifest;
import com.facebook.buck.parser.api.ProjectBuildFileParser;
import com.facebook.buck.parser.api.Syntax;
import com.facebook.buck.util.concurrent.ResourcePool;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.concurrent.GuardedBy;

/**
 * Allows multiple concurrently executing futures to share a constrained number of parsers.
 *
 * <p>Parser instances are lazily created up till a fixed maximum. If more than max parser are
 * requested the associated 'requests' are queued up. As soon as a parser is returned it will be
 * used to satisfy the first pending request, otherwise it is "parked".
 */
class ProjectBuildFileParserPool implements AutoCloseable {
  private static final Logger LOG = Logger.get(ProjectBuildFileParserPool.class);

  private final int maxParsersPerCell;

  @GuardedBy("this")
  private final Map<Cell, ResourcePool<ProjectBuildFileParser>> parserResourcePools;

  @GuardedBy("this")
  private final Map<Cell, ProjectBuildFileParser> nonPooledCells;

  private final ProjectBuildFileParserFactory projectBuildFileParserFactory;
  private final AtomicBoolean closing;
  private final boolean enableProfiler;

  /** @param maxParsersPerCell maximum number of parsers to create for a single cell. */
  public ProjectBuildFileParserPool(
      int maxParsersPerCell,
      ProjectBuildFileParserFactory projectBuildFileParserFactory,
      boolean enableProfiler) {
    Preconditions.checkArgument(maxParsersPerCell > 0);

    this.maxParsersPerCell = maxParsersPerCell;
    this.parserResourcePools = new HashMap<>();
    this.nonPooledCells = new HashMap<>();
    this.projectBuildFileParserFactory = projectBuildFileParserFactory;
    this.closing = new AtomicBoolean(false);
    this.enableProfiler = enableProfiler;
  }

  /**
   * @param cell the cell in which we're parsing
   * @param buildFile the file to parse
   * @param executorService where to perform the parsing.
   * @return a {@link ListenableFuture} containing the result of the parsing. The future will be
   *     cancelled if the {@link ProjectBuildFileParserPool#close()} method is called.
   */
  public ListenableFuture<BuildFileManifest> getBuildFileManifest(
      BuckEventBus buckEventBus,
      Cell cell,
      Path buildFile,
      AtomicLong processedBytes,
      ListeningExecutorService executorService) {
    Preconditions.checkState(!closing.get());

    if (shouldUsePoolForCell(cell)) {
      return getResourcePoolForCell(buckEventBus, cell)
          .scheduleOperationWithResource(
              parser -> parser.getBuildFileManifest(buildFile, processedBytes), executorService);
    }
    ProjectBuildFileParser parser = getParserForCell(buckEventBus, cell);
    return executorService.submit(() -> parser.getBuildFileManifest(buildFile, processedBytes));
  }

  private synchronized ResourcePool<ProjectBuildFileParser> getResourcePoolForCell(
      BuckEventBus buckEventBus, Cell cell) {
    return parserResourcePools.computeIfAbsent(
        cell,
        c ->
            new ResourcePool<>(
                maxParsersPerCell,
                // If the Python process garbles the output stream then the bser codec doesn't
                // always
                // recover and subsequent attempts at invoking the parser will fail.
                ResourcePool.ResourceUsageErrorPolicy.RETIRE,
                () -> projectBuildFileParserFactory.createBuildFileParser(buckEventBus, c)));
  }

  private synchronized ProjectBuildFileParser getParserForCell(
      BuckEventBus buckEventBus, Cell cell) {
    return nonPooledCells.computeIfAbsent(
        cell, c -> projectBuildFileParserFactory.createBuildFileParser(buckEventBus, c));
  }

  private void reportProfile() {
    if (!enableProfiler) {
      return;
    }
    synchronized (this) {
      parserResourcePools
          .values()
          .forEach(
              resourcePool -> {
                resourcePool.callOnEachResource(
                    parser -> {
                      try {
                        parser.reportProfile();
                      } catch (IOException exception) {
                        LOG.debug(
                            exception,
                            "Exception raised during reportProfile() and we're ignoring it");
                      }
                    });
              });
    }
  }

  @Override
  public void close() {
    reportProfile();
    ImmutableSet<ResourcePool<ProjectBuildFileParser>> resourcePools;
    synchronized (this) {
      Preconditions.checkState(!closing.get());
      closing.set(true);
      resourcePools = ImmutableSet.copyOf(parserResourcePools.values());
    }
    resourcePools.forEach(ResourcePool::close);
  }

  /**
   * This is a temporary work-around for not creating many instances of the Skylark parser, since
   * they are completely unnecessary and do not improve performance. This does not really belong
   * here and may potentially be made finer grained (per file).
   *
   * <p>TODO(buckteam): remove once Python DSL parser is gone.
   */
  private boolean shouldUsePoolForCell(Cell cell) {
    ParserConfig parserConfig = cell.getBuckConfig().getView(ParserConfig.class);
    // the only interpreter that benefits from pooling is Python DSL parser, which is used only in
    // polyglot mode or if default syntax is set to Python DSL
    return parserConfig.isPolyglotParsingEnabled()
        || parserConfig.getDefaultBuildFileSyntax() == Syntax.PYTHON_DSL;
  }
}

@@ -4980,22 +4980,21 @@
 %7B%0A      
-parser
+for (
 Resource
@@ -5001,79 +5001,77 @@
 Pool
-s%0A          .values()%0A          .forEach(%0A              resourcePool -%3E
+%3CProjectBuildFileParser%3E resourcePool : parserResourcePools.values())
  %7B%0A 
@@ -5077,24 +5077,16 @@
         
-  
-      
 resource
@@ -5106,32 +5106,24 @@
 chResource(%0A
-        
             
@@ -5148,24 +5148,16 @@
         
-        
 try %7B%0A  
@@ -5166,32 +5166,24 @@
             
-     
-   
 parser.repor
@@ -5194,24 +5194,16 @@
 file();%0A
-        
         
@@ -5258,24 +5258,16 @@
         
-        
 LOG.debu
@@ -5269,24 +5269,16 @@
 .debug(%0A
-        
         
@@ -5303,36 +5303,8 @@
 ion,
-%0A                           
  %22Ex
@@ -5384,23 +5384,10 @@
     
-        %7D%0A     
+%7D%0A
     
@@ -5386,35 +5386,32 @@
   %7D%0A            
-   
 %7D);%0A            
@@ -5400,35 +5400,25 @@
   %7D);%0A      
-    
-    %7D);
+%7D
 %0A    %7D%0A  %7D%0A%0A

Replace stream forEach with a regular for loop.