001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2024 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 static java.util.HashSet.newHashSet; 031 032/** 033 * A {@link Selectable} and {@link Reducible} implementation that works with {@link Bean} and {@link 034 * AttributedType} instances. 035 * 036 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 037 * 038 * @see #Beans(Selectable, Reducible) 039 * 040 * @see Selectable 041 * 042 * @see Reducible 043 * 044 * @see Reducer 045 * 046 * @see RankedReducer 047 * 048 * @see Bean 049 * 050 * @see AttributedType 051 */ 052public final class Beans implements Selectable<AttributedType, Bean<?>>, Reducible<AttributedType, Bean<?>> { 053 054 055 /* 056 * Static fields. 057 */ 058 059 060 private static final Logger LOGGER = System.getLogger(Beans.class.getName()); 061 062 private static final Comparator<Bean<?>> byRankComparator = Comparator 063 .<Bean<?>>comparingInt(Ranked::rank) 064 .reversed(); 065 066 private static final Comparator<Bean<?>> byAlternateThenByRankComparator = Comparator 067 .<Bean<?>, Boolean>comparing(Ranked::alternate) 068 .reversed() 069 .thenComparing(byRankComparator); 070 071 072 /* 073 * Instance fields. 074 */ 075 076 077 private final Selectable<AttributedType, Bean<?>> s; 078 079 private final Reducible<AttributedType, Bean<?>> r; 080 081 082 /* 083 * Constructors. 084 */ 085 086 087 public Beans(final Selectable<AttributedType, Bean<?>> s, 088 final Reducible<AttributedType, Bean<?>> r) { 089 this.s = Objects.requireNonNull(s, "s"); 090 this.r = Objects.requireNonNull(r, "r"); 091 } 092 093 094 /* 095 * Instance methods. 096 */ 097 098 099 @Override // Selectable<AttributedType, Bean<?>> 100 public final List<Bean<?>> select(final AttributedType c) { 101 return this.s.select(c); 102 } 103 104 @Override // Reducible<AttributedType, Bean<?>> 105 public final Bean<?> reduce(final AttributedType c) { 106 return this.r.reduce(c); 107 } 108 109 110 /* 111 * Static methods. 112 */ 113 114 115 public static final Selectable<AttributedType, Bean<?>> cachingSelectableOf(final Matcher<? super AttributedType, ? super Id> idMatcher, 116 final Map<? extends AttributedType, ? extends List<Bean<?>>> selections, 117 final Collection<? extends Bean<?>> beans) { 118 Objects.requireNonNull(idMatcher, "idMatcher"); 119 final Map<AttributedType, List<Bean<?>>> selectionCache = new ConcurrentHashMap<>(); 120 final ArrayList<Bean<?>> newBeans = new ArrayList<>(31); // 31 == arbitrary 121 final Set<Bean<?>> newBeansSet = newHashSet(31); // 31 == arbitrary 122 for (final Entry<? extends AttributedType, ? extends List<Bean<?>>> e : selections.entrySet()) { 123 final List<Bean<?>> selection = e.getValue(); 124 if (!selection.isEmpty()) { 125 final Set<Bean<?>> newSelectionSet = newHashSet(7); // 7 == arbitrary 126 final ArrayList<Bean<?>> newSelection = new ArrayList<>(selection.size()); 127 for (final Bean<?> b : selection) { 128 if (newSelectionSet.add(b)) { 129 newSelection.add(b); 130 } 131 if (newBeansSet.add(b)) { 132 newBeans.add(b); 133 } 134 } 135 newSelectionSet.clear(); 136 newSelection.trimToSize(); 137 Collections.sort(newSelection, byAlternateThenByRankComparator); 138 selectionCache.put(e.getKey(), Collections.unmodifiableList(newSelection)); 139 } 140 } 141 for (final Bean<?> bean : beans) { 142 if (newBeansSet.add(bean)) { 143 newBeans.add(bean); 144 } 145 } 146 newBeansSet.clear(); 147 if (newBeans.isEmpty()) { 148 return Beans::empty; 149 } 150 Collections.sort(newBeans, byAlternateThenByRankComparator); 151 newBeans.trimToSize(); 152 return attributedType -> selectionCache.computeIfAbsent(attributedType, at -> newBeans.stream().filter(b -> idMatcher.test(at, b.id())).toList()); 153 } 154 155 private static final <C, T> List<T> empty(final C ignored) { 156 return List.of(); 157 } 158 159}