001/*
002 *  Copyright 2016 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.core.util;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.LinkedHashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Objects;
024import java.util.function.BinaryOperator;
025import java.util.function.Consumer;
026import java.util.function.Function;
027import java.util.function.Predicate;
028import java.util.stream.Collector;
029
030/**
031 * Helper for lambda expressions.
032 */
033public final class LambdaUtils
034{
035    private LambdaUtils()
036    {
037        // empty constructor
038    }
039    
040    /**
041     * Function allowed to throw checked {@link Exception}. <br>
042     * It allows to build one-line lambda for methods throwing checked exceptions.
043     * @param <T> the type of the input to the function.
044     * @param <R> the type of the result of the function.
045     */
046    @FunctionalInterface
047    public interface ThrowingFunction<T, R>
048    {
049        /**
050         * Applies this function to the given argument.
051         * @param t the function argument
052         * @return the function result
053         * @throws Exception if something wrong occurs.
054         */
055        R apply(T t) throws Exception;
056    }
057    
058    /**
059     * Wraps a {@link Function} by catching its {@link Exception} and rethrowing them as {@link LambdaException}.
060     * @param function the {@link Function} to wrap.
061     * @param <T> The type of input to the function
062     * @param <R> The type of result of the function
063     * @return the wrapped {@link Function}.
064     */
065    public static <T, R> Function<T, R> wrap(ThrowingFunction<T, R> function)
066    {
067        return value -> 
068        {
069            try
070            {
071                return function.apply(value);
072            }
073            catch (Exception e)
074            {
075                if (e instanceof RuntimeException)
076                {
077                    throw (RuntimeException) e;
078                }
079                
080                throw new LambdaException(e);
081            }
082        };
083    }
084    
085    /**
086     * Predicate allowed to throw checked {@link Exception}. <br>
087     * It allows to build one-line lambda for methods throwing checked exceptions.
088     * @param <T> the type of the input to the predicate.
089     */
090    @FunctionalInterface
091    public interface ThrowingPredicate<T>
092    {
093        /**
094         * Evaluates this predicate on the given argument.
095         * @param t the input argument
096         * @return {@code true} if the input argument matches the predicate,
097         * otherwise {@code false}
098         * @throws Exception if something wrong occurs.
099         */
100        boolean test(T t) throws Exception;
101    }
102    
103    /**
104     * Wraps a {@link Predicate} by catching its {@link Exception} and rethrowing them as {@link LambdaException}.
105     * @param predicate the {@link Predicate} to wrap.
106     * @param <T> the type of the input to the predicate
107     * @return the wrapped {@link Predicate}.
108     */
109    public static <T> Predicate<T> wrapPredicate(ThrowingPredicate<T> predicate)
110    {
111        return LambdaUtils.wrap(predicate::test)::apply;
112        
113    }
114    
115    /**
116     * Consumer allowed to throw checked {@link Exception}. <br>
117     * It allows to build one-line lambda for methods throwing checked exceptions.
118     * @param <T> the type of the input to the operation
119     */
120    @FunctionalInterface
121    public interface ThrowingConsumer<T> extends ThrowingConsumerExceptionAware<T, Exception>
122    {
123        // Everything is in ThrowingConsumerExceptionAware
124    }
125    
126    /**
127     * Consumer allowed to throw checked {@link Exception}. <br>
128     * It allows to build one-line lambda for methods throwing checked exceptions.
129     * @param <T> the type of the input to the operation
130     * @param <E> the type of the exception.
131     */
132    @FunctionalInterface
133    public interface ThrowingConsumerExceptionAware<T, E extends Exception>
134    {
135        /**
136         * Performs this operation on the given argument.
137         * @param t the input argument
138         * @throws Exception if something wrong occurs.
139         */
140        void accept(T t) throws E;
141    }
142
143    /**
144     * Wraps a {@link Consumer} by catching its {@link Exception} and rethrowing them as {@link LambdaException}.
145     * @param consumer the {@link Consumer} to wrap.
146     * @param <T> The type of input to the operation
147     * @return the wrapped {@link Consumer}.
148     */
149    public static <T> Consumer<T> wrapConsumer(ThrowingConsumer<T> consumer)
150    {
151        return value -> 
152        {
153            try
154            {
155                consumer.accept(value);
156            }
157            catch (Exception e)
158            {
159                if (e instanceof RuntimeException)
160                {
161                    throw (RuntimeException) e;
162                }
163                
164                throw new LambdaException(e);
165            }
166        };
167    }
168    
169    /**
170     * Some useful {@link Collector collectors}
171     */
172    public static class Collectors
173    {
174        /**
175         * Returns a {@link Collector} that accumulates elements into a
176         * {@link LinkedHashMap} whose keys and values are the result of applying the provided
177         * mapping functions to the input elements.
178         * <br>
179         * <br>This is the same as {@link java.util.stream.Collectors#toMap(Function, Function)},
180         * but elements are collected into a {@link LinkedHashMap} instead of a {@link HashMap}
181         * 
182         * @param <T> the type of the input elements
183         * @param <K> the output type of the key mapping function
184         * @param <U> the output type of the value mapping function
185         * @param keyMapper a mapping function to produce keys
186         * @param valueMapper a mapping function to produce values
187         * @return a {@code Collector} which collects elements into a {@link LinkedHashMap}
188         * whose keys and values are the result of applying mapping functions to
189         * the input elements
190         */
191        public static <T, K, U> Collector<T, ?, Map<K, U>> toLinkedHashMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
192        {
193            return java.util.stream.Collectors.toMap(keyMapper, valueMapper, _throwingMerger(), LinkedHashMap::new);
194        }
195        
196        // copied from java.util.stream.Collectors.throwingMerger()
197        private static <T> BinaryOperator<T> _throwingMerger()
198        {
199            return (u, v) ->
200            {
201                throw new IllegalStateException(String.format("Duplicate key %s", u));
202            };
203        }
204        
205        /**
206         * A convenient method for creating a {@link Collector} which accumulates elements into a {@link List},
207         * this list being transformed by the provided finisher into the final result. 
208         * @param <T> the type of the input elements
209         * @param <R> the final result type 
210         * @param finisher The finisher function for the new collector
211         * @return the new collector
212         */
213        public static <T, R> Collector<T, List<T>, R> withListAccumulation(Function<List<T>, R> finisher)
214        {
215            return Collector.of(
216                ArrayList<T>::new, 
217                (res, q) -> res.add(q), 
218                (res1, res2) ->
219                {
220                    res1.addAll(res2);
221                    return res1;
222                },
223                finisher);
224        }
225    }
226    
227    /**
228     * Some useful {@link BiPredicate biPredicates}
229     * @param <T> the type of arguments to the specified bipredicate
230     * @param <U> the type of arguments to the specified bipredicate
231     */
232    public static class BiPredicate<T, U>
233    {
234        /**
235         * Returns a bipredicate that is the negation of the supplied bipredicate.
236         * @param <T> the type of arguments to the specified bipredicate
237         * @param <U> the type of arguments to the specified bipredicate
238         * @param target bipredicate to negate
239         * @return a bipredicate that negates the results of the supplied bipredicate
240         */
241        @SuppressWarnings("unchecked")
242        public static <T, U> java.util.function.BiPredicate<T, U> not(java.util.function.BiPredicate<? super T, ? super U> target)
243        {
244            Objects.requireNonNull(target);
245            return (java.util.function.BiPredicate<T, U>) target.negate();
246        }
247    }
248    
249    /**
250     * Runtime exception wrapping the exception thrown from the lambda
251     */
252    public static class LambdaException extends RuntimeException
253    {
254        /** Constructs a new runtime exception with the specified cause and a
255         * detail message of {@code (cause==null ? null : cause.toString())}
256         * (which typically contains the class and detail message of
257         * {@code cause}).  This constructor is useful for runtime exceptions
258         * that are little more than wrappers for other throwables.
259         *
260         * @param  cause the cause (which is saved for later retrieval by the
261         *         {@link #getCause()} method).  (A {@code null} value is
262         *         permitted, and indicates that the cause is nonexistent or
263         *         unknown.)
264         */
265        public LambdaException(Throwable cause) 
266        {
267            super(cause);
268        }
269    }
270}