001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024 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;
017import java.util.Map;
018
019import java.util.concurrent.ConcurrentHashMap;
020
021import java.util.function.BiFunction;
022
023/**
024 * A {@linkplain FunctionalInterface functional interface} whose implementations can either <em>reduce</em> a supplied
025 * {@link List} of elements representing a successful <em>selection</em> to a single element normally drawn or
026 * calculated from the selection according to some <em>criteria</em>, or fail gracefully in the face of ambiguity by
027 * invoking a supplied <em>failure handler</em>.
028 *
029 * <p>The reduction may be a simple filtering operation, or may be a summing or aggregating operation, or anything
030 * else.</p>
031 *
032 * <p>This interface is conceptually subordinate to, but should not be confused with, the {@link Reducible}
033 * interface.</p>
034 *
035 * <p>{@link Reducer} implementations are often used to help build {@link Reducible} implementations. See, for example,
036 * {@link Reducible#ofCaching(Selectable, Reducer, BiFunction)}.</p>
037 *
038 * @param <C> the type of criteria
039 *
040 * @param <T> the element type
041 *
042 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
043 *
044 * @see #reduce(List, Object, BiFunction)
045 *
046 * @see Reducible
047 */
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)} 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  public default T reduce(final Selectable<? super C, ? extends T> f,
096                          final C c,
097                          final BiFunction<? super List<? extends T>, ? super C, ? extends T> failureHandler) {
098    return this.reduce(f.select(c), c, failureHandler);
099  }
100
101  public default T reduce(final Selectable<? super C, ? extends T> f, final C c) {
102    return this.reduce(f.select(c), c, Reducer::fail);
103  }
104
105  public default T reduce(final List<? extends T> elements, final C c) {
106    return this.reduce(elements, c, Reducer::fail);
107  }
108
109  // Experimental. Probably overkill. Unbounded.
110  public default Reducer<C, T> cached() {
111    record Key<C, T>(List<T> l, C c) {};
112    final Map<Key<C, T>, T> cache = new ConcurrentHashMap<>();
113    return (l, c, fh) -> cache.computeIfAbsent(new Key<C, T>(List.copyOf(l), c), k -> this.reduce(k.l(), k.c(), fh));
114  }
115
116
117  /*
118   * Static methods.
119   */
120
121
122  // A Reducer that works only when the selection is of size 0 or 1.
123  public static <C, T> Reducer<C, T> ofSimple() {
124    return Reducer::reduceObviously;
125  }
126
127  // A Reducer that simply calls its supplied failure handler no matter what.
128  public static <C, T> Reducer<C, T> ofFailing() {
129    return Reducer::failUnconditionally;
130  }
131
132  // Default failure handler; call by method reference. Fails if the selection does not consist of one element.
133  public static <C, T> T fail(final List<? extends T> elements, final C c) {
134    if (elements.isEmpty()) {
135      throw new UnsatisfiedReductionException((Object)c);
136    } else if (elements.size() > 1) {
137      throw new AmbiguousReductionException(c, elements, "Cannot reduce: " + elements);
138    }
139    return elements.get(0);
140  }
141
142  // Convenience failure handler; call by method reference. Returns null when invoked.
143  public static <A, B, C> C returnNull(final A a, final B b) {
144    return null;
145  }
146
147  private static <C, T> T reduceObviously(final List<? extends T> l,
148                                          final C c,
149                                          final BiFunction<? super List<? extends T>, ? super C, ? extends T> fh) {
150    return
151      l.isEmpty() ? null :
152      l.size() == 1 ? l.get(0) :
153      fh.apply(l, c);
154  }
155
156  private static <C, T> T failUnconditionally(final List<? extends T> l,
157                                              final C c,
158                                              final BiFunction<? super List<? extends T>, ? super C, ? extends T> fh) {
159    return fh.apply(l, c);
160  }
161
162}