001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2024–2025 microBean™. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 006 * the License. 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 distributed under the License is distributed on 011 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 012 * specific language governing permissions and limitations under the License. 013 */ 014package org.microbean.bean; 015 016import java.util.List; 017 018import java.util.function.BiFunction; 019 020/** 021 * A {@linkplain FunctionalInterface functional interface} whose implementations can either <em>reduce</em> a supplied 022 * {@link List} of elements representing a successful <em>selection</em> to a single element normally drawn or 023 * calculated from the selection according to some <em>criteria</em>, or fail gracefully in the face of ambiguity by 024 * invoking a supplied <em>failure handler</em>. 025 * 026 * <p>The reduction may be a simple filtering operation, or may be a summing or aggregating operation, or anything 027 * else.</p> 028 * 029 * <p>This interface is conceptually subordinate to, but should not be confused with, the {@link Reducible} 030 * interface.</p> 031 * 032 * <p>{@link Reducer} implementations are often used to help build {@link Reducible} implementations. See, for example, 033 * {@link Reducible#ofCaching(Selectable, Reducer, BiFunction)}.</p> 034 * 035 * @param <C> the type of criteria 036 * 037 * @param <T> the element type 038 * 039 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 040 * 041 * @see #reduce(List, Object, BiFunction) 042 * 043 * @see Reducible 044 */ 045@FunctionalInterface 046public interface Reducer<C, T> { 047 048 /** 049 * Performs some kind of reductive or filtering operation on the supplied {@link List}, according to the supplied 050 * criteria, and returns the single result, or, if reduction fails, invokes the supplied {@link BiFunction} with a 051 * sublist representing a partial reduction (or an empty list representing a reduction that simply could not be 052 * performed), along with the supplied criteria, and returns its result. 053 * 054 * <p>Implementations of this method must return determinate values.</p> 055 * 056 * @param elements an immutable {@link List} to reduce; must not be {@code null}; represents a successful selection 057 * from a larger collection of elements 058 * 059 * @param c the criteria effectively describing the initial selection and the desired reduction; may be {@code null} 060 * to indicate no criteria; may be ignored if not needed by an implementation 061 * 062 * @param failureHandler a {@link BiFunction} receiving a failed reduction (usually a portion of the supplied {@code 063 * elements}), and the selection and reduction criteria, that returns a substitute reduction (or, more commonly, 064 * throws an exception); must not be {@code null}; must be invoked if reduction fails or undefined behavior may result 065 * 066 * @return a single, possibly {@code null}, element normally drawn or computed from the supplied {@code elements}, or 067 * a synthetic value returned by an invocation of the supplied {@code failureHandler}'s {@link 068 * BiFunction#apply(Object, Object) apply(Object, Object)} method 069 * 070 * @exception NullPointerException if {@code elements} or {@code failureHandler} is {@code null} 071 * 072 * @exception ReductionException if the {@code failureHandler} function throws a {@link ReductionException} 073 * 074 * @see #fail(List, Object) 075 */ 076 // List, not Stream, for equality semantics and caching purposes. 077 // List, not Set, because it's much faster and reduction can take care of duplicates if needed 078 // List, not Collection, because you want easy access to the (possibly) only element without creating iterators 079 // C, not Predicate, because it may not be necessary to actually filter the List to perform the reduction 080 // failureHandler will receive only those elements that could not be eliminated 081 // c is a pass-through used only during failure 082 public T reduce(final List<? extends T> elements, 083 final C c, 084 final BiFunction<? super List<? extends T>, ? super C, ? extends T> failureHandler); 085 086 087 /* 088 * Default methods. 089 */ 090 091 092 /** 093 * Invokes the {@link #reduce(List, Object, BiFunction)} method with the return value of an invocation of the {@link 094 * Selectable#select(Object) select(Object)} method on the supplied {@link Selectable} supplied with {@code c}, and 095 * the supplied {@code c} and {@code failureHandler} arguments, and returns the result. 096 * 097 * @param f a {@link Selectable}; must not be {@code null} 098 * 099 * @param c the criteria effectively describing the initial selection and the desired reduction; may be {@code null} 100 * to indicate no criteria; may be ignored if not needed by an implementation 101 * 102 * @param failureHandler a {@link BiFunction} receiving a failed reduction (usually a portion of the supplied {@code 103 * elements}), and the selection and reduction criteria, that returns a substitute reduction (or, more commonly, 104 * throws an exception); must not be {@code null}; must be invoked if reduction fails or undefined behavior may result 105 * 106 * @return a single, possibly {@code null}, element normally drawn or computed from the {@link List} returned from the 107 * supplied {@link Selectable}'s {@link Selectable#select(Object) select(Object)} method, or a synthetic value 108 * returned by an invocation of the supplied {@code failureHandler}'s {@link BiFunction#apply(Object, Object) 109 * apply(Object, Object)} method 110 * 111 * @exception NullPointerException if {@code f} or {@code failureHandler} is {@code null} 112 * 113 * @see #reduce(List, Object, BiFunction) 114 */ 115 public default T reduce(final Selectable<? super C, ? extends T> f, 116 final C c, 117 final BiFunction<? super List<? extends T>, ? super C, ? extends T> failureHandler) { 118 return this.reduce(f.select(c), c, failureHandler); 119 } 120 121 /** 122 * Invokes the {@link #reduce(List, Object, BiFunction)} method with the return value of an invocation of the {@link 123 * Selectable#select(Object) select(Object)} method on the supplied {@link Selectable} supplied with {@code c}, and 124 * the supplied {@code c} and a reference to the {@link #fail(List, Object) fail(List, Object)} method, and returns 125 * the result. 126 * 127 * @param f a {@link Selectable}; must not be {@code null} 128 * 129 * @param c the criteria effectively describing the initial selection and the desired reduction; may be {@code null} 130 * to indicate no criteria; may be ignored if not needed by an implementation 131 * 132 * @return a single, possibly {@code null}, element normally drawn or computed from the {@link List} returned from the 133 * supplied {@link Selectable}'s {@link Selectable#select(Object) select(Object)} method, or the sole element 134 * returned by an invocation of the {@link #fail(List, Object) fail(List, Object)} method 135 * 136 * @exception NullPointerException if {@code f} or {@code failureHandler} is {@code null} 137 * 138 * @exception UnsatisfiedReductionException if an invocation of the {@link #fail(List, Object) fail(List, Object)} method throws an {@link UnsatisfiedReductionException} 139 * 140 * @exception AmbiguousReductionException if an invocation of the {@link #fail(List, Object) fail(List, Object)} method throws an {@link AmbiguousReductionException} 141 * 142 * @see #reduce(Selectable, Object, BiFunction) 143 * 144 * @see #fail(List, Object) 145 */ 146 public default T reduce(final Selectable<? super C, ? extends T> f, final C c) { 147 return this.reduce(f, c, Reducer::fail); 148 } 149 150 /** 151 * Invokes the {@link #reduce(List, Object, BiFunction)} method with the supplied arguments and a reference to the 152 * {@link #fail(List, Object)} method, and returns the result. 153 * 154 * @param elements an immutable {@link List} to reduce; must not be {@code null}; represents a successful selection 155 * from a larger collection of elements 156 * 157 * @param c the criteria effectively describing the initial selection and the desired reduction; may be {@code null} 158 * to indicate no criteria; may be ignored if not needed by an implementation 159 * 160 * @return a single, possibly {@code null}, element normally drawn or computed from the supplied {@code elements}, or 161 * a synthetic value returned by an invocation of the supplied {@code failureHandler}'s {@link 162 * BiFunction#apply(Object, Object) apply(Object, Object)} method 163 * 164 * @exception NullPointerException if {@code elements} is {@code null} 165 * 166 * @see #fail(List, Object) 167 * 168 * @see #reduce(List, Object, BiFunction) 169 */ 170 public default T reduce(final List<? extends T> elements, final C c) { 171 return this.reduce(elements, c, Reducer::fail); 172 } 173 174 175 /* 176 * Static methods. 177 */ 178 179 180 /** 181 * Returns a {@link Reducer} whose {@link #reduce(List, Object, BiFunction)} method, for a given {@link List} of 182 * elements, returns the {@link List}'s sole element if the {@link List} has exactly one element, returns {@code null} 183 * if the {@link List} {@linkplain List#isEmpty() is empty}, and returns the result of invoking its supplied failure 184 * handler otherwise. 185 * 186 * @param <C> the type of the criteria 187 * 188 * @param <T> the type of the elements 189 * 190 * @return a {@link Reducer}; never {@code null} 191 * 192 * @see #reduce(List, Object, BiFunction) 193 */ 194 // A Reducer that works only when the selection is of size 0 or 1. 195 public static <C, T> Reducer<C, T> ofSimple() { 196 return Reducer::reduceObviously; 197 } 198 199 /** 200 * Returns a {@link Reducer} whose {@link #reduce(List, Object, BiFunction)} method, for a given {@link List} of 201 * elements, returns the result of invoking its supplied failure handler. 202 * 203 * @param <C> the type of the criteria 204 * 205 * @param <T> the type of the elements 206 * 207 * @return a {@link Reducer}; never {@code null} 208 * 209 * @see #reduce(List, Object, BiFunction) 210 */ 211 // A Reducer that simply calls its supplied failure handler no matter what. 212 public static <C, T> Reducer<C, T> ofFailing() { 213 return Reducer::failUnconditionally; 214 } 215 216 /** 217 * Throws an {@link UnsatisfiedReductionException} if {@code elements} is empty, throws an {@link 218 * AmbiguousReductionException} if the {@linkplain List#size() size} of {@code elements} is greater than {@code 1}, 219 * and returns the sole element of {@code elements} otherwise. 220 * 221 * <p>A reference to this method is often used as a <dfn>failure handler</dfn> in an invocation of the {@link 222 * #reduce(List, Object, BiFunction)} method.</p> 223 * 224 * @param <C> the type of the criteria 225 * 226 * @param <T> the type of the elements 227 * 228 * @param elements a {@link List} of elements under reduction; must not be {@code null} 229 * 230 * @param c a criteria object; may be {@code null} 231 * 232 * @return the sole element present in {@code elements}, which may be {@code null} 233 * 234 * @exception NullPointerException if {@code elements} is {@code null} 235 * 236 * @exception UnsatisfiedReductionException if {@code elements} is empty 237 * 238 * @exception AmbiguousReductionException if the {@linkplain List#size() size} of {@code elements} is greater than 239 * {@code 1} 240 * 241 * @see #reduce(List, Object, BiFunction) 242 * 243 * @see #reduce(List, Object) 244 */ 245 // Default failure handler; call by method reference. Fails if the selection does not consist of one element. 246 public static <C, T> T fail(final List<? extends T> elements, final C c) { 247 if (elements.isEmpty()) { 248 throw new UnsatisfiedReductionException((Object)c, null, null); 249 } else if (elements.size() > 1) { 250 throw new AmbiguousReductionException(c, elements, "Cannot reduce: " + elements); 251 } 252 return elements.get(0); 253 } 254 255 /** 256 * Returns {@code null} when invoked, regardless of arguments. 257 * 258 * <p>A reference to this method can be used as a <dfn>failure handler</dfn> in an invocation of the {@link 259 * #reduce(List, Object, BiFunction)} method.</p> 260 * 261 * @param <A> the type of the first parameter; ignored 262 * 263 * @param <B> the type of the second parameter; ignored 264 * 265 * @param <C> the type of the return type; ignored 266 * 267 * @param a an object; may be {@code null}; ignored 268 * 269 * @param b an object; may be {@code null}; ignored 270 * 271 * @return {@code null} when invoked 272 */ 273 // Convenience failure handler; call by method reference. Returns null when invoked. 274 public static <A, B, C> C returnNull(final A a, final B b) { 275 return null; 276 } 277 278 private static <C, T> T reduceObviously(final List<? extends T> l, 279 final C c, 280 final BiFunction<? super List<? extends T>, ? super C, ? extends T> fh) { 281 return 282 l.isEmpty() ? null : 283 l.size() == 1 ? l.get(0) : 284 fh.apply(l, c); 285 } 286 287 private static <C, T> T failUnconditionally(final List<? extends T> l, 288 final C c, 289 final BiFunction<? super List<? extends T>, ? super C, ? extends T> fh) { 290 return fh.apply(l, c); 291 } 292 293}