001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2025–2026 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.assign;
015
016import java.util.ArrayList;
017import java.util.Collection;
018import java.util.List;
019import java.util.Map;
020import java.util.Objects;
021
022import java.util.concurrent.ConcurrentHashMap;
023
024import java.util.function.BiFunction;
025import java.util.function.BiPredicate;
026import java.util.function.Function;
027import java.util.function.Predicate;
028
029import javax.lang.model.AnnotatedConstruct;
030
031import javax.lang.model.element.AnnotationMirror;
032import javax.lang.model.element.Element;
033import javax.lang.model.element.ExecutableElement;
034
035import static java.util.Objects.requireNonNull;
036
037/**
038 * Utility methods for working with {@link Selectable}s.
039 *
040 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
041 *
042 * @see Selectable
043 */
044public final class Selectables {
045
046  private Selectables() {
047    super();
048  }
049
050  /**
051   * Returns a {@link Selectable} that caches its results.
052   *
053   * <p>The cache is unbounded.</p>
054   *
055   * @param <C> the criteria type
056   *
057   * @param <E> the element type
058   *
059   * @param selectable a {@link Selectable}; must not be {@code null}
060   *
061   * @return a non-{@code null} {@link Selectable}
062   *
063   * @exception NullPointerException if {@code selectable} is {@code null}
064   *
065   * @see #caching(Selectable, BiFunction)
066   */
067  public static <C, E> Selectable<C, E> caching(final Selectable<? super C, E> selectable) {
068    final Map<C, List<E>> selectionCache = new ConcurrentHashMap<>();
069    return Selectables.<C, E>caching(selectable, selectionCache::computeIfAbsent);
070  }
071
072  /**
073   * Returns a {@link Selectable} that caches its results.
074   *
075   * @param <C> the criteria type
076   *
077   * @param <E> the element type
078   *
079   * @param selectable a {@link Selectable}; must not be {@code null}
080   *
081   * @param f a {@link BiFunction} that returns a cached result, computing it on demand via its supplied mapping {@link
082   * Function} if necessary; must not be {@code null}; normally safe for concurrent use by multiple threads; often a
083   * reference to the {@link ConcurrentHashMap#computeIfAbsent(Object, Function)} method
084   *
085   * @return a non-{@code null} {@link Selectable}
086   *
087   * @exception NullPointerException if {@code selectable} or {@code f} is {@code null}
088   *
089   * @see ConcurrentHashMap#computeIfAbsent(Object, Function)
090   */
091  public static <C, E> Selectable<C, E> caching(final Selectable<? super C, E> selectable,
092                                                final BiFunction<? super C, Function<? super C, ? extends List<E>>, ? extends List<E>> f) {
093    return c -> f.apply(c, selectable::select);
094  }
095  
096  /**
097   * An <strong>experimental</strong> method that converts a {@link Selectable} accepting {@link Annotated
098   * Annotated&lt;AnnotatedConstruct&gt;} instances into a {@link Selectable} accepting {@link AnnotatedConstruct}
099   * instances.
100   *
101   * @param <C> the criteria type
102   *
103   * @param <E> the element type
104   *
105   * @param s a non-{@code null} {@link Selectable} accepting {@link Annotated Annotated&lt;AnnotatedConstruct&gt;}
106   * instances
107   *
108   * @return a non-{@code null}, determinate {@link Selectable} accepting {@link AnnotatedConstruct} instances
109   *
110   * @exception NullPointerException if {@code s} is {@code null}
111   *
112   * @see #convert(Selectable, Predicate)
113   *
114   * @see Annotated
115   *
116   * @see AnnotatedConstruct
117   */
118  @Deprecated(forRemoval = true) // Annotated.of(AnnotatedConstruct) exists
119  public static <C extends AnnotatedConstruct, E> Selectable<C, E> convert(final Selectable<? super Annotated<C>, E> s) {
120    return convert(s, null);
121  }
122  
123  /**
124   * An <strong>experimental</strong> method that converts a {@link Selectable} accepting {@link Annotated
125   * Annotated&lt;AnnotatedConstruct&gt;} instances into a {@link Selectable} accepting {@link AnnotatedConstruct}
126   * instances.
127   *
128   * @param <C> the criteria type
129   *
130   * @param <E> the element type
131   *
132   * @param s a non-{@code null} {@link Selectable} accepting {@link Annotated Annotated&lt;AnnotatedConstruct&gt;}
133   * instances
134   *
135   * @param annotationElementInclusionPredicate a {@link Predicate} that returns {@code true} if a given {@link
136   * ExecutableElement}, representing an annotation element, is to be included in comparison operations; may be {@code
137   * null} in which case it is as if {@code e -> true} were supplied instead
138   *
139   * @return a non-{@code null}, determinate {@link Selectable} accepting {@link AnnotatedConstruct} instances
140   *
141   * @exception NullPointerException if {@code s} is {@code null}
142   *
143   * @see Annotated
144   *
145   * @see Annotated#of(AnnotatedConstruct, Predicate)
146   *
147   * @see AnnotatedConstruct
148   */
149  @Deprecated(forRemoval = true) // Annotated.of(AnnotatedConstruct) exists
150  public static <C extends AnnotatedConstruct, E> Selectable<C, E> convert(final Selectable<? super Annotated<C>, E> s,
151                                                                           final Predicate<? super ExecutableElement> annotationElementInclusionPredicate) {
152    return ac -> s.select(Annotated.of(ac, annotationElementInclusionPredicate));
153  }
154
155  /**
156   * Returns a {@link Selectable} whose {@link Selectable#select(Object)} method always returns an {@linkplain List#of()
157   * empty, immutable <code>List</code>}.
158   *
159   * <p>This method is useful primarily for completeness and for testing pathological situations.</p>
160   *
161   * @param <C> the criteria type
162   *
163   * @param <E> the element type
164   *
165   * @return a non-{@code null} {@link Selectable}
166   */
167  public static final <C, E> Selectable<C, E> empty() {
168    return Selectables::empty;
169  }
170
171  private static final <C, E> List<E> empty(final C ignored) {
172    return List.of();
173  }
174
175  /**
176   * Returns a {@link Selectable} using the supplied {@link Collection} as its elements, and the supplied {@link
177   * BiPredicate} as its <em>selector</em>.
178   *
179   * <p>There is no guarantee that this method will return new {@link Selectable} instances.</p>
180   *
181   * <p>The {@link Selectable} instances returned by this method may or may not cache their selections.</p>
182   *
183   * <p>The selector tests its first argument to see if it is <dfn>selected</dfn> by its second argument. The selector
184   * is invoked repeatedly. If, for any given invocation, the first argument is selected, the selected element is added
185   * to the selection that is eventually returned as a sublist of the supplied {@link Collection}. The selector must
186   * additionally be idempotent and must produce a determinate value when given the same arguments.</p>
187   *
188   * <p>No validation of these semantics of the selector is performed.</p>
189   *
190   * @param <C> the criteria type
191   *
192   * @param <E> the element type
193   *
194   * @param collection a {@link Collection} of elements from which sublists may be selected; must not be {@code null}
195   *
196   * @param p the selector; must not be {@code null}
197   *
198   * @return a {@link Selectable}; never {@code null}
199   *
200   * @exception NullPointerException if either {@code collection} or {@code p} is {@code null}
201   */
202  @SuppressWarnings("unchecked")
203  public static <C, E> Selectable<C, E> filtering(final Collection<? extends E> collection,
204                                                  final BiPredicate<? super E, ? super C> p) {
205    requireNonNull(p, "p");
206    return collection.isEmpty() ? empty() : c -> (List<E>)collection.stream().filter(e -> p.test(e, c)).toList();
207  }
208
209  /*
210   * An <strong>experimental</strong> convenience method that returns a non-{@code null}, determinate {@link Selectable}
211   * representing the composition of the supplied {@link Selectable} with the supplied {@code argumentTransformer} {@link
212   * Function}.
213   *
214   * @param <B> the (criteria) type of the sole parameter of the returned {@link Selectable}
215   *
216   * @param <C> the (criteria) type of the sole parameter of the supplied {@link Selectable}
217   *
218   * @param <E> the element type of both {@link Selectable}s
219   *
220   * @param selectable a non-{@code null} {@link Selectable}
221   *
222   * @param argumentTransformer a non-{@code null} {@link Function} that maps its sole parameter (of type {@code B}) to
223   * a value of type {@code C} suitable for supplying as criteria to the supplied {@link Selectable}; must be idempotent
224   * and return a determinate value
225   *
226   * @return a non-{@code null}, determinate, composed {@link Selectable}
227   *
228   * @exception NullPointerException if any argument is {@code null}
229   *
230   * @see Function#compose(Function)
231   */
232  /*
233  public static <B, C, E> Selectable<B, E> compose(final Selectable<? super C, E> selectable,
234                                                   final Function<? super B, ? extends C> argumentTransformer) {
235    return ((Function<C, List<E>>)selectable::select).compose(argumentTransformer)::apply;
236  }
237  */
238
239}