001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2024–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.bean; 015 016import java.lang.System.Logger; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.Comparator; 022import java.util.List; 023import java.util.Map; 024import java.util.Map.Entry; 025import java.util.Objects; 026import java.util.Set; 027 028import java.util.concurrent.ConcurrentHashMap; 029 030import org.microbean.assign.Matcher; 031 032import org.microbean.construct.Domain; 033 034import static java.util.HashSet.newHashSet; 035 036/** 037 * A {@link Selectable} and {@link Reducible} implementation that works with {@link Bean} and {@link 038 * AttributedType} instances. 039 * 040 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 041 * 042 * @see #Beans(Selectable, Reducible) 043 * 044 * @see #cachingSelectableOf(Collection, Matcher, Map) 045 * 046 * @see Selectable 047 * 048 * @see Reducible 049 * 050 * @see Reducer 051 * 052 * @see RankedReducer 053 * 054 * @see Bean 055 * 056 * @see AttributedType 057 */ 058public final class Beans implements Selectable<AttributedType, Bean<?>>, Reducible<AttributedType, Bean<?>> { 059 060 061 /* 062 * Static fields. 063 */ 064 065 066 private static final Logger LOGGER = System.getLogger(Beans.class.getName()); 067 068 private static final Comparator<Bean<?>> byRankComparator = Comparator 069 .<Bean<?>>comparingInt(Ranked::rank) 070 .reversed(); 071 072 private static final Comparator<Bean<?>> byAlternateThenByRankComparator = Comparator 073 .<Bean<?>, Boolean>comparing(Ranked::alternate) 074 .reversed() 075 .thenComparing(byRankComparator); 076 077 078 /* 079 * Instance fields. 080 */ 081 082 083 private final Selectable<AttributedType, Bean<?>> s; 084 085 private final Reducible<AttributedType, Bean<?>> r; 086 087 088 /* 089 * Constructors. 090 */ 091 092 093 /** 094 * Creates a new {@link Beans}. 095 * 096 * <p>This constructor is best suited for testing.</p> 097 * 098 * @param domain a {@link Domain}; must not be {@code null} 099 * 100 * @param beans an array of zero or more {@link Bean}s; may be {@code null} 101 * 102 * @exception NullPointerException if {@code domain} is {@code null} 103 * 104 * @see #Beans(Domain, Collection) 105 */ 106 public Beans(final Domain domain, final Bean<?>... beans) { 107 this(domain, beans == null || beans.length == 0 ? List.of() : List.of(beans)); 108 } 109 110 /** 111 * Creates a new {@link Beans}. 112 * 113 * @param domain a {@link Domain}; must not be {@code null} 114 * 115 * @param beans a {@link Collection} of {@link Bean}s; must not be {@code null} 116 * 117 * @exception NullPointerException if any argument is {@code null} 118 * 119 * @see #cachingSelectableOf(Collection, Matcher, Map) 120 * 121 * @see #Beans(Selectable) 122 */ 123 public Beans(final Domain domain, final Collection<? extends Bean<?>> beans) { 124 this(cachingSelectableOf(beans, 125 new IdMatcher(new BeanQualifiersMatcher(), 126 new InterceptorBindingsMatcher(), 127 new BeanTypeMatcher(domain)), 128 Map.of())); 129 } 130 131 /** 132 * Creates a new {@link Beans}. 133 * 134 * @param s a {@link Selectable}; must not be {@code null} 135 * 136 * @exception NullPointerException if {@code s} is {@code null} 137 * 138 * @see #Beans(Selectable, Reducer) 139 * 140 * @see RankedReducer#of() 141 */ 142 public Beans(final Selectable<AttributedType, Bean<?>> s) { 143 this(s, (Reducer<AttributedType, Bean<?>>)null); 144 } 145 146 /** 147 * Creates a new {@link Beans}. 148 * 149 * @param s a {@link Selectable}; must not be {@code null} 150 * 151 * @param r a {@link Reducer}; may be {@code null} in which case a {@link RankedReducer#of() RankedReducer} will be 152 * used instead 153 * 154 * @exception NullPointerException if {@code s} is {@code null} 155 * 156 * @see #Beans(Selectable, Reducible) 157 * 158 * @see Reducible#of(Selectable, Reducer) 159 */ 160 public Beans(final Selectable<AttributedType, Bean<?>> s, final Reducer<AttributedType, Bean<?>> r) { 161 this(s, Reducible.of(s, r == null ? RankedReducer.of() : r)); 162 } 163 164 /** 165 * Creates a new {@link Beans}. 166 * 167 * @param s a {@link Selectable}; must not be {@code null} 168 * 169 * @param r a {@link Reducible} to apply to elements selected by the supplied {@link Selectable}; must not be {@code 170 * null} 171 * 172 * @exception NullPointerException if any argument is {@code null} 173 */ 174 public Beans(final Selectable<AttributedType, Bean<?>> s, final Reducible<AttributedType, Bean<?>> r) { 175 this.s = Objects.requireNonNull(s, "s"); 176 this.r = Objects.requireNonNull(r, "r"); 177 } 178 179 180 /* 181 * Instance methods. 182 */ 183 184 185 @Override // Selectable<AttributedType, Bean<?>> 186 public final List<Bean<?>> select(final AttributedType c) { 187 return this.s.select(c); 188 } 189 190 @Override // Reducible<AttributedType, Bean<?>> 191 public final Bean<?> reduce(final AttributedType c) { 192 return this.r.reduce(c); 193 } 194 195 196 /* 197 * Static methods. 198 */ 199 200 201 /** 202 * Returns a new {@link Selectable} that caches its results. 203 * 204 * <p>The cache is unbounded.</p> 205 * 206 * @param beans a {@link Collection} of {@link Bean}s; must not be {@code null} 207 * 208 * @param idMatcher an {@link IdMatcher}; must not be {@code null} 209 * 210 * @param selections a (normally empty) {@link Map} of precomputed selections; must not be {@code null} 211 * 212 * @return a new {@link Selectable}; never {@code null} 213 * 214 * @exception NullPointerException if any argument is {@code null} 215 */ 216 public static final Selectable<AttributedType, Bean<?>> cachingSelectableOf(final Collection<? extends Bean<?>> beans, 217 final Matcher<? super AttributedType, ? super Id> idMatcher, 218 final Map<? extends AttributedType, ? extends List<Bean<?>>> selections) { 219 220 Objects.requireNonNull(idMatcher, "idMatcher"); 221 final Map<AttributedType, List<Bean<?>>> selectionCache = new ConcurrentHashMap<>(); 222 final ArrayList<Bean<?>> newBeans = new ArrayList<>(31); // 31 == arbitrary 223 final Set<Bean<?>> newBeansSet = newHashSet(31); // 31 == arbitrary 224 for (final Entry<? extends AttributedType, ? extends List<Bean<?>>> e : selections.entrySet()) { 225 final List<Bean<?>> selection = e.getValue(); 226 if (!selection.isEmpty()) { 227 final Set<Bean<?>> newSelectionSet = newHashSet(7); // 7 == arbitrary 228 final ArrayList<Bean<?>> newSelection = new ArrayList<>(selection.size()); 229 for (final Bean<?> b : selection) { 230 if (newSelectionSet.add(b)) { 231 newSelection.add(b); 232 } 233 if (newBeansSet.add(b)) { 234 newBeans.add(b); 235 } 236 } 237 newSelectionSet.clear(); 238 newSelection.trimToSize(); 239 Collections.sort(newSelection, byAlternateThenByRankComparator); 240 selectionCache.put(e.getKey(), Collections.unmodifiableList(newSelection)); 241 } 242 } 243 for (final Bean<?> bean : beans) { 244 if (newBeansSet.add(bean)) { 245 newBeans.add(bean); 246 } 247 } 248 newBeansSet.clear(); 249 if (newBeans.isEmpty()) { 250 return Beans::empty; 251 } 252 Collections.sort(newBeans, byAlternateThenByRankComparator); 253 newBeans.trimToSize(); 254 return attributedType -> selectionCache.computeIfAbsent(attributedType, at -> newBeans.stream().filter(b -> idMatcher.test(at, b.id())).toList()); 255 } 256 257 private static final <C, T> List<T> empty(final C ignored) { 258 return List.of(); 259 } 260 261}