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 static java.util.HashSet.newHashSet; 033 034/** 035 * A {@link Selectable} and {@link Reducible} implementation that works with {@link Bean} and {@link 036 * AttributedType} instances. 037 * 038 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 039 * 040 * @see #Beans(Selectable, Reducible) 041 * 042 * @see Selectable 043 * 044 * @see Reducible 045 * 046 * @see Reducer 047 * 048 * @see RankedReducer 049 * 050 * @see Bean 051 * 052 * @see AttributedType 053 */ 054public final class Beans implements Selectable<AttributedType, Bean<?>>, Reducible<AttributedType, Bean<?>> { 055 056 057 /* 058 * Static fields. 059 */ 060 061 062 private static final Logger LOGGER = System.getLogger(Beans.class.getName()); 063 064 private static final Comparator<Bean<?>> byRankComparator = Comparator 065 .<Bean<?>>comparingInt(Ranked::rank) 066 .reversed(); 067 068 private static final Comparator<Bean<?>> byAlternateThenByRankComparator = Comparator 069 .<Bean<?>, Boolean>comparing(Ranked::alternate) 070 .reversed() 071 .thenComparing(byRankComparator); 072 073 074 /* 075 * Instance fields. 076 */ 077 078 079 private final Selectable<AttributedType, Bean<?>> s; 080 081 private final Reducible<AttributedType, Bean<?>> r; 082 083 084 /* 085 * Constructors. 086 */ 087 088 /** 089 * Creates a new {@link Beans}. 090 * 091 * <p>{@link Bean} instances selected by the supplied {@link Selectable} will be reduced using a {@link 092 * RankedReducer}.</p> 093 * 094 * @param s a {@link Selectable}; must not be {@code null} 095 * 096 * @see RankedReducer 097 */ 098 public Beans(final Selectable<AttributedType, Bean<?>> s) { 099 this(s, Reducible.of(s, RankedReducer.of())); 100 } 101 102 /** 103 * Creates a new {@link Beans}. 104 * 105 * @param s a {@link Selectable}; must not be {@code null} 106 * 107 * @param r a {@link Reducible} to apply to elements selected by the supplied {@link Selectable}; must not be {@code 108 * null} 109 * 110 * @see #cachingSelectableOf(Matcher, Map, Collection) 111 * 112 * @see Reducible#of(Selectable, Reducer) 113 * 114 * @see RankedReducer 115 */ 116 public Beans(final Selectable<AttributedType, Bean<?>> s, 117 final Reducible<AttributedType, Bean<?>> r) { 118 this.s = Objects.requireNonNull(s, "s"); 119 this.r = Objects.requireNonNull(r, "r"); 120 } 121 122 123 /* 124 * Instance methods. 125 */ 126 127 128 @Override // Selectable<AttributedType, Bean<?>> 129 public final List<Bean<?>> select(final AttributedType c) { 130 return this.s.select(c); 131 } 132 133 @Override // Reducible<AttributedType, Bean<?>> 134 public final Bean<?> reduce(final AttributedType c) { 135 return this.r.reduce(c); 136 } 137 138 139 /* 140 * Static methods. 141 */ 142 143 144 /** 145 * Returns a new {@link Selectable} that caches its results. 146 * 147 * <p>The cache is unbounded.</p> 148 * 149 * @param idMatcher an {@link IdMatcher}; must not be {@code null} 150 * 151 * @param selections a (normally empty) {@link Map} of precomputed selections; must not be {@code null} 152 * 153 * @param beans a {@link Collection} of {@link Bean}s; must not be {@code null} 154 * 155 * @return a new {@link Selectable}; never {@code null} 156 * 157 * @exception NullPointerException if any argument is {@code null} 158 */ 159 public static final Selectable<AttributedType, Bean<?>> cachingSelectableOf(final Matcher<? super AttributedType, ? super Id> idMatcher, 160 final Map<? extends AttributedType, ? extends List<Bean<?>>> selections, 161 final Collection<? extends Bean<?>> beans) { 162 Objects.requireNonNull(idMatcher, "idMatcher"); 163 final Map<AttributedType, List<Bean<?>>> selectionCache = new ConcurrentHashMap<>(); 164 final ArrayList<Bean<?>> newBeans = new ArrayList<>(31); // 31 == arbitrary 165 final Set<Bean<?>> newBeansSet = newHashSet(31); // 31 == arbitrary 166 for (final Entry<? extends AttributedType, ? extends List<Bean<?>>> e : selections.entrySet()) { 167 final List<Bean<?>> selection = e.getValue(); 168 if (!selection.isEmpty()) { 169 final Set<Bean<?>> newSelectionSet = newHashSet(7); // 7 == arbitrary 170 final ArrayList<Bean<?>> newSelection = new ArrayList<>(selection.size()); 171 for (final Bean<?> b : selection) { 172 if (newSelectionSet.add(b)) { 173 newSelection.add(b); 174 } 175 if (newBeansSet.add(b)) { 176 newBeans.add(b); 177 } 178 } 179 newSelectionSet.clear(); 180 newSelection.trimToSize(); 181 Collections.sort(newSelection, byAlternateThenByRankComparator); 182 selectionCache.put(e.getKey(), Collections.unmodifiableList(newSelection)); 183 } 184 } 185 for (final Bean<?> bean : beans) { 186 if (newBeansSet.add(bean)) { 187 newBeans.add(bean); 188 } 189 } 190 newBeansSet.clear(); 191 if (newBeans.isEmpty()) { 192 return Beans::empty; 193 } 194 Collections.sort(newBeans, byAlternateThenByRankComparator); 195 newBeans.trimToSize(); 196 return attributedType -> selectionCache.computeIfAbsent(attributedType, at -> newBeans.stream().filter(b -> idMatcher.test(at, b.id())).toList()); 197 } 198 199 private static final <C, T> List<T> empty(final C ignored) { 200 return List.of(); 201 } 202 203}