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<AnnotatedConstruct>} 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<AnnotatedConstruct>} 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<AnnotatedConstruct>} 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<AnnotatedConstruct>} 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}