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.exceptions;
021
022import static ch.powerunit.extensions.exceptions.Constants.verifyExceptionMapper;
023import static ch.powerunit.extensions.exceptions.Constants.verifyPredicate;
024import static java.util.Objects.requireNonNull;
025
026import java.nio.file.Path;
027import java.nio.file.PathMatcher;
028import java.util.function.Function;
029import java.util.function.Supplier;
030
031/**
032 * An interface that is implemented by objects that perform match operations on
033 * paths and may throw an exception.
034 * <h3>General contract</h3>
035 * <ul>
036 * <li><b>{@link #matches(Path) boolean matches(Path path) throws
037 * E}</b>&nbsp;-&nbsp;The functional method.</li>
038 * <li><b>uncheck</b>&nbsp;-&nbsp;Return a {@code PathMatcher}</li>
039 * <li><b>lift</b>&nbsp;-&nbsp;Return a {@code PathMatcher}</li>
040 * <li><b>ignore</b>&nbsp;-&nbsp;Return a {@code PathMatcher}</li>
041 * </ul>
042 *
043 * @see PathMatcher
044 * @param <E>
045 *            the type of the potential exception of the function
046 * @since 1.1.0
047 */
048@FunctionalInterface
049public interface PathMatcherWithException<E extends Exception>
050                extends PrimitiveReturnExceptionHandlerSupport<PathMatcher, PathMatcherWithException<E>>, BooleanDefaultValue {
051
052        /**
053         * Tells if given path matches this matcher's pattern.
054         *
055         * @param path
056         *            the path to match
057         *
058         * @return {@code true} if, and only if, the path matches this matcher's pattern
059         * @throws E
060         *             any exception
061         * @see PathMatcher#matches(Path)
062         */
063        boolean matches(Path path) throws E;
064
065        @Override
066        default PathMatcher uncheckOrIgnore(boolean uncheck) {
067                return value -> {
068                        try {
069                                return matches(value);
070                        } catch (Exception e) {
071                                PrimitiveReturnExceptionHandlerSupport.handleException(uncheck, e, exceptionMapper());
072                                return defaultValue();
073                        }
074                };
075        }
076
077        /**
078         * Returns a composed PathMatcher that represents a short-circuiting logical AND
079         * of this PathMatcher and another. When evaluating the composed predicate, if
080         * this predicate is {@code false}, then the {@code other} predicate is not
081         * evaluated.
082         *
083         * <p>
084         * Any exceptions thrown during evaluation of either PathMatcher are relayed to
085         * the caller; if evaluation of this predicate throws an exception, the
086         * {@code other} PathMatcher will not be evaluated.
087         *
088         * @param other
089         *            a PathMatcher that will be logically-ANDed with this predicate
090         * @return a composed PathMatcher that represents the short-circuiting logical
091         *         AND of this PathMatcher and the {@code other} PathMatcher
092         * @throws NullPointerException
093         *             if other is null
094         * @see #or(PathMatcherWithException)
095         * @see #negate()
096         */
097        default PathMatcherWithException<E> and(PathMatcherWithException<? extends E> other) {
098                requireNonNull(other);
099                return t -> matches(t) && other.matches(t);
100        }
101
102        /**
103         * Returns a PathMatcher that represents the logical negation of this
104         * PathMatcher.
105         *
106         * @return a PathMatcher that represents the logical negation of this
107         *         PathMatcher
108         * @see #and(PathMatcherWithException)
109         * @see #or(PathMatcherWithException)
110         */
111        default PathMatcherWithException<E> negate() {
112                return t -> !matches(t);
113        }
114
115        /**
116         * Negate a {@code PredicateWithException}.
117         *
118         * @param predicate
119         *            to be negate
120         * @param <E>
121         *            the type of the potential exception
122         * @return the negated PathMatcher
123         * @see #negate()
124         * @throws NullPointerException
125         *             if predicate is null
126         */
127        static <E extends Exception> PathMatcherWithException<E> negate(PathMatcherWithException<E> predicate) {
128                return verifyPredicate(predicate).negate();
129        }
130
131        /**
132         * Returns a composed PathMatcher that represents a short-circuiting logical OR
133         * of this PathMatcher and another. When evaluating the composed PathMatcher, if
134         * this PathMatcher is {@code true}, then the {@code other} predicate is not
135         * evaluated.
136         *
137         * <p>
138         * Any exceptions thrown during evaluation of either PathMatcher are relayed to
139         * the caller; if evaluation of this PathMatcher throws an exception, the
140         * {@code other} PathMatcher will not be evaluated.
141         *
142         * @param other
143         *            a PathMatcher that will be logically-ORed with this predicate
144         * @return a composed PathMatcher that represents the short-circuiting logical
145         *         OR of this predicate and the {@code other} PathMatcher
146         * @throws NullPointerException
147         *             if other is null
148         * @see #and(PathMatcherWithException)
149         * @see #negate()
150         */
151        default PathMatcherWithException<E> or(PathMatcherWithException<? extends E> other) {
152                requireNonNull(other);
153                return t -> matches(t) || other.matches(t);
154        }
155
156        /**
157         * Returns a PathMatcher that always throw exception.
158         *
159         * @param exceptionBuilder
160         *            the supplier to create the exception
161         * @param <E>
162         *            the type of the exception
163         * @return a PathMatcher that always throw exception
164         */
165        static <E extends Exception> PathMatcherWithException<E> failing(Supplier<E> exceptionBuilder) {
166                return t -> {
167                        throw exceptionBuilder.get();
168                };
169        }
170
171        /**
172         * Converts a {@code PathMatcherWithException} to a {@code PathMatcher} that
173         * wraps exception to {@code RuntimeException}.
174         *
175         * @param predicate
176         *            to be unchecked
177         * @param <E>
178         *            the type of the potential exception
179         * @return the unchecked PathMatcher
180         * @see #uncheck()
181         * @see #unchecked(PathMatcherWithException, Function)
182         * @throws NullPointerException
183         *             if predicate is null
184         */
185        static <E extends Exception> PathMatcher unchecked(PathMatcherWithException<E> predicate) {
186                return verifyPredicate(predicate).uncheck();
187        }
188
189        /**
190         * Converts a {@code PathMatcherWithException} to a {@code PathMatcher} that
191         * wraps exception to {@code RuntimeException} by using the provided mapping
192         * function.
193         *
194         * @param predicate
195         *            the be unchecked
196         * @param exceptionMapper
197         *            a function to convert the exception to the runtime exception.
198         * @param <E>
199         *            the type of the potential exception
200         * @return the unchecked PathMatcher
201         * @see #uncheck()
202         * @see #unchecked(PathMatcherWithException)
203         * @throws NullPointerException
204         *             if predicate or exceptionMapper is null
205         */
206        static <E extends Exception> PathMatcher unchecked(PathMatcherWithException<E> predicate,
207                        Function<Exception, RuntimeException> exceptionMapper) {
208                verifyPredicate(predicate);
209                verifyExceptionMapper(exceptionMapper);
210                return new PathMatcherWithException<E>() {
211
212                        @Override
213                        public boolean matches(Path t) throws E {
214                                return predicate.matches(t);
215                        }
216
217                        @Override
218                        public Function<Exception, RuntimeException> exceptionMapper() {
219                                return exceptionMapper;
220                        }
221
222                }.uncheck();
223        }
224
225        /**
226         * Converts a {@code PathMatcherWithException} to a lifted {@code PathMatcher}
227         * returning {@code false} in case of exception.
228         *
229         * @param predicate
230         *            to be lifted
231         * @param <E>
232         *            the type of the potential exception
233         * @return the lifted PathMatcher
234         * @see #lift()
235         * @throws NullPointerException
236         *             if predicate is null
237         */
238        static <E extends Exception> PathMatcher lifted(PathMatcherWithException<E> predicate) {
239                return verifyPredicate(predicate).lift();
240        }
241
242        /**
243         * Converts a {@code PathMatcherWithException} to a lifted {@code PathMatcher}
244         * returning {@code false} in case of exception.
245         *
246         * @param predicate
247         *            to be lifted
248         * @param <E>
249         *            the type of the potential exception
250         * @return the lifted PathMatcher
251         * @see #ignore()
252         * @throws NullPointerException
253         *             if predicate is null
254         */
255        static <E extends Exception> PathMatcher ignored(PathMatcherWithException<E> predicate) {
256                return verifyPredicate(predicate).ignore();
257        }
258
259        /**
260         * Converts a {@code PathMatcherWithException} to a lifted {@code PathMatcher}
261         * returning a default value in case of exception.
262         *
263         * @param predicate
264         *            to be lifted
265         * @param defaultValue
266         *            value in case of exception
267         * @param <E>
268         *            the type of the potential exception
269         * @return the lifted PathMatcher
270         * @see #ignore()
271         * @see #ignored(PathMatcherWithException)
272         * @throws NullPointerException
273         *             if predicate is null
274         * @since 3.0.0
275         */
276        static <E extends Exception> PathMatcher ignored(PathMatcherWithException<E> predicate, boolean defaultValue) {
277                verifyPredicate(predicate);
278                return new PathMatcherWithException<E>() {
279
280                        @Override
281                        public boolean matches(Path t) throws E {
282                                return predicate.matches(t);
283                        }
284
285                        @Override
286                        public boolean defaultValue() {
287                                return defaultValue;
288                        }
289
290                }.ignore();
291        }
292
293}