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.bean; 015 016import java.util.ArrayList; 017import java.util.Collection; 018import java.util.List; 019import java.util.Map; 020 021import java.util.concurrent.ConcurrentHashMap; 022 023import java.util.function.Predicate; 024import java.util.function.ToIntFunction; 025 026import javax.lang.model.type.TypeMirror; 027 028import org.microbean.assign.Matcher; 029import org.microbean.assign.Selectable; 030 031import static java.util.Objects.requireNonNull; 032 033/** 034 * Utility methods for working with {@link Selectable}s. 035 * 036 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 037 * 038 * @see Selectable 039 * 040 * @see org.microbean.assign.Selectables 041 */ 042public final class Selectables { 043 044 private Selectables() { 045 super(); 046 } 047 048 /** 049 * Returns a {@link Selectable} that reduces any ambiguity in the results returned by another {@link Selectable}, 050 * considering notional alternate status and rank. 051 * 052 * @param <C> the criteria type 053 * 054 * @param <E> the element type 055 * 056 * @param s a {@link Selectable}; must not be {@code null} 057 * 058 * @param p a {@link Predicate} that tests whether an element is an <dfn>alternate</dfn>; may be 059 * {@code null} in which case it will be as if {@code e -> false} were supplied 060 * 061 * @param rankingFunction a {@link ToIntFunction} that returns a <dfn>rank</dfn> for an alternate; a rank of {@code 0} 062 * indicates no particular rank; may be {@code null} in which case it will be as if {@code e -> 0} were supplied 063 * 064 * @return a non-{@code null} {@link Selectable} 065 * 066 * @exception NullPointerException if any argument is {@code null} 067 */ 068 public static final <C, E> Selectable<C, E> ambiguityReducing(final Selectable<C, E> s, 069 final Predicate<? super E> p, // are you an alternate? 070 final ToIntFunction<? super E> rankingFunction) { // rank? 071 requireNonNull(s, "s"); 072 if (p == null) { 073 // If there's no way to tell if something is an alternate, then it also has no rank 074 return s; 075 } 076 final ToIntFunction<? super E> ranker = rankingFunction == null ? e -> 0 : rankingFunction; 077 078 // Relevant bits: 079 // 080 // https://jakarta.ee/specifications/cdi/4.1/jakarta-cdi-spec-4.1#unsatisfied_and_ambig_dependencies 081 // https://jakarta.ee/specifications/cdi/4.1/jakarta-cdi-spec-4.1#dynamic_lookup (Search for "The iterator() method 082 // must") 083 // 084 // In CDI 5 @Reserve will also enter the chat. 085 return c -> { 086 final List<E> elements = s.select(c); // remember: c could be null; the result could be everything 087 final int size = elements.size(); 088 switch (size) { 089 case 0: 090 return List.of(); 091 case 1: 092 return List.of(elements.get(0)); 093 default: 094 break; 095 } 096 097 int maxRank = Integer.MIN_VALUE; 098 final List<E> reductionList = new ArrayList<>(size); // will never be larger, only smaller 099 boolean reductionListContainsOnlyRankedAlternates = false; 100 101 for (final E element : elements) { 102 if (!p.test(element)) { 103 // The element is not an alternate. We skip it. 104 continue; 105 } 106 107 final int rank = ranker.applyAsInt(element); 108 if (rank == 0) { 109 // The element is an alternate. It has the default rank, so no explicit rank. Headed toward ambiguity. No need 110 // to look at maxRank etc. 111 if (reductionListContainsOnlyRankedAlternates) { 112 reductionListContainsOnlyRankedAlternates = false; 113 } 114 reductionList.add(element); 115 continue; 116 } 117 118 if (reductionList.isEmpty()) { 119 // The element is an alternate with an explicit rank. The reduction list is empty. The element's rank is 120 // therefore by definition the highest one encountered so far. Add the element to the reduction list. 121 assert !reductionListContainsOnlyRankedAlternates : "Unexpected reductionListContainsOnlyRankedAlternates: " + reductionListContainsOnlyRankedAlternates; 122 assert rank > maxRank : "rank <= maxRank: " + rank + " <= " + maxRank; // TODO: I think this is correct 123 maxRank = rank; 124 reductionList.add(element); 125 reductionListContainsOnlyRankedAlternates = true; 126 continue; 127 } 128 129 if (reductionListContainsOnlyRankedAlternates) { 130 // The element is an alternate. It has an explicit rank. The (non-empty) reduction list is known to contain 131 // only ranked alternates (in fact it should contain exactly one). 132 assert reductionList.size() == 1 : "Unexpected reductionList size: " + reductionList; 133 if (rank > maxRank) { 134 // The element's rank is higher than the rank of the (sole) element in the list. Record the new highest rank 135 // and replace the sole element in the list with this element. 136 maxRank = rank; 137 reductionList.set(0, element); 138 } 139 continue; 140 } 141 142 // The element is an alternate. It has an explicit rank (but this does not matter as we'll see). The list we're 143 // using to store alternates does not have a possibility of reducing to size 1, because it already contains 144 // unranked alternates, so we have to add this element to it, regardless of what its rank is. This operation 145 // will not affect the highest rank. 146 reductionList.add(element); 147 } 148 149 assert reductionListContainsOnlyRankedAlternates ? reductionList.size() == 1 : true : "Unexpected reductionList size: " + reductionList; 150 151 return switch (reductionList.size()) { 152 // No reduction at all took place. "If typesafe resolution results in an ambiguous dependency and the set of 153 // candidate beans contains no alternative, the set of resulting beans contains all candidate beans." 154 case 0 -> elements; 155 case 1 -> List.of(reductionList.get(0)); // Optimization for the common case 156 default -> List.copyOf(reductionList); 157 }; 158 }; 159 } 160 161}