<commit_before>/*
 * (c) 2016 <NAME>
 */
package com.github.mike10004.xvfbmanager;

import com.github.mike10004.nativehelper.Program;
import com.github.mike10004.nativehelper.ProgramResult;
import com.github.mike10004.nativehelper.ProgramWithOutputFiles;
import com.github.mike10004.nativehelper.ProgramWithOutputFilesResult;
import com.github.mike10004.nativehelper.ProgramWithOutputResult;
import com.github.mike10004.xvfbmanager.Poller.StopReason;
import com.github.mike10004.xvfbmanager.Poller.PollOutcome;
import com.github.mike10004.xvfbmanager.DefaultXvfbController.Screenshooter;
import com.google.common.base.Optional;
import com.google.common.collect.Iterables;
import com.google.common.io.CharSource;
import com.google.common.io.Files;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.novetta.ibg.common.sys.Whicher;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

/**
 * Class that helps manage the creation of virtual framebuffer processes.
 */
public class XvfbManager {

    private static final Logger log = LoggerFactory.getLogger(XvfbManager.class);
    private static final int SCREEN = 0;

    private final File xvfbExecutable;
    private final XvfbConfig xvfbConfig;

    /**
     * Constructs a default instance of the class.
     * @throws IOException if Xvfb executable cannot be resolved
     * @see #resolveXvfbExecutable()
     */
    public XvfbManager() throws IOException {
        this(resolveXvfbExecutable(), XvfbConfig.DEFAULT);
    }

    /**
     * Constructs an instance of the class with a given configuration.
     * @param xvfbConfig the configuration
     * @throws IOException if Xvfb executable cannot be resolved
     */
    public XvfbManager(XvfbConfig xvfbConfig) throws IOException {
        this(resolveXvfbExecutable(), xvfbConfig);
    }

    /**
     * Constructs an instance of the class that will launch the given executable
     * with the given configuration.
     * @param xvfbExecutable pathname of the {@code Xvfb} executable
     * @param xvfbConfig virtual framebuffer configuration
     */
    public XvfbManager(File xvfbExecutable, XvfbConfig xvfbConfig) {
        this.xvfbExecutable = checkNotNull(xvfbExecutable);
        this.xvfbConfig = checkNotNull(xvfbConfig);
    }

    /**
     * Creates an executor service with a 2-thread pool and thread factory that creates daemon threads.
     * @return the executor service
     */
    protected static ExecutorService createDefaultExecutorService() {
        return Executors.newFixedThreadPool(2, new ThreadFactory() {

            private ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory();

            @Override
            public Thread newThread(Runnable r) {
                Thread t = defaultThreadFactory.newThread(r);
                t.setDaemon(true);
                return t;
            }
        });
    }

    protected static String toDisplayValue(int displayNumber) {
        checkArgument(displayNumber >= 0, "displayNumber must be nonnegative");
        return String.format(":%d", displayNumber);
    }

    protected static File resolveXvfbExecutable() throws FileNotFoundException {
        Optional<File> file = Whicher.gnu().which("Xvfb");
        if (!file.isPresent()) {
            throw new FileNotFoundException("Xvfb executable");
        }
        return file.get();
    }

    protected Screenshooter createScreenshooter(String display, File framebufferDir) {
        return new FramebufferDirScreenshooter(display, framebufferDir, SCREEN, framebufferDir);
    }

    protected Sleeper createSleeper() {
        return Sleeper.DefaultSleeper.getInstance();
    }

    protected DisplayReadinessChecker createDisplayReadinessChecker(String display, File framebufferDir) {
        return new DefaultDisplayReadinessChecker();
    }

    protected DefaultXvfbController createController(ListenableFuture<? extends ProgramWithOutputResult> future, String display, File framebufferDir) {
        return new DefaultXvfbController(future, display, createDisplayReadinessChecker(display, framebufferDir), createScreenshooter(display, framebufferDir), createSleeper());
    }

    /**
     * Starts Xvfb on a vacant display. A directory for temp files will be created and deleted
     * when the process is stopped.
     * @return the process controller
     * @throws IOException if the files and directories the process requires cannot be created or written to
     */
    public XvfbController start() throws IOException {
        return start(createDefaultExecutorService());
    }

    /**
     * Starts Xvfb on the specified display number. A directory for temp files will be created and deleted
     * when the process is stopped.
     * @param displayNumber the display number
     * @return the process controller
     * @throws IOException if the files and directories the process requires cannot be created or written to
     */
    public XvfbController start(int displayNumber) throws IOException {
        return start(displayNumber, createDefaultExecutorService());
    }

    /**
     * Starts Xvfb on a vacant display, writing temp files to the specified directory.
     * @param scratchDir the temp directory
     * @return a controller for the process
     * @throws IOException if the files and directories the process requires cannot be created or written to
     */
    public XvfbController start(Path scratchDir) throws IOException {
        return start(scratchDir, createDefaultExecutorService());
    }

    /**
     * Starts Xvfb on the specified display and writes temp files to the specified directory.
     * @param displayNumber the display number
     * @param scratchDir the temp directory
     * @return the process controller
     * @throws IOException if the files and directories the process requires cannot be created or written to
     */
    public XvfbController start(int displayNumber, Path scratchDir) throws IOException {
        return start(displayNumber, scratchDir, createDefaultExecutorService());
    }

    protected XvfbController start(int displayNumber, Path scratchDir, ExecutorService executorService) throws IOException {
        return doStart(displayNumber, nonDeletingExistingDirectoryProvider(scratchDir), executorService);
    }

    protected XvfbController start(int displayNumber, ExecutorService executorService) throws IOException {
        return doStart(displayNumber, newTempDirProvider(FileUtils.getTempDirectory().toPath()), executorService);
    }

    protected XvfbController start(ExecutorService executorService) throws IOException {
        return doStart(null, newTempDirProvider(FileUtils.getTempDirectory().toPath()), executorService);
    }

    protected XvfbController start(Path scratchDir, ExecutorService executorService) throws IOException {
        return doStart(null, nonDeletingExistingDirectoryProvider(scratchDir), executorService);
    }

    /**
     * Starts Xvfb, maybe auto-selecting a display number.
     * @param displayNumber display number, or null to auto-select
     * @param scratchDirProvider provider of scratch directory
     * @param executorService executor service
     * @return process controller
     * @throws IOException if the files and directories the process requires cannot be created or written to<commit_msg>support using custom executor service publicly<commit_after>/*
 * (c) 2016 <NAME>
 */
package com.github.mike10004.xvfbmanager;

import com.github.mike10004.nativehelper.Program;
import com.github.mike10004.nativehelper.ProgramResult;
import com.github.mike10004.nativehelper.ProgramWithOutputFiles;
import com.github.mike10004.nativehelper.ProgramWithOutputFilesResult;
import com.github.mike10004.nativehelper.ProgramWithOutputResult;
import com.github.mike10004.xvfbmanager.Poller.StopReason;
import com.github.mike10004.xvfbmanager.Poller.PollOutcome;
import com.github.mike10004.xvfbmanager.DefaultXvfbController.Screenshooter;
import com.google.common.base.Optional;
import com.google.common.collect.Iterables;
import com.google.common.io.CharSource;
import com.google.common.io.Files;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.novetta.ibg.common.sys.Whicher;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

/**
 * Class that helps manage the creation of virtual framebuffer processes.
 */
public class XvfbManager {

    private static final Logger log = LoggerFactory.getLogger(XvfbManager.class);
    private static final int SCREEN = 0;

    private final File xvfbExecutable;
    private final XvfbConfig xvfbConfig;

    /**
     * Constructs a default instance of the class.
     * @throws IOException if Xvfb executable cannot be resolved
     * @see #resolveXvfbExecutable()
     */
    public XvfbManager() throws IOException {
        this(resolveXvfbExecutable(), XvfbConfig.DEFAULT);
    }

    /**
     * Constructs an instance of the class with a given configuration.
     * @param xvfbConfig the configuration
     * @throws IOException if Xvfb executable cannot be resolved
     */
    public XvfbManager(XvfbConfig xvfbConfig) throws IOException {
        this(resolveXvfbExecutable(), xvfbConfig);
    }

    /**
     * Constructs an instance of the class that will launch the given executable
     * with the given configuration.
     * @param xvfbExecutable pathname of the {@code Xvfb} executable
     * @param xvfbConfig virtual framebuffer configuration
     */
    public XvfbManager(File xvfbExecutable, XvfbConfig xvfbConfig) {
        this.xvfbExecutable = checkNotNull(xvfbExecutable);
        this.xvfbConfig = checkNotNull(xvfbConfig);
    }

    /**
     * Creates an executor service with a 2-thread pool and thread factory that creates daemon threads.
     * @return the executor service
     */
    public static ExecutorService createDefaultExecutorService() {
        return Executors.newFixedThreadPool(2, new ThreadFactory() {

            private ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory();

            @Override
            public Thread newThread(Runnable r) {
                Thread t = defaultThreadFactory.newThread(r);
                t.setDaemon(true);
                return t;
            }
        });
    }

    protected static String toDisplayValue(int displayNumber) {
        checkArgument(displayNumber >= 0, "displayNumber must be nonnegative");
        return String.format(":%d", displayNumber);
    }

    protected static File resolveXvfbExecutable() throws FileNotFoundException {
        Optional<File> file = Whicher.gnu().which("Xvfb");
        if (!file.isPresent()) {
            throw new FileNotFoundException("Xvfb executable");
        }
        return file.get();
    }

    protected Screenshooter createScreenshooter(String display, File framebufferDir) {
        return new FramebufferDirScreenshooter(display, framebufferDir, SCREEN, framebufferDir);
    }

    protected Sleeper createSleeper() {
        return Sleeper.DefaultSleeper.getInstance();
    }

    protected DisplayReadinessChecker createDisplayReadinessChecker(String display, File framebufferDir) {
        return new DefaultDisplayReadinessChecker();
    }

    protected DefaultXvfbController createController(ListenableFuture<? extends ProgramWithOutputResult> future, String display, File framebufferDir) {
        return new DefaultXvfbController(future, display, createDisplayReadinessChecker(display, framebufferDir), createScreenshooter(display, framebufferDir), createSleeper());
    }

    /**
     * Starts Xvfb on a vacant display. A directory for temp files will be created and deleted
     * when the process is stopped.
     * @return the process controller
     * @throws IOException if the files and directories the process requires cannot be created or written to
     */
    public XvfbController start() throws IOException {
        return start(createDefaultExecutorService());
    }

    /**
     * Starts Xvfb on the specified display number. A directory for temp files will be created and deleted
     * when the process is stopped.
     * @param displayNumber the display number
     * @return the process controller
     * @throws IOException if the files and directories the process requires cannot be created or written to
     */
    public XvfbController start(int displayNumber) throws IOException {
        return start(displayNumber, createDefaultExecutorService());
    }

    /**
     * Starts Xvfb on a vacant display, writing temp files to the specified directory.
     * @param scratchDir the temp directory
     * @return a controller for the process
     * @throws IOException if the files and directories the process requires cannot be created or written to
     */
    public XvfbController start(Path scratchDir) throws IOException {
        return start(scratchDir, createDefaultExecutorService());
    }

    /**
     * Starts Xvfb on the specified display and writes temp files to the specified directory.
     * @param displayNumber the display number
     * @param scratchDir the temp directory
     * @return the process controller
     * @throws IOException if the files and directories the process requires cannot be created or written to
     */
    public XvfbController start(int displayNumber, Path scratchDir) throws IOException {
        return start(displayNumber, scratchDir, createDefaultExecutorService());
    }

    /**
     * Starts Xvfb on the specified display using the specified executor service, writing temp
     * files to the specified directory.
     * @param displayNumber the display number
     * @param scratchDir the temp directory
     * @param executorService the executor service
     * @return the process controller
     * @throws IOException if the files and directories the process requires cannot be created or written to
     */
    public XvfbController start(int displayNumber, Path scratchDir, ExecutorService executorService) throws IOException {
        return doStart(displayNumber, nonDeletingExistingDirectoryProvider(scratchDir), executorService);
    }

    /**
     * Starts Xvfb on the specified display using the specified executor service. A directory for temp files
     * is created and deleted when the process is stopped.
     * @param displayNumber the display number
     * @param executorService the executor service
     * @return the process controller
     * @throws IOException if the files and directories the process requires cannot be created or written to
     */
    public XvfbController start(int displayNumber, ExecutorService executorService) throws IOException {
        return doStart(displayNumber, newTempDirProvider(FileUtils.getTempDirectory().toPath()), executorService);
    }

    /**
     * Starts Xvfb on a vacant display using the specified executor service. A directory for temp files
     * is created and deleted when the process is stopped.
     * @param executorService the executor service
     * @return the process controller
     * @throws IOException if the files and directories the process requires cannot be created or written to
     */
    public XvfbController start(ExecutorService executorService) throws IOException {
        return doStart(null, newTempDirProvider(FileUtils.getTempDirectory().toPath()), executorService);
    }

    /**
     * Starts Xvfb on a vacant display using the specified executor service and writing temp files
     * to the given directory.
     * @param scratchDir the temp directory
     * @param executorService the executor service
     * @return the process controller
     * @throws IOException if the files and directories the process requires cannot be created or written to
     */
    public XvfbController start(Path scratchDir, ExecutorService executorService) throws IOException {
        return doStart(null, nonDeletingExistingDirectoryProvider(scratchDir), executorService);
    }

    /**
     * Starts Xvfb, maybe auto-selecting a display number.
     * @param displayNumber display number, or null to auto-select
     * @param scratchDirProvider provider of scratch directory
     * @param executorService executor service
     * @return process controller
     * @throws IOException if the files and directories the process requires cannot be created or written to
{'fraction_non_alphanumeric': 0.0711730059998899, 'fraction_numerical': 0.0062751142180877415, 'mean_word_length': 3.8127152317880793, 'pattern_counts': {'":': 2, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2772355', 'n_tokens_mistral': 4775, 'n_tokens_neox': 4606, 'n_words': 1483}