001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2022 microBean™.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
014 * implied.  See the License for the specific language governing
015 * permissions and limitations under the License.
016 */
017package org.microbean.loader.spi;
018
019import java.lang.reflect.Type;
020
021import java.util.NoSuchElementException;
022
023import org.microbean.development.annotation.Experimental;
024
025import org.microbean.loader.api.Loader;
026
027import org.microbean.path.Path;
028import org.microbean.path.Path.Element;
029
030import org.microbean.qualifier.Qualifiers;
031
032import org.microbean.type.Type.CovariantSemantics;
033
034/**
035 * An interface whose implementations handle various kinds of
036 * ambiguity that arise when a {@link Loader} seeks configured objects
037 * by way of various {@link Provider}s.
038 *
039 * @author <a href="https://about.me/lairdnelson"
040 * target="_parent">Laird Nelson</a>
041 */
042@LoaderFacade(false)
043public interface AmbiguityHandler {
044
045  /**
046   * Called to notify this {@link AmbiguityHandler} that a {@link
047   * Provider} was discarded during the search for a configured
048   * object.
049   *
050   * <p>The default implementation of this method does nothing.</p>
051   *
052   * @param rejector the {@link Loader} that rejected the {@link
053   * Provider}; must not be {@code null}
054   *
055   * @param absolutePath the {@linkplain Path#absolute() absolute
056   * <code>Path</code>} for which a configured object is being sought;
057   * must not be {@code null}
058   *
059   * @param provider the rejected {@link Provider}, which may be
060   * {@code null}
061   *
062   * @exception NullPointerException if either {@code rejector} or
063   * {@code absolutePath} is {@code null}
064   */
065  public default void providerRejected(final Loader<?> rejector,
066                                       final Path<? extends Type> absolutePath,
067                                       final Provider provider) {
068
069  }
070
071  /**
072   * Called to notify this {@link AmbiguityHandler} that a {@link
073   * Value} provided by a {@link Provider} was discarded during the
074   * search for a configured object.
075   *
076   * <p>The default implementation of this method does nothing.</p>
077   *
078   * @param rejector the {@link Loader} that rejected the {@link
079   * Provider}; must not be {@code null}
080   *
081   * @param absolutePath the {@linkplain Path#absolute() absolute
082   * <code>Path</code>} for which a configured object is being sought;
083   * must not be {@code null}
084   *
085   * @param provider the {@link Provider} providing the rejected
086   * value; must not be {@code null}
087   *
088   * @param value the rejected {@link Value}; must not be {@code null}
089   *
090   * @exception NullPointerException if any argument is {@code null}
091   */
092  public default void valueRejected(final Loader<?> rejector,
093                                    final Path<? extends Type> absolutePath,
094                                    final Provider provider,
095                                    final Value<?> value) {
096
097  }
098
099  /**
100   * Returns a score indicating the relative specificity of {@code
101   * valueQualifiers} with respect to {@code referenceQualifiers}, or
102   * {@link Integer#MIN_VALUE} if {@code valueQualifiers} is wholly
103   * unsuitable for further consideration or processing.
104   *
105   * <p>This is <em>not</em> a comparison method.</p>
106   *
107   * @param referenceQualifiers the {@link Qualifiers} against which
108   * to score the supplied {@code valueQualifiers}; must not be {@code
109   * null}
110   *
111   * @param valueQualifiers the {@link Qualifiers} to score against
112   * the supplied {@code referenceQualifiers}; must not be {@code
113   * null}
114   *
115   * @return a relative score for {@code valueQualifiers} with respect
116   * to {@code referenceQualifiers}; meaningless on its own
117   * <em>unless</em> it is {@link Integer#MIN_VALUE} in which case the
118   * supplied {@code valueQualifiers} will be treated as wholly
119   * unsuitable for further consideration or processing
120   *
121   * @exception NullPointerException if either parameter is {@code
122   * null}
123   *
124   * @see Loader#load(Path)
125   *
126   * @threadsafety The default implementation of this method is, and
127   * its overrides must be, safe for concurrent use by multiple
128   * threads.
129   *
130   * @idempotency The default implementation of this method is, and
131   * its overrides must be, idempotent and deterministic.
132   * Specifically, the same score is and must be returned whenever
133   * this method is invoked with the same arguments.
134   */
135  public default int score(final Qualifiers<? extends String, ?> referenceQualifiers,
136                           final Qualifiers<? extends String, ?> valueQualifiers) {
137    final int intersectionSize = referenceQualifiers.intersectionSize(valueQualifiers);
138    if (intersectionSize > 0) {
139      return
140        intersectionSize == valueQualifiers.size() ?
141        intersectionSize :
142        intersectionSize - referenceQualifiers.symmetricDifferenceSize(valueQualifiers);
143    } else {
144      return -(referenceQualifiers.size() + valueQualifiers.size());
145    }
146  }
147
148  /**
149   * Returns a score indicating the relative specificity of {@code
150   * valuePath} with respect to {@code absoluteReferencePath}, or
151   * {@link Integer#MIN_VALUE} if {@code valuePath} is wholly
152   * unsuitable for further consideration or processing.
153   *
154   * <p>This is <em>not</em> a comparison method.</p>
155   *
156   * <p>The following preconditions must hold or undefined behavior
157   * will result:</p>
158   *
159   * <ul>
160   *
161   * <li>Neither parameter's value may be {@code null}.</li>
162   *
163   * <li>{@code absoluteReferencePath} must be {@linkplain
164   * Path#absolute() absolute}
165   *
166   * <li>{@code valuePath} must be <em>selectable</em> with respect to
167   * <code>absoluteReferencePath</code>, where the definition of
168   * selectability is described below</li>
169   *
170   * </ul>
171   *
172   * <p>For {@code valuePath} to "be selectable" with respect to
173   * {@code absoluteReferencePath} for the purposes of this method and
174   * for no other purpose, {@code true} must be returned by a
175   * hypothetical invocation of code whose behavior is that of the
176   * following:</p>
177   *
178   * <blockquote><pre>absoluteReferencePath.endsWith(valuePath, {@link
179   * #compatible(Element, Element)
180   * AmbiguityHandler::compatible});</pre></blockquote>
181   *
182   * <p>If, during scoring, {@code valuePath} is found to be wholly
183   * unsuitable for further consideration or processing, {@link
184   * Integer#MIN_VALUE} will be returned to indicate this.  Overrides
185   * must follow suit or undefined behavior elsewhere in this
186   * framework will result.</p>
187   *
188   * <p>In the normal course of events, this method will be called
189   * after a call to {@link #score(Qualifiers, Qualifiers)}, and so
190   * there is normally no need for an implementation of this method to
191   * consult a {@link Path}'s {@linkplain Path#qualifiers() affiliated
192   * <code>Qualifiers</code>}.</p>
193   *
194   * @param absoluteReferencePath the {@link Path} against which to
195   * score the supplied {@code valuePath}; must not be {@code null};
196   * must adhere to the preconditions above
197   *
198   * @param valuePath the {@link Path} to score against the supplied
199   * {@code absoluteReferencePath}; must not be {@code null}; must
200   * adhere to the preconditions above
201   *
202   * @return a relative score for {@code valuePath} with respect to
203   * {@code absoluteReferencePath}; meaningless on its own
204   * <em>unless</em> it is {@link Integer#MIN_VALUE} in which case the
205   * supplied {@code valuePath} will be treated as wholly unsuitable
206   * for further consideration or processing
207   *
208   * @exception NullPointerException if either parameter is {@code
209   * null}
210   *
211   * @exception IllegalArgumentException if certain preconditions have
212   * been violated
213   *
214   * @see Loader#load(Path)
215   *
216   * @threadsafety The default implementation of this method is, and
217   * its overrides must be, safe for concurrent use by multiple
218   * threads.
219   *
220   * @idempotency The default implementation of this method is, and
221   * its overrides must be, idempotent and deterministic.
222   * Specifically, the same score is and must be returned whenever
223   * this method is invoked with the same {@link Path}s.
224   */
225  @Experimental
226  public default int score(final Path<? extends Type> absoluteReferencePath, final Path<? extends Type> valuePath) {
227    if (!absoluteReferencePath.absolute()) {
228      throw new IllegalArgumentException("absoluteReferencePath: " + absoluteReferencePath);
229    }
230
231    final int lastValuePathIndex = absoluteReferencePath.lastIndexOf(valuePath, AmbiguityHandler::compatible);
232    assert lastValuePathIndex >= 0 : "absoluteReferencePath: " + absoluteReferencePath + "; valuePath: " + valuePath;
233    assert lastValuePathIndex + valuePath.size() == absoluteReferencePath.size() : "absoluteReferencePath: " + absoluteReferencePath + "; valuePath: " + valuePath;
234
235    // Multiplying by 10 (arbitrary) gives us some headroom for score
236    // boosts based on path element name equality; see below.
237    int score = 10 * valuePath.size();
238    for (int valuePathIndex = 0; valuePathIndex < valuePath.size(); valuePathIndex++) {
239      // We already know they're *compatible*, but boost the score if
240      // they're *equal*.
241      if (absoluteReferencePath.get(lastValuePathIndex + valuePathIndex).name().equals(valuePath.get(valuePathIndex).name())) {
242        ++score;
243      }
244    }
245    return score;
246  }
247
248  /**
249   * Given two {@link Value}s and some contextual objects, chooses one
250   * over the other and returns it, or synthesizes a new {@link Value}
251   * and returns that, or indicates that disambiguation is impossible
252   * by returning {@code null}.
253   *
254   * @param <U> the type of objects the {@link Value}s in question can
255   * supply
256   *
257   * @param requestor the {@link Loader} currently seeking a
258   * {@link Value}; must not be {@code null}
259   *
260   * @param absolutePath an {@linkplain Path#absolute() absolute
261   * <code>Path</code>} for which a value is being sought; must not be
262   * {@code null}
263   *
264   * @param p0 the {@link Provider} that supplied the first {@link
265   * Value}; must not be {@code null}; must not be equal to {@code p1}
266   *
267   * @param v0 the first {@link Value}; must not be {@code null}
268   *
269   * @param p1 the {@link Provider} that supplied the second {@link
270   * Value}; must not be {@code null}; must not be equal to {@code p0}
271   *
272   * @param v1 the second {@link Value}; must not be {@code null}
273   *
274   * @return the {@link Value} to use instead; ordinarily one of the
275   * two supplied {@link Value}s but may be {@code null} to indicate
276   * that disambiguation was impossible, or an entirely different
277   * {@link Value} altogether
278   *
279   * @exception NullPointerException if any argument is {@code null}
280   *
281   * @nullability The default implementation of this method and its
282   * overrides may return {@code null}.
283   *
284   * @threadsafety The default implementation of this method is, and
285   * its overrides must be, safe for concurrent use by multiple
286   * threads.
287   *
288   * @idempotency The default implementation of this method is, and
289   * its overrides must be, idempotent. The default implementation of
290   * this method is deterministic, but its overrides need not be.
291   */
292  public default <U> Value<U> disambiguate(final Loader<?> requestor,
293                                           final Path<? extends Type> absolutePath,
294                                           final Provider p0,
295                                           final Value<U> v0,
296                                           final Provider p1,
297                                           final Value<U> v1) {
298    return null;
299  }
300
301  /**
302   * Returns {@code true} if and only if the second {@link Element} is
303   * <em>compatible</em> with respect to the first {@link Element}.
304   *
305   * <p>This method returns {@code true} <strong>unless</strong> one
306   * of the following conditions holds:</p>
307   *
308   * <ul>
309   *
310   * <li>Both {@linkplain Element element}s' {@linkplain
311   * Element#name() names} are non-{@linkplain String#isEmpty() empty}
312   * and are not {@linkplain String#equals(Object) equal}</li>
313   *
314   * <li>One {@linkplain Element element}'s {@linkplain
315   * Element#qualified() qualified} is {@code null} but the other is
316   * not</li>
317   *
318   * <li>An {@link Element element}'s {@linkplain Element#qualified()
319   * qualified} is non-{@code null} but not a {@link Type}</li>
320   *
321   * <li>Both {@linkplain Element element}'s {@linkplain
322   * Element#qualified() qualified}s are {@link Type}s, but the second
323   * {@linkplain Element element}'s {@link Type} is not {@linkplain
324   * CovariantSemantics#assignable(Type, Type) assignable} to the
325   * first {@linkplain Element element}'s {@link Type}</li>
326   *
327   * <li>If the first {@linkplain Element element}'s {@linkplain
328   * Element#qualifiers() qualifiers} are not {@linkplain
329   * Qualifiers#isEmpty() empty} and the second {@linkplain Element
330   * element}'s {@linkplain Element#qualifiers() qualifiers} are not
331   * {@linkplain Qualifiers#isEmpty() empty} and the first {@linkplain
332   * Element element}'s {@linkplain Element#qualifiers() qualifiers}
333   * {@linkplain Qualifiers#intersectionSize(Iterable) do not
334   * intersect} with the second {@linkplain Element element}'s
335   * {@linkplain Element#qualifiers() qualifiers}</li>
336   *
337   * </ul>
338   *
339   * @param e1 the first {@link Element}; must not be {@code null}
340   *
341   * @param e2 the second {@link Element}; must not be {@code null}
342   *
343   * @return {@code true} if and only if the second {@link Element} is
344   * compatible with the first
345   *
346   * @exception NullPointerException if either argument is {@code
347   * null}
348   *
349   * @idempotency This method is idempotent and deterministic.
350   *
351   * @threadsafety This method is safe for concurrent use by multiple
352   * threads.
353   */
354  public static boolean compatible(Element<?> e1, Element<?> e2) {
355    final String name1 = e1.name();
356    if (!name1.isEmpty()) {
357      final String name2 = e2.name();
358      if (!name2.isEmpty() && !name1.equals(name2)) {
359        // Empty names have special significance in that they
360        // "match" any other name.
361        return false;
362      }
363    }
364
365    final Object o1 = e1.qualified();
366    if (o1 == null) {
367      return e2.qualified() == null;
368    } else if (!(o1 instanceof Type) ||
369               !(e2.qualified() instanceof Type t2) ||
370               !CovariantSemantics.INSTANCE.assignable((Type)o1, t2)) {
371      return false;
372    }
373
374    final Qualifiers<?, ?> q1 = e1.qualifiers();
375    if (!q1.isEmpty()) {
376      final Qualifiers<?, ?> q2 = e2.qualifiers();
377      if (!q2.isEmpty() && q1.intersectionSize(q2) <= 0) {
378        return false;
379      }
380    }
381
382    return true;
383  }
384
385}