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>
122    {
123        /**
124         * Performs this operation on the given argument.
125         * @param t the input argument
126         * @throws Exception if something wrong occurs.
127         */
128        void accept(T t) throws Exception;
129    }
130    
131    /**
132     * Wraps a {@link Consumer} by catching its {@link Exception} and rethrowing them as {@link LambdaException}.
133     * @param consumer the {@link Consumer} to wrap.
134     * @param <T> The type of input to the operation
135     * @return the wrapped {@link Consumer}.
136     */
137    public static <T> Consumer<T> wrapConsumer(ThrowingConsumer<T> consumer)
138    {
139        return value -> 
140        {
141            try
142            {
143                consumer.accept(value);
144            }
145            catch (Exception e)
146            {
147                if (e instanceof RuntimeException)
148                {
149                    throw (RuntimeException) e;
150                }
151                
152                throw new LambdaException(e);
153            }
154        };
155    }
156    
157    /**
158     * Some useful {@link Collector collectors}
159     */
160    public static class Collectors
161    {
162        /**
163         * Returns a {@link Collector} that accumulates elements into a
164         * {@link LinkedHashMap} whose keys and values are the result of applying the provided
165         * mapping functions to the input elements.
166         * <br>
167         * <br>This is the same as {@link java.util.stream.Collectors#toMap(Function, Function)},
168         * but elements are collected into a {@link LinkedHashMap} instead of a {@link HashMap}
169         * 
170         * @param <T> the type of the input elements
171         * @param <K> the output type of the key mapping function
172         * @param <U> the output type of the value mapping function
173         * @param keyMapper a mapping function to produce keys
174         * @param valueMapper a mapping function to produce values
175         * @return a {@code Collector} which collects elements into a {@link LinkedHashMap}
176         * whose keys and values are the result of applying mapping functions to
177         * the input elements
178         */
179        public static <T, K, U> Collector<T, ?, Map<K, U>> toLinkedHashMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
180        {
181            return java.util.stream.Collectors.toMap(keyMapper, valueMapper, _throwingMerger(), LinkedHashMap::new);
182        }
183        
184        // copied from java.util.stream.Collectors.throwingMerger()
185        private static <T> BinaryOperator<T> _throwingMerger()
186        {
187            return (u, v) ->
188            {
189                throw new IllegalStateException(String.format("Duplicate key %s", u));
190            };
191        }
192        
193        /**
194         * A convenient method for creating a {@link Collector} which accumulates elements into a {@link List},
195         * this list being transformed by the provided finisher into the final result. 
196         * @param <T> the type of the input elements
197         * @param <R> the final result type 
198         * @param finisher The finisher function for the new collector
199         * @return the new collector
200         */
201        public static <T, R> Collector<T, List<T>, R> withListAccumulation(Function<List<T>, R> finisher)
202        {
203            return Collector.of(
204                ArrayList<T>::new, 
205                (res, q) -> res.add(q), 
206                (res1, res2) ->
207                {
208                    res1.addAll(res2);
209                    return res1;
210                },
211                finisher);
212        }
213    }
214    
215    /**
216     * Some useful {@link BiPredicate biPredicates}
217     * @param <T> the type of arguments to the specified bipredicate
218     * @param <U> the type of arguments to the specified bipredicate
219     */
220    public static class BiPredicate<T, U>
221    {
222        /**
223         * Returns a bipredicate that is the negation of the supplied bipredicate.
224         * @param <T> the type of arguments to the specified bipredicate
225         * @param <U> the type of arguments to the specified bipredicate
226         * @param target bipredicate to negate
227         * @return a bipredicate that negates the results of the supplied bipredicate
228         */
229        @SuppressWarnings("unchecked")
230        public static <T, U> java.util.function.BiPredicate<T, U> not(java.util.function.BiPredicate<? super T, ? super U> target)
231        {
232            Objects.requireNonNull(target);
233            return (java.util.function.BiPredicate<T, U>) target.negate();
234        }
235    }
236    
237    /**
238     * Runtime exception wrapping the exception thrown from the lambda
239     */
240    public static class LambdaException extends RuntimeException
241    {
242        /** Constructs a new runtime exception with the specified cause and a
243         * detail message of {@code (cause==null ? null : cause.toString())}
244         * (which typically contains the class and detail message of
245         * {@code cause}).  This constructor is useful for runtime exceptions
246         * that are little more than wrappers for other throwables.
247         *
248         * @param  cause the cause (which is saved for later retrieval by the
249         *         {@link #getCause()} method).  (A {@code null} value is
250         *         permitted, and indicates that the cause is nonexistent or
251         *         unknown.)
252         */
253        public LambdaException(Throwable cause) 
254        {
255            super(cause);
256        }
257    }
258}