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}