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}