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}