001/**
002 * Powerunit - A JDK1.8 test framework
003 * Copyright (C) 2014 Mathieu Boretti.
004 *
005 * This file is part of Powerunit
006 *
007 * Powerunit is free software: you can redistribute it and/or modify
008 * it under the terms of the GNU General Public License as published by
009 * the Free Software Foundation, either version 3 of the License, or
010 * (at your option) any later version.
011 *
012 * Powerunit is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
015 * GNU General Public License for more details.
016 *
017 * You should have received a copy of the GNU General Public License
018 * along with Powerunit. If not, see <http://www.gnu.org/licenses/>.
019 */
020package ch.powerunit.extensions.async.lang;
021
022import static ch.powerunit.extensions.async.lang.WaitResult.callableWithToString;
023import static ch.powerunit.extensions.async.lang.WaitResult.of;
024import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
025import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
026import static java.util.Objects.requireNonNull;
027import static java.util.stream.Collectors.collectingAndThen;
028import static java.util.stream.Collectors.toList;
029
030import java.nio.file.Path;
031import java.nio.file.WatchEvent;
032import java.nio.file.WatchEvent.Kind;
033import java.util.Collection;
034import java.util.Collections;
035import java.util.Objects;
036import java.util.concurrent.Callable;
037
038import ch.powerunit.extensions.async.impl.FilePool;
039
040/**
041 * This class provides methods to wait for fileSystem events.
042 * <p>
043 * The goal is to wait for filesystem event, by using the
044 * {@link java.nio.file.WatchService} functionality.
045 * 
046 * @since 1.1.0
047 * @see java.nio.file.WatchService
048 */
049public final class WaitFile {
050        private WaitFile() {
051        }
052
053        /**
054         * Wait for a folder to have some event.
055         * <p>
056         * The wait starts at the first try to get the result.
057         * <p>
058         * For example :
059         * 
060         * <pre>
061         * CompletableFuture&lt;Optional&lt;Collection&lt;WatchEvent&lt;Path&gt;&gt;&gt;&gt; wait = WaitFile
062         *              .eventIn(test, StandardWatchEventKinds.ENTRY_CREATE)
063         *              .expecting(l -&gt; l.stream().map(WatchEvent::context).map(Path::getFileName).map(Path::toString)
064         *                              .anyMatch(n -&gt; n.equals("test")))
065         *              .repeat(3).every(Duration.ofMillis(250)).usingDefaultExecutor().asyncExec();
066         * </pre>
067         * 
068         * Defines a 3 tries with a wait time of 250ms, for a creation event, containing
069         * at least one event with last part of a path named test.
070         * 
071         * @param directory
072         *            the directory to be verified.
073         * @param events
074         *            the events to wait for.
075         * @return {@link WaitResultBuilder1} the next step of the builder.
076         */
077        @SafeVarargs
078        public static WaitResultBuilder1<Collection<WatchEvent<Path>>> eventIn(Path directory, Kind<Path>... events) {
079                requireNonNull(directory, "directory can't be null");
080                FilePool filePool = new FilePool(directory, events);
081                return of(filePool, filePool::close);
082        }
083
084        /**
085         * Wait for a folder to contains new entry.
086         * <p>
087         * The wait starts at the first try to get the result.
088         * <p>
089         * For example :
090         * 
091         * <pre>
092         * CompletableFuture&lt;Optional&lt;Collection&lt;Path&gt;&gt;&gt; wait = WaitFile.newFileIn(test)
093         *              .expecting(l -&gt; l.stream().map(Path::getFileName).map(Path::toString).anyMatch(n -&gt; n.equals("test")))
094         *              .repeat(3).every(Duration.ofMillis(250)).usingDefaultExecutor().asyncExec();
095         * </pre>
096         * 
097         * Defines a 3 tries with a wait time of 250ms, for a list of new file,
098         * containing at least one file with last part of a path named test.
099         * 
100         * 
101         * @param directory
102         *            the directory to be verified.
103         * @return {@link WaitResultBuilder1} the next step of the builder.
104         */
105        public static WaitResultBuilder1<Collection<Path>> newFileIn(Path directory) {
106                requireNonNull(directory, "directory can't be null");
107                FilePool filePool = new FilePool(directory, ENTRY_CREATE);
108                return of(callableWithToString(toPathCollection(filePool), () -> "new file in " + filePool), filePool::close);
109        }
110
111        /**
112         * Wait for a folder to contains new entry based on his name.
113         * <p>
114         * The wait starts at the first try to get the result.
115         * <p>
116         * For example :
117         * 
118         * <pre>
119         * CompletableFuture&lt;Optional&lt;Path&gt;&gt; wait = WaitFile.newFileNamedIn(test, "test").expectingNotNull().repeat(3)
120         *              .every(Duration.ofMillis(250)).usingDefaultExecutor().asyncExec();
121         * </pre>
122         * 
123         * Defines a 3 tries with a wait time of 25ms, for a file named "test".
124         * 
125         * @param directory
126         *            the directory to be verified.
127         * @param name
128         *            the expected name
129         * @return {@link WaitResultBuilder1} the next step of the builder.
130         */
131        public static WaitResultBuilder1<Path> newFileNamedIn(Path directory, String name) {
132                requireNonNull(directory, "directory can't be null");
133                requireNonNull(name, "name can't be null");
134                FilePool filePool = new FilePool(directory, ENTRY_CREATE);
135                return of(callableWithToString(toPathByName(toPathCollection(filePool), name),
136                                () -> String.format("New file named %s in %s", name, filePool)), filePool::close);
137        }
138
139        /**
140         * Wait for a folder to have entry removed.
141         * <p>
142         * The wait starts at the first try to get the result.
143         * 
144         * @param directory
145         *            the directory to be verified.
146         * @return {@link WaitResultBuilder1} the next step of the builder.
147         */
148        public static WaitResultBuilder1<Collection<Path>> removeFileFrom(Path directory) {
149                requireNonNull(directory, "directory can't be null");
150                FilePool filePool = new FilePool(directory, ENTRY_DELETE);
151                return of(callableWithToString(toPathCollection(filePool), () -> "Removed file from " + directory),
152                                filePool::close);
153        }
154
155        private static Callable<Collection<Path>> toPathCollection(Callable<Collection<WatchEvent<Path>>> callable) {
156                return () -> callable.call().stream().map(WatchEvent::context)
157                                .collect(collectingAndThen(toList(), Collections::unmodifiableList));
158        }
159
160        private static Callable<Path> toPathByName(Callable<Collection<Path>> callable, String name) {
161                return () -> callable.call().stream()
162                                .filter(p -> Objects.equals(p.getName(p.getNameCount() - 1).toString(), name)).findFirst().orElse(null);
163        }
164
165}