com.facebook.watchman.WatchmanClientImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.watchman.WatchmanClientImpl.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;

import javax.annotation.Nullable;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;

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

public class WatchmanClientImpl implements WatchmanClient {
    private static final String SUBSCRIPTION_KEY = "subscription";
    private static final Collection<String> UNILATERAL_LABELS = Arrays.asList(SUBSCRIPTION_KEY);

    private final WatchmanConnection connection;
    private final ConcurrentHashMap<SubscriptionDescriptor, Callback> subscriptions = new ConcurrentHashMap<SubscriptionDescriptor, Callback>();
    private final AtomicInteger subscriptionIndex = new AtomicInteger(0);

    private final Supplier<Boolean> supportsWatchProject;

    public WatchmanClientImpl(Socket socket) throws IOException {
        connection = new WatchmanConnection(socket, Optional.of(UNILATERAL_LABELS),
                Optional.<Callback>of(new UnilateralCallbackImpl()));

        supportsWatchProject = Suppliers.memoize(new Supplier<Boolean>() {
            @Override
            public Boolean get() {
                return CapabilitiesStrategy.checkWatchProjectCapability(WatchmanClientImpl.this);
            }
        });
    }

    @VisibleForTesting
    WatchmanClientImpl(Supplier<Map<String, Object>> inputSupplier, OutputStream outputStream,
            Supplier<Boolean> supportsWatchProject) {
        connection = new WatchmanConnection(inputSupplier, outputStream, Optional.of(UNILATERAL_LABELS),
                Optional.<Callback>of(new UnilateralCallbackImpl()));

        this.supportsWatchProject = supportsWatchProject;
    }

    @Override
    public ListenableFuture<Map<String, Object>> clock(Path path) {
        List<String> request = ImmutableList.of("clock", path.toAbsolutePath().toString());
        return connection.run(request);
    }

    @Override
    public ListenableFuture<Map<String, Object>> clock(Path path, Number syncTimeout) {
        List<Object> request = ImmutableList.<Object>of("clock", path.toAbsolutePath().toString(),
                ImmutableMap.<String, Object>of("sync_timeout", syncTimeout));

        return connection.run(request);
    }

    @Override
    public ListenableFuture<Map<String, Object>> watch(Path path) {
        if (!supportsWatchProject.get()) {
            return Futures.immediateFailedFuture(
                    new WatchmanException("Please upgrade Watchman to the latest version in order to use "
                            + "the watching functionality"));
        }

        List<String> request = ImmutableList.of("watch-project", path.toAbsolutePath().toString());
        return connection.run(request);
    }

    @Override
    public ListenableFuture<Map<String, Object>> watchDel(Path path) {
        List<String> request = ImmutableList.of("watch-del", path.toAbsolutePath().toString());
        return connection.run(request);
    }

    @Override
    public ListenableFuture<Boolean> unsubscribe(final SubscriptionDescriptor descriptor) {
        if (!subscriptions.containsKey(descriptor)) {
            return Futures.immediateFuture(false);
        }

        List<String> request = ImmutableList.of("unsubscribe", descriptor.root(), descriptor.name());

        return Futures.transform(connection.run(request), new Function<Map<String, Object>, Boolean>() {
            @Nullable
            @Override
            public Boolean apply(@Nullable Map<String, Object> input) {
                checkNotNull(input);

                boolean wasDeleted = (Boolean) input.get("deleted");
                if (wasDeleted) {
                    if (subscriptions.remove(descriptor) == null) {
                        return false;
                    }
                }
                return wasDeleted;
            }
        });
    }

    @Override
    public ListenableFuture<SubscriptionDescriptor> subscribe(Path path, Map<String, Object> query,
            final Callback listener) {
        final String subscriptionId = "sub-" + subscriptionIndex.getAndAdd(1);
        final String root = path.toAbsolutePath().toString();

        final SubscriptionDescriptor result = new SubscriptionDescriptorBuilder().name(subscriptionId).root(root)
                .build();
        subscriptions.put(result, listener);

        List<Object> request = ImmutableList.of("subscribe", root, subscriptionId,
                query == null ? Collections.emptyMap() : query);

        return Futures.transform(connection.run(request),
                new Function<Map<String, Object>, SubscriptionDescriptor>() {
                    @Nullable
                    @Override
                    public SubscriptionDescriptor apply(@Nullable Map<String, Object> input) {
                        // TODO remove subscription descriptor from `subscriptions` if we got an error from wman
                        return result;
                    }
                });
    }

    @Override
    public ListenableFuture<Map<String, Object>> version() {
        List<String> request = ImmutableList.of("version");
        return connection.run(request);
    }

    @Override
    public ListenableFuture<Map<String, Object>> version(List<String> optionalCapabilities,
            List<String> requiredCapabilities) {
        Map<String, Object> capabilities = ImmutableMap.<String, Object>of("optional", optionalCapabilities,
                "required", requiredCapabilities);
        List<Object> request = ImmutableList.of("version", capabilities);
        return connection.run(request);
    }

    @Override
    public ListenableFuture<Map<String, Object>> run(List<Object> command) {
        return connection.run(command);
    }

    /**
     * unsubscribes from all the subscriptions; convenience method
     */
    @Override
    public ListenableFuture<Boolean> unsubscribeAll() {
        Collection<ListenableFuture<Boolean>> unsubscribeAll = Collections2.transform(subscriptions.keySet(),
                new Function<SubscriptionDescriptor, ListenableFuture<Boolean>>() {
                    @Nullable
                    @Override
                    public ListenableFuture<Boolean> apply(@Nullable SubscriptionDescriptor input) {
                        return unsubscribe(input);
                    }
                });
        return Futures.transform(Futures.allAsList(unsubscribeAll), new Function<List<Boolean>, Boolean>() {
            @Nullable
            @Override
            public Boolean apply(@Nullable List<Boolean> input) {
                return !Collections2.filter(input, Predicates.equalTo(false)).isEmpty();
            }
        });
    }

    /**
     * closes the WatchmanConnection
     */
    @Override
    public void close() throws IOException {
        connection.close();
    }

    @Override
    public void start() {
        connection.start();
    }

    private class UnilateralCallbackImpl implements Callback {

        @Override
        public void call(Map<String, Object> message) throws Exception {
            if (message.containsKey(SUBSCRIPTION_KEY)) {
                String subscriptionId = (String) message.get("subscription");
                String root = (String) message.get("root");
                SubscriptionDescriptor subscription = new SubscriptionDescriptorBuilder().name(subscriptionId)
                        .root(root).build();
                if (!subscriptions.containsKey(subscription)) {
                    // TODO log error?!
                    return;
                }
                subscriptions.get(subscription).call(message);
            }
        }
    }

}