/*
 * LanguageTool, a natural language style checker
 * Copyright (C) 2021 Stefan Viol (https://stevio.de)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 */

package org.languagetool.tools;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;

import java.util.Optional;
import java.util.concurrent.*;

import static java.util.concurrent.TimeUnit.SECONDS;

@Slf4j
public final class LtThreadPoolFactory {

  private static final ConcurrentMap<String, ThreadPoolExecutor> executorServices = new ConcurrentHashMap<>();

  private LtThreadPoolFactory() {
  }

  /**
   * @param identifier       Name of the thread-pool, will be used as name of the threads in the threadPool
   * @param maxThreads       Number of parallel threads running in this pool
   * @param maxTaskInQueue   Number of maximum Task in the pool queue
   * @param isDaemon         Run the threads as daemon threads
   * @param exceptionHandler Handler for exceptions in Thread
   * @return a Fixed ThreadPoolExecutor
   */
  private static ThreadPoolExecutor getOrCreateFixedThreadPoolExecutor(@NotNull String identifier, @NotNull int maxThreads, @NotNull int maxTaskInQueue, @NotNull boolean isDaemon, @NotNull Thread.UncaughtExceptionHandler exceptionHandler) {
    if (executorServices.containsKey(identifier)) {
      log.info("ThreadPool with identifier: " + identifier + " already exists. Return this one");
      return executorServices.get(identifier);
    }
    log.info(String.format("Create new threadPool with maxThreads: %d maxTaskInQueue: %d identifier: %s daemon: %s exceptionHandler: %s", maxThreads, maxTaskInQueue, identifier, isDaemon, exceptionHandler));
    ArrayBlockingQueue<Runnable> boundedQueue = new ArrayBlockingQueue<>(maxTaskInQueue);
    ThreadFactory threadFactory = new ThreadFactoryBuilder()
      .setNameFormat(identifier + "-%d")
      .setDaemon(isDaemon)
      .setUncaughtExceptionHandler(exceptionHandler)
      .build();
    ThreadPoolExecutor newThreadPoolExecutor = new ThreadPoolExecutor(maxThreads / 2, maxThreads, 60, SECONDS, boundedQueue, threadFactory, new ThreadPoolExecutor.AbortPolicy());
    executorServices.put(identifier, newThreadPoolExecutor);
    return newThreadPoolExecutor;
  }

  public static ThreadPoolExecutor createFixedThreadPoolExecutor(@NotNull String identifier, @NotNull int maxThreads, @NotNull int maxTaskInQueue, @NotNull boolean isDaemon, @NotNull Thread.UncaughtExceptionHandler exceptionHandler, boolean reuse) {
    if (reuse) {
      return getOrCreateFixedThreadPoolExecutor(identifier, maxThreads, maxTaskInQueue, isDaemon, exceptionHandler);
    }
    log.info(String.format("Create new threadPool with maxThreads: %d maxTaskInQueue: %d identifier: %s daemon: %s exceptionHandler: %s", maxThreads, maxTaskInQueue, identifier, isDaemon, exceptionHandler));
    ArrayBlockingQueue<Runnable> boundedQueue = new ArrayBlockingQueue<>(maxTaskInQueue);
    ThreadFactory threadFactory = new ThreadFactoryBuilder()
      .setNameFormat(identifier + "-%d")
      .setDaemon(isDaemon)
      .setUncaughtExceptionHandler(exceptionHandler)
      .build();
    ThreadPoolExecutor newThreadPoolExecutor = new ThreadPoolExecutor(maxThreads / 2, maxThreads, 60, SECONDS, boundedQueue, threadFactory, new ThreadPoolExecutor.AbortPolicy());
    return newThreadPoolExecutor;
  }

  /**
   * @param identifier Name of an already created tread-pool
   * @return An optional of ThreadPoolExecutor (Null or Object)
   */
  public static Optional<ThreadPoolExecutor> getFixedThreadPoolExecutor(@NotNull String identifier) {
    log.info("Request: " + identifier + " ThreadPoolExecutor");
    return Optional.ofNullable(executorServices.get(identifier));
  }
}

@@ -1719,16 +1719,106 @@
  Thread%0A
+   * @param reuse            True if thread-pool should be reused                        %0A
    * @re
@@ -1854,30 +1854,29 @@
 or%0A   */%0A  p
-rivate
+ublic
  static Thre
@@ -1890,22 +1890,17 @@
 xecutor 
-getOrC
+c
 reateFix
@@ -2086,16 +2086,40 @@
 nHandler
+, @NotNull boolean reuse
 ) %7B%0A    
@@ -2122,16 +2122,25 @@
     if (
+reuse && 
 executor
@@ -3009,1168 +3009,90 @@
     
-executorServices.put(identifier, newThreadPoolExecutor);%0A    return newThreadPoolExecutor;%0A  %7D%0A%0A  public static ThreadPoolExecutor createFixedThreadPoolExecutor(@NotNull String identifier, @NotNull int maxThreads, @NotNull int maxTaskInQueue, @NotNull boolean isDaemon, @NotNull Thread.UncaughtExceptionHandler exceptionHandler, boolean reuse) %7B%0A    if (reuse) %7B%0A      return getOrCreateFixedThreadPoolExecutor(identifier, maxThreads, maxTaskInQueue, isDaemon, exceptionHandler);%0A    %7D%0A    log.info(String.format(%22Create new threadPool with maxThreads: %25d maxTaskInQueue: %25d identifier: %25s daemon: %25s exceptionHandler: %25s%22, maxThreads, maxTaskInQueue, identifier, isDaemon, exceptionHandler));%0A    ArrayBlockingQueue%3CRunnable%3E boundedQueue = new ArrayBlockingQueue%3C%3E(maxTaskInQueue);%0A    ThreadFactory threadFactory = new ThreadFactoryBuilder()%0A      .setNameFormat(identifier + %22-%25d%22)%0A      .setDaemon(isDaemon)%0A      .setUncaughtExceptionHandler(exceptionHandler)%0A      .build();%0A    ThreadPoolExecutor newThreadPoolExecutor = new ThreadPoolExecutor(maxThreads / 2, maxThreads, 60, SECONDS, boundedQueue, threadFactory, new ThreadPoolExecutor.AbortPolicy());
+if (reuse) %7B%0A      executorServices.put(identifier, newThreadPoolExecutor);%0A%0A    %7D
 %0A   

remove duplicate code in LtThreadPoolFactory