/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.vfs.watcher;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Singleton;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import static com.google.common.collect.Sets.newConcurrentHashSet;
import static java.nio.file.Files.exists;
@Singleton
public class FileWatcherByPathMatcher implements Consumer<Path> {
private static final Logger LOG = LoggerFactory.getLogger(FileWatcherByPathMatcher.class);
private final AtomicInteger operationIdCounter = new AtomicInteger();
private final FileWatcherByPathValue watcher;
/** Operation ID -> Operation (create, modify, delete) */
private final Map<Integer, Operation> operations = new ConcurrentHashMap<>();
/** Operation ID -> Registered paths */
private final Map<Integer, Set<Path>> paths = new ConcurrentHashMap<>();
/** Matcher -> Operation IDs */
private final Map<PathMatcher, Set<Integer>> matchers = new ConcurrentHashMap<>();
/** Registered path -> Path watch operation IDs */
private final Map<Path, Set<Integer>> pathWatchRegistrations = new ConcurrentHashMap<>();
@Inject
public FileWatcherByPathMatcher(FileWatcherByPathValue watcher) {
this.watcher = watcher;
}
@Override
public void accept(Path path) {
if (!exists(path)) {
if (pathWatchRegistrations.containsKey(path)) {
pathWatchRegistrations.remove(path).forEach(watcher::unwatch);
}
paths.values().forEach(it -> it.remove(path));
paths.entrySet().removeIf(it -> it.getValue().isEmpty());
}
for (PathMatcher matcher : matchers.keySet()) {
if (matcher.matches(path)) {
for (int operationId : matchers.get(matcher)) {
paths.putIfAbsent(operationId, newConcurrentHashSet());
if (paths.get(operationId).contains(path)) {
return;
}
paths.get(operationId).add(path);
Operation operation = operations.get(operationId);
int pathWatcherOperationId = watcher.watch(path, operation.create, operation.modify, operation.delete);
pathWatchRegistrations.putIfAbsent(path, newConcurrentHashSet());
pathWatchRegistrations.get(path).add(pathWatcherOperationId);
}
}
}
}
int watch(PathMatcher matcher, Consumer<String> create, Consumer<String> modify, Consumer<String> delete) {
LOG.debug("Watching matcher '{}'", matcher);
int operationId = operationIdCounter.getAndIncrement();
matchers.putIfAbsent(matcher, newConcurrentHashSet());
matchers.get(matcher).add(operationId);
operations.put(operationId, new Operation(create, modify, delete));
LOG.debug("Registered matcher operation set with id '{}'", operationId);
return operationId;
}
void unwatch(int operationId) {
LOG.debug("Unwatching matcher operation set with id '{}'", operationId);
for (Entry<PathMatcher, Set<Integer>> entry : matchers.entrySet()) {
PathMatcher matcher = entry.getKey();
Set<Integer> operationsIdList = entry.getValue();
Iterator<Integer> iterator = operationsIdList.iterator();
while (iterator.hasNext()) {
if (iterator.next() == operationId) {
pathWatchRegistrations.keySet().stream()
.filter(matcher::matches)
.flatMap(it -> pathWatchRegistrations.remove(it).stream())
.forEach(watcher::unwatch);
paths.values().forEach(it -> it.removeIf(matcher::matches));
paths.entrySet().removeIf(it -> it.getValue().isEmpty());
iterator.remove();
operations.remove(operationId);
break;
}
}
if (matchers.get(matcher) == null || matchers.get(matcher).isEmpty()) {
matchers.remove(matcher);
}
}
}
private static class Operation {
final Consumer<String> create;
final Consumer<String> modify;
final Consumer<String> delete;
private Operation(Consumer<String> create, Consumer<String> modify, Consumer<String> delete) {
this.create = create;
this.modify = modify;
this.delete = delete;
}
}
}