001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 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.assign;
015
016import java.util.Collection;
017import java.util.List;
018import java.util.Map;
019import java.util.Objects;
020
021import java.util.concurrent.ConcurrentHashMap;
022
023import java.util.function.BiFunction;
024import java.util.function.BiPredicate;
025import java.util.function.Function;
026
027/**
028 * Utility methods for working with {@link Selectable}s.
029 *
030 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
031 *
032 * @see Selectable
033 */
034public final class Selectables {
035
036  private Selectables() {
037    super();
038  }
039  
040  /**
041   * Returns a {@link Selectable} that caches its results.
042   *
043   * <p>The cache is unbounded.</p>
044   *
045   * @param <C> the criteria type
046   *
047   * @param <E> the element type
048   *
049   * @param selectable a {@link Selectable}; must not be {@code null}
050   *
051   * @return a non-{@code null} {@link Selectable}
052   *
053   * @exception NullPointerException if {@code selectable} is {@code null}
054   *
055   * @see #caching(Selectable, BiFunction)
056   */
057  public static <C, E> Selectable<C, E> caching(final Selectable<C, E> selectable) {
058    final Map<C, List<E>> selectionCache = new ConcurrentHashMap<>();
059    return Selectables.<C, E>caching(selectable, selectionCache::computeIfAbsent);
060  }
061
062  /**
063   * Returns a {@link Selectable} that caches its results.
064   *
065   * @param <C> the criteria type
066   *
067   * @param <E> the element type
068   *
069   * @param selectable a {@link Selectable}; must not be {@code null}
070   *
071   * @param f a {@link BiFunction} that returns a cached result, computing it on demand via its supplied mapping {@link
072   * Function} if necessary; must not be {@code null}; normally safe for concurrent use by multiple threads; often a
073   * reference to the {@link ConcurrentHashMap#computeIfAbsent(Object, Function)} method
074   *
075   * @return a non-{@code null} {@link Selectable}
076   *
077   * @exception NullPointerException if {@code selectable} or {@code f} is {@code null}
078   *
079   * @see ConcurrentHashMap#computeIfAbsent(Object, Function)
080   */
081  public static <C, E> Selectable<C, E> caching(final Selectable<C, E> selectable,
082                                                final BiFunction<? super C, Function<? super C, ? extends List<E>>, ? extends List<E>> f) {
083    return c -> f.apply(c, selectable::select);
084  }
085
086  /**
087   * Returns a {@link Selectable} whose {@link Selectable#select(Object)} method always returns an {@linkplain List#of()
088   * empty, immutable <code>List</code>}.
089   *
090   * <p>This method is useful primarily for completeness and for testing pathological situations.</p>
091   *
092   * @param <C> the criteria type
093   *
094   * @param <E> the element type
095   *
096   * @return a non-{@code null} {@link Selectable}
097   */
098  public static final <C, E> Selectable<C, E> empty() {
099    return Selectables::empty;
100  }
101
102  private static final <C, E> List<E> empty(final C ignored) {
103    return List.of();
104  }
105
106  /**
107   * Returns a {@link Selectable} using the supplied {@link Collection} as its elements, and the supplied {@link
108   * BiPredicate} as its <em>selector</em>.
109   *
110   * <p>There is no guarantee that this method will return new {@link Selectable} instances.</p>
111   *
112   * <p>The {@link Selectable} instances returned by this method may or may not cache their selections.</p>
113   *
114   * <p>The selector must (indirectly) designate a sublist from the supplied {@link Collection} as mediated by the
115   * supplied criteria. The selector must additionally be idempotent and must produce a determinate value when given the
116   * same arguments.</p>
117   *
118   * <p>No validation of these semantics of the selector is performed.</p>
119   *
120   * @param <C> the criteria type
121   *
122   * @param <E> the element type
123   *
124   * @param collection a {@link Collection} of elements from which sublists may be selected; must not be {@code null}
125   *
126   * @param p the selector; must not be {@code null}
127   *
128   * @return a {@link Selectable}; never {@code null}
129   *
130   * @exception NullPointerException if either {@code collection} or {@code p} is {@code null}
131   */
132  @SuppressWarnings("unchecked")
133  public static <C, E> Selectable<C, E> filtering(final Collection<? extends E> collection,
134                                                  final BiPredicate<? super E, ? super C> p) {
135    Objects.requireNonNull(p, "p");
136    return collection.isEmpty() ? empty() : c -> (List<E>)collection.stream().filter(e -> p.test(e, c)).toList();
137  }
138
139  
140}