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.util.ArrayList;
017import java.util.List;
018
019import java.util.function.BiFunction;
020
021import static java.util.Collections.unmodifiableList;
022
023import static org.microbean.bean.Ranked.DEFAULT_RANK;
024
025/**
026 * A {@link Reducer} implementation that works with {@link Ranked} objects.
027 *
028 * @param <C> the type of criteria used in the {@link #reduce(List, Object, BiFunction)} method
029 *
030 * @param <T> a {@link Ranked} type
031 *
032 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
033 *
034 * @see #reduce(List, Object, BiFunction)
035 *
036 * @deprecated This class is not really needed and is tentatively deprecated.
037 */
038@Deprecated(since = "0.0.18")
039public final class RankedReducer<C, T extends Ranked> implements Reducer<C, T> {
040
041
042  /*
043   * Static fields.
044   */
045
046
047  private static final RankedReducer<?, ?> INSTANCE = new RankedReducer<>();
048
049
050  /*
051   * Constructors.
052   */
053
054
055  private RankedReducer() {
056    super();
057  }
058
059
060  /*
061   * Instance methods.
062   */
063
064
065  @Override
066  public final T reduce(final List<? extends T> elements,
067                        final C c,
068                        final BiFunction<? super List<? extends T>, ? super C, ? extends T> failureHandler) {
069    if (elements == null || elements.isEmpty()) {
070      // https://github.com/microbean/microbean-bean/issues/21
071      return failureHandler == null ? Reducer.fail(List.of(), c) : failureHandler.apply(List.of(), c);
072    } else if (elements.size() == 1) {
073      return elements.get(0);
074    }
075
076    T candidate = null;
077    List<T> unresolved = null;
078    // Highest rank wins
079    int maxRank = DEFAULT_RANK;
080
081    for (final T element : elements) {
082      if (alternate(element)) {
083        final int elementRank = rank(element);
084        if (elementRank == maxRank) {
085          if (candidate == null || !alternate(candidate)) {
086            // Prefer elements regardless of ranks.
087            candidate = element;
088          } else {
089            assert rank(candidate) == maxRank : "Unexpected rank: " + rank(candidate) + "; was expecting: " + maxRank;
090            // The existing candidate is an alternate and by definition has the highest rank we've seen so far; the
091            // incoming element is also an alternate; both have equal ranks: we can't resolve this.
092            if (unresolved == null) {
093              unresolved = new ArrayList<>(6);
094            }
095            unresolved.add(candidate);
096            unresolved.add(element);
097            candidate = null;
098          }
099        } else if (elementRank > maxRank) {
100          if (candidate == null || !alternate(candidate) || elementRank > rank(candidate)) {
101            // The existing candidate is either null, not an alternate (and alternates are always preferred), or an
102            // alternate with losing rank, so junk it in favor of the incoming element.
103            candidate = element;
104            // We have a new maxRank.
105            maxRank = elementRank;
106          } else if (elementRank == rank(candidate)) {
107            // The existing candidate is also an alternate and has the same rank.
108            if (unresolved == null) {
109              unresolved = new ArrayList<>(6);
110            }
111            unresolved.add(candidate);
112            unresolved.add(element);
113            candidate = null;
114          } else {
115            assert elementRank < rank(candidate) : "elementRank >= rank(candidate): " + elementRank + " >= " + rank(candidate);
116            // The existing candidate is also an alternate but has a higher rank than the alternate, so keep it (do
117            // nothing).
118          }
119        }
120        // ...else drop element by doing nothing
121      } else if (candidate == null) {
122        // The incoming element is not an alternate, but that doesn't matter; the candidate is null, so accept the
123        // element no matter what.
124        candidate = element;
125      } else if (!alternate(candidate)) {
126        // The existing candidate is not an alternate. The incoming element is not an alternate. Ranks in this case are
127        // irrelevant, perhaps surprisingly. We cannot resolve this.
128        if (unresolved == null) {
129          unresolved = new ArrayList<>(6);
130        }
131        unresolved.add(candidate);
132        unresolved.add(element);
133        candidate = null;
134      }
135      // ...else do nothing
136    }
137
138    if (unresolved != null && !unresolved.isEmpty()) {
139      if (candidate != null) {
140        unresolved.add(candidate);
141      }
142      candidate =
143        failureHandler == null ? Reducer.fail(unmodifiableList(unresolved), c) : failureHandler.apply(unmodifiableList(unresolved), c);
144    }
145
146    return candidate;
147  }
148
149  // Preparing for a future where alternate status is not a first-class citizen.
150  private final boolean alternate(final T t) {
151    return t.alternate();
152  }
153
154  // Preparing for a future where rank is not a first-class citizen.
155  private final int rank(final T t) {
156    return t.rank();
157  }
158
159
160  /*
161   * Static methods.
162   */
163
164
165  /**
166   * Returns a {@link RankedReducer} implementation.
167   *
168   * @param <C> the type of criteria
169   *
170   * @param <T> the type of the {@link Ranked} reduction
171   *
172   * @return a {@link RankedReducer} implementation; never {@code null}
173   *
174   * @microbean.nullability This method never returns {@code null}.
175   *
176   * @microbean.idempotency This method is idempotent and deterministic.
177   *
178   * @microbean.threadsafety This method is safe for concurrent use by multiple threads.
179   */
180  @SuppressWarnings("unchecked")
181  public static final <C, T extends Ranked> Reducer<C, T> of() {
182    return (Reducer<C, T>)INSTANCE;
183  }
184
185}