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}