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, and returns the
183   * result of invoking its supplied failure handler otherwise.
184   *
185   * @param <C> the type of the criteria
186   *
187   * @param <T> the type of the elements
188   *
189   * @return a {@link Reducer}; never {@code null}
190   *
191   * @see #reduce(List, Object, BiFunction)
192   */
193  // A Reducer that works only when the selection is of size 1.
194  public static <C, T> Reducer<C, T> ofSimple() {
195    return Reducer::reduceObviously;
196  }
197
198  /**
199   * Returns a {@link Reducer} whose {@link #reduce(List, Object, BiFunction)} method, for a given {@link List} of
200   * elements, returns the result of invoking its supplied failure handler.
201   *
202   * @param <C> the type of the criteria
203   *
204   * @param <T> the type of the elements
205   *
206   * @return a {@link Reducer}; never {@code null}
207   *
208   * @see #reduce(List, Object, BiFunction)
209   */
210  // A Reducer that simply calls its supplied failure handler no matter what.
211  public static <C, T> Reducer<C, T> ofFailing() {
212    return Reducer::failUnconditionally;
213  }
214
215  /**
216   * Throws an {@link UnsatisfiedReductionException} if {@code elements} is empty, throws an {@link
217   * AmbiguousReductionException} if the {@linkplain List#size() size} of {@code elements} is greater than {@code 1},
218   * and returns the sole element of {@code elements} otherwise.
219   *
220   * <p>A reference to this method is often used as a <dfn>failure handler</dfn> in an invocation of the {@link
221   * #reduce(List, Object, BiFunction)} method.</p>
222   *
223   * @param <C> the type of the criteria
224   *
225   * @param <T> the type of the elements
226   *
227   * @param elements a {@link List} of elements under reduction; must not be {@code null}
228   *
229   * @param c a criteria object; may be {@code null}
230   *
231   * @return the sole element present in {@code elements}, which may be {@code null}
232   *
233   * @exception NullPointerException if {@code elements} is {@code null}
234   *
235   * @exception UnsatisfiedReductionException if {@code elements} is empty
236   *
237   * @exception AmbiguousReductionException if the {@linkplain List#size() size} of {@code elements} is greater than
238   * {@code 1}
239   *
240   * @see #reduce(List, Object, BiFunction)
241   *
242   * @see #reduce(List, Object)
243   */
244  // Default failure handler; call by method reference. Fails if the selection does not consist of one element.
245  public static <C, T> T fail(final List<? extends T> elements, final C c) {
246    return switch (elements.size()) {
247    case 0 -> throw new UnsatisfiedReductionException((Object)c, null, null);
248    case 1 -> elements.get(0);
249    default -> throw new AmbiguousReductionException(c, elements, "Cannot reduce: " + elements);
250    };
251  }
252
253  /**
254   * Returns {@code null} when invoked, regardless of arguments.
255   *
256   * <p>A reference to this method can be used as a <dfn>failure handler</dfn> in an invocation of the {@link
257   * #reduce(List, Object, BiFunction)} method.</p>
258   *
259   * @param <A> the type of the first parameter; ignored
260   *
261   * @param <B> the type of the second parameter; ignored
262   *
263   * @param <C> the type of the return type; ignored
264   *
265   * @param a an object; may be {@code null}; ignored
266   *
267   * @param b an object; may be {@code null}; ignored
268   *
269   * @return {@code null} when invoked
270   */
271  // Convenience failure handler; call by method reference. Returns null when invoked.
272  public static <A, B, C> C returnNull(final A a, final B b) {
273    return null;
274  }
275
276  private static <C, T> T reduceObviously(final List<? extends T> l,
277                                          final C c,
278                                          final BiFunction<? super List<? extends T>, ? super C, ? extends T> fh) {
279    return switch (l.size()) {
280    case 0 -> fh == null ? fail(List.of(), c) : fh.apply(List.of(), c);
281    case 1 -> l.get(0);
282    default -> fh == null ? fail(l, c) : fh.apply(l, c);
283    };
284  }
285
286  private static <C, T> T failUnconditionally(final List<? extends T> l,
287                                              final C c,
288                                              final BiFunction<? super List<? extends T>, ? super C, ? extends T> fh) {
289    return fh.apply(l, c);
290  }
291
292}