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 * @deprecated This interface is not really needed and is tentatively deprecated.
046 */
047@Deprecated(since = "0.0.18")
048@FunctionalInterface
049public interface Reducer<C, T> {
050
051  /**
052   * Performs some kind of reductive or filtering operation on the supplied {@link List}, according to the supplied
053   * criteria, and returns the single result, or, if reduction fails, invokes the supplied {@link BiFunction} with a
054   * sublist representing a partial reduction (or an empty list representing a reduction that simply could not be
055   * performed), along with the supplied criteria, and returns its result.
056   *
057   * <p>Implementations of this method must return determinate values.</p>
058   *
059   * @param elements an immutable {@link List} to reduce; must not be {@code null}; represents a successful selection
060   * from a larger collection of elements
061   *
062   * @param c the criteria effectively describing the initial selection and the desired reduction; may be {@code null}
063   * to indicate no criteria; may be ignored if not needed by an implementation
064   *
065   * @param failureHandler a {@link BiFunction} receiving a failed reduction (usually a portion of the supplied {@code
066   * elements}), and the selection and reduction criteria, that returns a substitute reduction (or, more commonly,
067   * throws an exception); must not be {@code null}; must be invoked if reduction fails or undefined behavior may result
068   *
069   * @return a single, possibly {@code null}, element normally drawn or computed from the supplied {@code elements}, or
070   * a synthetic value returned by an invocation of the supplied {@code failureHandler}'s {@link
071   * BiFunction#apply(Object, Object) apply(Object, Object)} method
072   *
073   * @exception NullPointerException if {@code elements} or {@code failureHandler} is {@code null}
074   *
075   * @exception ReductionException if the {@code failureHandler} function throws a {@link ReductionException}
076   *
077   * @see #fail(List, Object)
078   */
079  // List, not Stream, for equality semantics and caching purposes.
080  // List, not Set, because it's much faster and reduction can take care of duplicates if needed
081  // List, not Collection, because you want easy access to the (possibly) only element without creating iterators
082  // C, not Predicate, because it may not be necessary to actually filter the List to perform the reduction
083  // failureHandler will receive only those elements that could not be eliminated
084  // c is a pass-through used only during failure
085  public T reduce(final List<? extends T> elements,
086                  final C c,
087                  final BiFunction<? super List<? extends T>, ? super C, ? extends T> failureHandler);
088
089
090  /*
091   * Default methods.
092   */
093
094
095  /**
096   * Invokes the {@link #reduce(List, Object, BiFunction)} method with the return value of an invocation of the {@link
097   * Selectable#select(Object) select(Object)} method on the supplied {@link Selectable} supplied with {@code c}, and
098   * the supplied {@code c} and {@code failureHandler} arguments, and returns the result.
099   *
100   * @param f a {@link Selectable}; must not be {@code null}
101   *
102   * @param c the criteria effectively describing the initial selection and the desired reduction; may be {@code null}
103   * to indicate no criteria; may be ignored if not needed by an implementation
104   *
105   * @param failureHandler a {@link BiFunction} receiving a failed reduction (usually a portion of the supplied {@code
106   * elements}), and the selection and reduction criteria, that returns a substitute reduction (or, more commonly,
107   * throws an exception); must not be {@code null}; must be invoked if reduction fails or undefined behavior may result
108   *
109   * @return a single, possibly {@code null}, element normally drawn or computed from the {@link List} returned from the
110   * supplied {@link Selectable}'s {@link Selectable#select(Object) select(Object)} method, or a synthetic value
111   * returned by an invocation of the supplied {@code failureHandler}'s {@link BiFunction#apply(Object, Object)
112   * apply(Object, Object)} method
113   *
114   * @exception NullPointerException if {@code f} or {@code failureHandler} is {@code null}
115   *
116   * @see #reduce(List, Object, BiFunction)
117   */
118  public default T reduce(final Selectable<? super C, ? extends T> f,
119                          final C c,
120                          final BiFunction<? super List<? extends T>, ? super C, ? extends T> failureHandler) {
121    return this.reduce(f.select(c), c, failureHandler);
122  }
123
124  /**
125   * Invokes the {@link #reduce(List, Object, BiFunction)} method with the return value of an invocation of the {@link
126   * Selectable#select(Object) select(Object)} method on the supplied {@link Selectable} supplied with {@code c}, and
127   * the supplied {@code c} and a reference to the {@link #fail(List, Object) fail(List, Object)} method, and returns
128   * the result.
129   *
130   * @param f a {@link Selectable}; must not be {@code null}
131   *
132   * @param c the criteria effectively describing the initial selection and the desired reduction; may be {@code null}
133   * to indicate no criteria; may be ignored if not needed by an implementation
134   *
135   * @return a single, possibly {@code null}, element normally drawn or computed from the {@link List} returned from the
136   * supplied {@link Selectable}'s {@link Selectable#select(Object) select(Object)} method, or the sole element
137   * returned by an invocation of the {@link #fail(List, Object) fail(List, Object)} method
138   *
139   * @exception NullPointerException if {@code f} or {@code failureHandler} is {@code null}
140   *
141   * @exception UnsatisfiedReductionException if an invocation of the {@link #fail(List, Object) fail(List, Object)} method throws an {@link UnsatisfiedReductionException}
142   *
143   * @exception AmbiguousReductionException if an invocation of the {@link #fail(List, Object) fail(List, Object)} method throws an {@link AmbiguousReductionException}
144   *
145   * @see #reduce(Selectable, Object, BiFunction)
146   *
147   * @see #fail(List, Object)
148   */
149  public default T reduce(final Selectable<? super C, ? extends T> f, final C c) {
150    return this.reduce(f, c, Reducer::fail);
151  }
152
153  /**
154   * Invokes the {@link #reduce(List, Object, BiFunction)} method with the supplied arguments and a reference to the
155   * {@link #fail(List, Object)} method, and returns the result.
156   *
157   * @param elements an immutable {@link List} to reduce; must not be {@code null}; represents a successful selection
158   * from a larger collection of elements
159   *
160   * @param c the criteria effectively describing the initial selection and the desired reduction; may be {@code null}
161   * to indicate no criteria; may be ignored if not needed by an implementation
162   *
163   * @return a single, possibly {@code null}, element normally drawn or computed from the supplied {@code elements}, or
164   * a synthetic value returned by an invocation of the supplied {@code failureHandler}'s {@link
165   * BiFunction#apply(Object, Object) apply(Object, Object)} method
166   *
167   * @exception NullPointerException if {@code elements} is {@code null}
168   *
169   * @see #fail(List, Object)
170   *
171   * @see #reduce(List, Object, BiFunction)
172   */
173  public default T reduce(final List<? extends T> elements, final C c) {
174    return this.reduce(elements, c, Reducer::fail);
175  }
176
177
178  /*
179   * Static methods.
180   */
181
182
183  /**
184   * Returns a {@link Reducer} whose {@link #reduce(List, Object, BiFunction)} method, for a given {@link List} of
185   * elements, returns the {@link List}'s sole element if the {@link List} has exactly one element, and returns the
186   * result of invoking its supplied failure handler otherwise.
187   *
188   * @param <C> the type of the criteria
189   *
190   * @param <T> the type of the elements
191   *
192   * @return a {@link Reducer}; never {@code null}
193   *
194   * @see #reduce(List, Object, BiFunction)
195   */
196  // A Reducer that works only when the selection is of size 1.
197  public static <C, T> Reducer<C, T> ofSimple() {
198    return Reducer::reduceObviously;
199  }
200
201  /**
202   * Returns a {@link Reducer} whose {@link #reduce(List, Object, BiFunction)} method, for a given {@link List} of
203   * elements, returns the result of invoking its supplied failure handler.
204   *
205   * @param <C> the type of the criteria
206   *
207   * @param <T> the type of the elements
208   *
209   * @return a {@link Reducer}; never {@code null}
210   *
211   * @see #reduce(List, Object, BiFunction)
212   */
213  // A Reducer that simply calls its supplied failure handler no matter what.
214  public static <C, T> Reducer<C, T> ofFailing() {
215    return Reducer::failUnconditionally;
216  }
217
218  /**
219   * Throws an {@link UnsatisfiedReductionException} if {@code elements} is empty, throws an {@link
220   * AmbiguousReductionException} if the {@linkplain List#size() size} of {@code elements} is greater than {@code 1},
221   * and returns the sole element of {@code elements} otherwise.
222   *
223   * <p>A reference to this method is often used as a <dfn>failure handler</dfn> in an invocation of the {@link
224   * #reduce(List, Object, BiFunction)} method.</p>
225   *
226   * @param <C> the type of the criteria
227   *
228   * @param <T> the type of the elements
229   *
230   * @param elements a {@link List} of elements under reduction; must not be {@code null}
231   *
232   * @param c a criteria object; may be {@code null}
233   *
234   * @return the sole element present in {@code elements}, which may be {@code null}
235   *
236   * @exception NullPointerException if {@code elements} is {@code null}
237   *
238   * @exception UnsatisfiedReductionException if {@code elements} is empty
239   *
240   * @exception AmbiguousReductionException if the {@linkplain List#size() size} of {@code elements} is greater than
241   * {@code 1}
242   *
243   * @see #reduce(List, Object, BiFunction)
244   *
245   * @see #reduce(List, Object)
246   */
247  // Default failure handler; call by method reference. Fails if the selection does not consist of one element.
248  public static <C, T> T fail(final List<? extends T> elements, final C c) {
249    return switch (elements.size()) {
250    case 0 -> throw new UnsatisfiedReductionException((Object)c, null, null);
251    case 1 -> elements.get(0);
252    default -> throw new AmbiguousReductionException(c, elements, "Cannot reduce: " + elements);
253    };
254  }
255
256  /**
257   * Returns {@code null} when invoked, regardless of arguments.
258   *
259   * <p>A reference to this method can be used as a <dfn>failure handler</dfn> in an invocation of the {@link
260   * #reduce(List, Object, BiFunction)} method.</p>
261   *
262   * @param <A> the type of the first parameter; ignored
263   *
264   * @param <B> the type of the second parameter; ignored
265   *
266   * @param <C> the type of the return type; ignored
267   *
268   * @param a an object; may be {@code null}; ignored
269   *
270   * @param b an object; may be {@code null}; ignored
271   *
272   * @return {@code null} when invoked
273   */
274  // Convenience failure handler; call by method reference. Returns null when invoked.
275  public static <A, B, C> C returnNull(final A a, final B b) {
276    return null;
277  }
278
279  private static <C, T> T reduceObviously(final List<? extends T> l,
280                                          final C c,
281                                          final BiFunction<? super List<? extends T>, ? super C, ? extends T> fh) {
282    return switch (l.size()) {
283    case 0 -> fh == null ? fail(List.of(), c) : fh.apply(List.of(), c);
284    case 1 -> l.get(0);
285    default -> fh == null ? fail(l, c) : fh.apply(l, c);
286    };
287  }
288
289  private static <C, T> T failUnconditionally(final List<? extends T> l,
290                                              final C c,
291                                              final BiFunction<? super List<? extends T>, ? super C, ? extends T> fh) {
292    return fh.apply(l, c);
293  }
294
295}