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.function.BinaryOperator;
024import java.util.function.Consumer;
025import java.util.function.Function;
026import java.util.function.Predicate;
027import java.util.stream.Collector;
028
029/**
030 * Helper for lambda expressions.
031 */
032public final class LambdaUtils
033{
034    private LambdaUtils()
035    {
036        // empty constructor
037    }
038    
039    /**
040     * Function allowed to throw checked {@link Exception}. <br>
041     * It allows to build one-line lambda for methods throwing checked exceptions.
042     * @param <T> the type of the input to the function.
043     * @param <R> the type of the result of the function.
044     */
045    @FunctionalInterface
046    public interface ThrowingFunction<T, R>
047    {
048        /**
049         * Applies this function to the given argument.
050         * @param t the function argument
051         * @return the function result
052         * @throws Exception if something wrong occurs.
053         */
054        R apply(T t) throws Exception;
055    }
056    
057    /**
058     * Wraps a {@link Function} by catching its {@link Exception} and rethrowing them as {@link RuntimeException}.
059     * @param function the {@link Function} to wrap.
060     * @param <T> The type of input to the function
061     * @param <R> The type of result of the function
062     * @return the wrapped {@link Function}.
063     */
064    public static <T, R> Function<T, R> wrap(ThrowingFunction<T, R> function)
065    {
066        return value -> 
067        {
068            try
069            {
070                return function.apply(value);
071            }
072            catch (Exception e)
073            {
074                if (e instanceof RuntimeException)
075                {
076                    throw (RuntimeException) e;
077                }
078                
079                throw new RuntimeException(e);
080            }
081        };
082    }
083    
084    /**
085     * Predicate allowed to throw checked {@link Exception}. <br>
086     * It allows to build one-line lambda for methods throwing checked exceptions.
087     * @param <T> the type of the input to the predicate.
088     */
089    @FunctionalInterface
090    public interface ThrowingPredicate<T>
091    {
092        /**
093         * Evaluates this predicate on the given argument.
094         * @param t the input argument
095         * @return {@code true} if the input argument matches the predicate,
096         * otherwise {@code false}
097         * @throws Exception if something wrong occurs.
098         */
099        boolean test(T t) throws Exception;
100    }
101    
102    /**
103     * Wraps a {@link Predicate} by catching its {@link Exception} and rethrowing them as {@link RuntimeException}.
104     * @param predicate the {@link Predicate} to wrap.
105     * @param <T> the type of the input to the predicate
106     * @return the wrapped {@link Predicate}.
107     */
108    public static <T> Predicate<T> wrapPredicate(ThrowingPredicate<T> predicate)
109    {
110        return LambdaUtils.wrap(predicate::test)::apply;
111        
112    }
113    
114    /**
115     * Consumer allowed to throw checked {@link Exception}. <br>
116     * It allows to build one-line lambda for methods throwing checked exceptions.
117     * @param <T> the type of the input to the operation
118     */
119    @FunctionalInterface
120    public interface ThrowingConsumer<T>
121    {
122        /**
123         * Performs this operation on the given argument.
124         * @param t the input argument
125         * @throws Exception if something wrong occurs.
126         */
127        void accept(T t) throws Exception;
128    }
129    
130    /**
131     * Wraps a {@link Consumer} by catching its {@link Exception} and rethrowing them as {@link RuntimeException}.
132     * @param consumer the {@link Consumer} to wrap.
133     * @param <T> The type of input to the operation
134     * @return the wrapped {@link Consumer}.
135     */
136    public static <T> Consumer<T> wrapConsumer(ThrowingConsumer<T> consumer)
137    {
138        return value -> 
139        {
140            try
141            {
142                consumer.accept(value);
143            }
144            catch (Exception e)
145            {
146                if (e instanceof RuntimeException)
147                {
148                    throw (RuntimeException) e;
149                }
150                
151                throw new RuntimeException(e);
152            }
153        };
154    }
155    
156    /**
157     * Some useful {@link Collector collectors}
158     */
159    public static class Collectors
160    {
161        /**
162         * Returns a {@link Collector} that accumulates elements into a
163         * {@link LinkedHashMap} whose keys and values are the result of applying the provided
164         * mapping functions to the input elements.
165         * <br>
166         * <br>This is the same as {@link java.util.stream.Collectors#toMap(Function, Function)},
167         * but elements are collected into a {@link LinkedHashMap} instead of a {@link HashMap}
168         * 
169         * @param <T> the type of the input elements
170         * @param <K> the output type of the key mapping function
171         * @param <U> the output type of the value mapping function
172         * @param keyMapper a mapping function to produce keys
173         * @param valueMapper a mapping function to produce values
174         * @return a {@code Collector} which collects elements into a {@link LinkedHashMap}
175         * whose keys and values are the result of applying mapping functions to
176         * the input elements
177         */
178        public static <T, K, U> Collector<T, ?, Map<K, U>> toLinkedHashMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
179        {
180            return java.util.stream.Collectors.toMap(keyMapper, valueMapper, _throwingMerger(), LinkedHashMap::new);
181        }
182        
183        // copied from java.util.stream.Collectors.throwingMerger()
184        private static <T> BinaryOperator<T> _throwingMerger()
185        {
186            return (u, v) ->
187            {
188                throw new IllegalStateException(String.format("Duplicate key %s", u));
189            };
190        }
191        
192        /**
193         * A convenient method for creating a {@link Collector} which accumulates elements into a {@link List},
194         * this list being transformed by the provided finisher into the final result. 
195         * @param <T> the type of the input elements
196         * @param <R> the final result type 
197         * @param finisher The finisher function for the new collector
198         * @return the new collector
199         */
200        public static <T, R> Collector<T, List<T>, R> withListAccumulation(Function<List<T>, R> finisher)
201        {
202            return Collector.of(
203                ArrayList<T>::new, 
204                (res, q) -> res.add(q), 
205                (res1, res2) ->
206                {
207                    res1.addAll(res2);
208                    return res1;
209                },
210                finisher);
211        }
212    }
213}