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}