com.facebook.watchman.environment.ExecutableFinder.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.watchman.environment.ExecutableFinder.java

Source

/*
 * Copyright 2004-present Facebook. All Rights Reserved.
 *
 * 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.watchman.environment;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.sun.jna.Platform;

import static java.io.File.pathSeparator;

/**
 * Given the name of an executable, search a set of (possibly platform-specific) known locations for
 * that executable.
 */
public class ExecutableFinder {

    private static final ImmutableSet<String> DEFAULT_WINDOWS_EXTENSIONS = ImmutableSet.of(".bat", ".cmd", ".com",
            ".cpl", ".exe", ".js", ".jse", ".msc", ".vbs", ".wsf", ".wsh");
    // Avoid using MorePaths.TO_PATH because of circular deps in this package
    private static final Function<String, Path> TO_PATH = new Function<String, Path>() {
        @Override
        public Path apply(String path) {
            return Paths.get(path);
        }
    };

    private static final Function<Path, Boolean> IS_EXECUTABLE = new Function<Path, Boolean>() {
        @Override
        public Boolean apply(Path path) {
            return ExecutableFinder.isExecutable(path);
        }
    };

    public static Path getExecutable(Path suggestedExecutable, ImmutableMap<String, String> env) {
        Optional<Path> exe = getOptionalExecutable(suggestedExecutable, env);
        if (!exe.isPresent()) {
            throw new RuntimeException(String.format(
                    "Unable to locate %s on PATH, or it's not marked as being executable", suggestedExecutable));
        }
        return exe.get();
    }

    public static Optional<Path> getOptionalExecutable(Path suggestedExecutable, ImmutableMap<String, String> env) {
        return getOptionalExecutable(suggestedExecutable, getPaths(env), getExecutableSuffixes(env));
    }

    public static Optional<Path> getOptionalExecutable(Path suggestedExecutable, ImmutableCollection<Path> path,
            ImmutableCollection<String> fileSuffixes) {

        // Fast path out of here.
        if (isExecutable(suggestedExecutable)) {
            return Optional.of(suggestedExecutable);
        }

        Optional<Path> executable = FileFinder.getOptionalFile(FileFinder.combine(/* prefixes */ null,
                suggestedExecutable.toString(), ImmutableSet.copyOf(fileSuffixes)), path, IS_EXECUTABLE);

        return executable;
    }

    private static boolean isExecutable(Path exe) {
        if (!Files.exists(exe)) {
            return false;
        }

        if (Files.isSymbolicLink(exe)) {
            try {
                Path target = Files.readSymbolicLink(exe);
                return isExecutable(exe.resolveSibling(target).normalize());
            } catch (IOException e) { // NOPMD
            } catch (SecurityException e) { // NOPMD

            }
        }

        if (Files.isDirectory(exe)) {
            return false;
        }

        if (!Files.isExecutable(exe) && !Files.isSymbolicLink(exe)) {
            return false;
        }

        return true;
    }

    private static ImmutableSet<Path> getPaths(ImmutableMap<String, String> env) {
        ImmutableSet.Builder<Path> paths = ImmutableSet.builder();

        // Add the empty path so that when we iterate over it, we can check for the suffixed version of
        // a given path, be it absolute or not.
        paths.add(Paths.get(""));

        String pathEnv = env.get("PATH");
        if (pathEnv != null) {
            paths.addAll(FluentIterable.from(Splitter.on(pathSeparator).omitEmptyStrings().split(pathEnv))
                    .transform(TO_PATH));
        }

        if (Platform.isMac()) {
            Path osXPaths = Paths.get("/etc/paths");
            if (Files.exists(osXPaths)) {
                try {
                    paths.addAll(FluentIterable.from(Files.readAllLines(osXPaths, Charset.defaultCharset()))
                            .transform(TO_PATH));
                } catch (IOException e) {
                }
            }
        }

        return paths.build();
    }

    private static ImmutableSet<String> getExecutableSuffixes(ImmutableMap<String, String> env) {
        if (Platform.isWindows()) {
            String pathext = env.get("PATHEXT");
            if (pathext == null) {
                return DEFAULT_WINDOWS_EXTENSIONS;
            }
            return ImmutableSet.<String>builder().addAll(Splitter.on(";").omitEmptyStrings().split(pathext))
                    .build();
        }
        return ImmutableSet.of("");
    }

    /**
     * Constructor hidden; there is no reason to instantiate this class.
     */
    private ExecutableFinder() {
    }
}