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}