All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.methvin.watchservice.AbstractWatchService Maven / Gradle / Ivy

/*
 * 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 io.methvin.watchservice;

import java.io.IOException;
import java.nio.file.ClosedWatchServiceException;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.Collections;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import static java.util.Objects.requireNonNull;

/**
 * Abstract implementation of {@link WatchService}. Provides the means for registering and managing
 * keys but does not handle actually watching. Subclasses should implement the means of watching
 * watchables, posting events to registered keys and queueing keys with the service by signalling
 * them.
 *
 * @author Colin Decker
 * @author Greg Methvin
 */
abstract class AbstractWatchService implements WatchService {

  private final BlockingQueue queue = new LinkedBlockingQueue<>();
  private final WatchKey poison = new AbstractWatchKey(this, null, Collections.emptySet(), 1);

  private final AtomicBoolean open = new AtomicBoolean(true);

  /**
   * Registers the given watchable with this service, returning a new watch key for it. This
   * implementation just checks that the service is open and creates a key; subclasses may override
   * it to do other things as well.
   */
  public abstract AbstractWatchKey register(
      WatchablePath watchable, Iterable> eventTypes)
      throws IOException;

  /** Returns whether or not this watch service is open. */
  public boolean isOpen() {
    return open.get();
  }

  /** Enqueues the given key if the watch service is open; does nothing otherwise. */
  final void enqueue(AbstractWatchKey key) {
    if (isOpen()) {
      queue.add(key);
    }
  }

  /** Called when the given key is cancelled. Does nothing by default. */
  public void cancelled(AbstractWatchKey key) {}

  @Override
  public WatchKey poll() {
    checkOpen();
    return check(queue.poll());
  }

  @Override
  public WatchKey poll(long timeout, TimeUnit unit) throws InterruptedException {
    checkOpen();
    return check(queue.poll(timeout, unit));
  }

  @Override
  public WatchKey take() throws InterruptedException {
    checkOpen();
    return check(queue.take());
  }

  /** Returns the given key, throwing an exception if it's the poison. */
  private WatchKey check(WatchKey key) {
    if (key == poison) {
      // ensure other blocking threads get the poison
      queue.offer(poison);
      throw new ClosedWatchServiceException();
    }
    return key;
  }

  /** Checks that the watch service is open, throwing {@link ClosedWatchServiceException} if not. */
  protected final void checkOpen() {
    if (!open.get()) {
      throw new ClosedWatchServiceException();
    }
  }

  @Override
  public void close() {
    if (open.compareAndSet(true, false)) {
      queue.clear();
      queue.offer(poison);
    }
  }

  /** A basic implementation of {@link WatchEvent}. */
  static final class Event implements WatchEvent {

    private final Kind kind;
    private final int count;

    private final T context;

    public Event(Kind kind, int count, T context) {
      this.kind = requireNonNull(kind);
      if (count < 0) {
        throw new IllegalArgumentException(String.format("count (%s) must be non-negative", count));
      }
      this.count = count;
      this.context = context;
    }

    @Override
    public Kind kind() {
      return kind;
    }

    @Override
    public int count() {
      return count;
    }

    @Override
    public T context() {
      return context;
    }

    @Override
    public boolean equals(Object obj) {
      if (obj instanceof Event) {
        Event other = (Event) obj;
        return kind().equals(other.kind())
            && count() == other.count()
            && Objects.equals(context(), other.context());
      }
      return false;
    }

    @Override
    public int hashCode() {
      return Objects.hash(kind(), count(), context());
    }

    @Override
    public String toString() {
      return new StringJoiner(", ", getClass().getSimpleName() + "[", "]")
          .add("kind=" + kind())
          .add("count=" + count())
          .add("context=" + context())
          .toString();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy