001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2025–2026 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.scopelet; 015 016import java.util.ArrayDeque; 017import java.util.Collection; 018import java.util.Iterator; 019import java.util.List; 020import java.util.Map; 021import java.util.Objects; 022import java.util.Queue; 023import java.util.Set; 024 025import java.util.function.BiFunction; 026import java.util.function.BooleanSupplier; 027import java.util.function.Supplier; 028 029import javax.lang.model.AnnotatedConstruct; 030 031import javax.lang.model.element.AnnotationMirror; 032import javax.lang.model.element.Element; 033 034import javax.lang.model.type.TypeMirror; 035 036import org.microbean.assign.Annotated; 037import org.microbean.assign.Selectable; 038 039import org.microbean.bean.AmbiguousResolutionException; 040import org.microbean.bean.Bean; 041import org.microbean.bean.Creation; 042import org.microbean.bean.Factory; 043import org.microbean.bean.Id; 044import org.microbean.bean.ReferencesSelector; 045 046import org.microbean.construct.Domain; 047 048import org.microbean.construct.element.SyntheticAnnotationMirror; 049import org.microbean.construct.element.SyntheticAnnotationTypeElement; 050 051import org.microbean.construct.type.UniversalType; 052 053import org.microbean.reference.Instances; 054 055import static java.util.Objects.requireNonNull; 056 057import static org.microbean.construct.element.AnnotationMirrors.sameAnnotation; 058 059/** 060 * An {@link Instances} implementation that is based on scopes. 061 * 062 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 063 * 064 * @see #supplier(Bean, Creation) 065 * 066 * @see Instances 067 */ 068public class ScopedInstances implements Instances { 069 070 071 /* 072 * Instance fields. 073 */ 074 075 076 private final Domain domain; 077 078 private final org.microbean.bean.Qualifiers bq; 079 080 private final Qualifiers sq; 081 082 private final Scopes scopes; 083 084 private final TypeMirror scopeletType; 085 086 // Deliberately not a scope or a qualifier 087 private final AnnotationMirror considerActiveness; 088 089 090 /* 091 * Constructors. 092 */ 093 094 095 /** 096 * Creates a new {@link ScopedInstances}. 097 * 098 * @param domain a non-{@code null} {@link Domain} 099 * 100 * @param bq a non-{@code null} {@link org.microbean.bean.Qualifiers} 101 * 102 * @param sq a non-{@code null} {@link Qualifiers} 103 * 104 * @param scopes a non-{@code null} {@link Scopes} 105 * 106 * @param considerActiveness an {@link AnnotationMirror} used to signal that <dfn>activeness</dfn> should be taken 107 * into consideration during typesafe resolution; may be {@code null} 108 * 109 * @exception NullPointerException if any argument is {@code null} 110 */ 111 public ScopedInstances(final Domain domain, 112 final org.microbean.bean.Qualifiers bq, 113 final Qualifiers sq, 114 final Scopes scopes, 115 final AnnotationMirror considerActiveness) { 116 super(); 117 this.domain = domain; 118 this.scopeletType = 119 domain.declaredType(null, domain.typeElement(Scopelet.class.getCanonicalName()), domain.wildcardType()); 120 this.scopes = requireNonNull(scopes, "scopes"); 121 this.bq = bq; 122 this.sq = requireNonNull(sq, "sq"); 123 this.considerActiveness = 124 considerActiveness == null ? 125 new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement("ConsiderActiveness")) : 126 considerActiveness; 127 } 128 129 130 /* 131 * Instance methods. 132 */ 133 134 135 /** 136 * Returns {@code true} if and only if the supplied {@link Id} is <dfn>proxiable</dfn>. 137 * 138 * @param id an {@link Id}; must not be {@code null} 139 * 140 * @return {@code true} if and only if the supplied {@link Id} is <dfn>proxiable</dfn> 141 * 142 * @exception NullPointerException if {@code id} is {@code null} 143 */ 144 @Override // Instances 145 public boolean proxiable(final Id id) { 146 return id.types().proxiable() && this.findNormalScope(id) != null; 147 } 148 149 /** 150 * Returns a {@link Selectable Selectable<AnnotatedConstruct, Bean<?>>} that properly considers the fact 151 * that a {@link Scopelet} may be {@linkplain Scopelet#active() active or inactive} at any point for any reason. 152 * 153 * @param selectable a {@link Selectable} that will be used for all {@link AnnotatedConstruct}s other than {@link 154 * Scopelet} types being sought for the purpose of instantiating or acquiring contextual instances; must not be 155 * {@code null} 156 * 157 * @return a non-{@code null} {@link Selectable} 158 * 159 * @exception NullPointerException if any argument is {@code null} 160 */ 161 public final Selectable<Annotated<? extends AnnotatedConstruct>, Bean<?>> selectableOf(final Selectable<? super Annotated<? extends AnnotatedConstruct>, Bean<?>> selectable) { 162 requireNonNull(selectable, "selectable"); 163 final Selectable<Annotated<? extends AnnotatedConstruct>, Bean<?>> scopeletSelectable = aac -> { 164 Bean<?> activeScopeletBean = null; 165 for (final Bean<?> b : selectable.select(aac)) { 166 if (((Scopelet<?>)b.factory()).active()) { 167 if (activeScopeletBean == null) { 168 activeScopeletBean = b; 169 } else { 170 throw new TooManyActiveScopeletsException("scopelet1: " + activeScopeletBean + "; scopelet: " + b); 171 } 172 } 173 } 174 return activeScopeletBean == null ? List.of() : List.of(activeScopeletBean); 175 }; 176 return aac -> 177 this.domain.sameType(this.scopeletType, type(aac)) && this.considerActiveness(aac.annotations()) ? 178 // A ScopedInstances is requesting a Scopelet for the purposes of instantiating something else. Use the 179 // scopeletSelectable. 180 scopeletSelectable.select(aac) : 181 // A ScopedInstances is requesting something "normal". Use the unadorned supplied Selectable. 182 selectable.select(aac); 183 } 184 185 private static final TypeMirror type(final Annotated<? extends AnnotatedConstruct> a) { 186 final AnnotatedConstruct ac = a.annotated(); 187 if (ac instanceof TypeMirror t) { 188 return t; 189 } 190 return ((Element)ac).asType(); 191 } 192 193 @Override // Instances 194 public final <I> Supplier<? extends I> supplier(final Bean<I> bean, final Creation<I> request) { 195 final Id id = bean.id(); 196 final AnnotationMirror scopeId = this.findScope(id); 197 // In this implementation, all ids must have scopes. 198 if (scopeId == null) { 199 throw new IllegalStateException(); 200 } 201 final Factory<I> factory = bean.factory(); 202 if (factory instanceof Scopelet<?> && this.primordial(scopeId)) { 203 // This is a request for, e.g., the Singleton Scopelet, which backs the primordial (notional) singleton scope. 204 // Scopelets are always their own factories. The Scopelet implementing the primordial scope (normally Singleton) 205 // is not made or stored by any other Scopelet. 206 I scopelet = factory.singleton(); 207 if (scopelet == null) { 208 return () -> factory.create(request); 209 } 210 assert scopelet == factory : "scopelet != factory: " + scopelet + " != " + factory; 211 return factory::singleton; 212 } 213 final Annotated<TypeMirror> ast = this.annotatedScopeletType(scopeId); 214 // Get the Scopelet and have it provide the instance 215 return () -> { 216 return request.<Scopelet<?>>reference(ast) 217 .instance(id, factory, request); // assumes Scopelet inactivity is handled 218 }; 219 } 220 221 /** 222 * Returns {@code true} if and only if the supplied {@link Collection} of {@link AnnotationMirror}s is deemed to 223 * designate something as <dfn>primordial</dfn>. 224 * 225 * <p>The default implementation of this method returns {@code true} if and only if the supplied {@link Collection} 226 * contains an {@link AnnotationMirror} that is the {@linkplain 227 * org.microbean.construct.element.AnnotationMirrors#sameAnnotation(AnnotationMirror, AnnotationMirror) same 228 * annotation} as the {@link org.microbean.bean.Qualifiers#primordialQualifier() primordial qualifier}.</p> 229 * 230 * @param c a {@link Collection} of {@link AnnotationMirror}s; must not be {@code null} 231 * 232 * @return {@code true} if and only if the supplied {@link Collection} of {@link AnnotationMirror}s is deemed to 233 * designate something as <dfn>primordial</dfn> 234 * 235 * @exception NullPointerException if {@code c} is {@code null} 236 * 237 * @see Qualifiers#primordialQualifier() 238 */ 239 private final boolean primordial(final Collection<? extends AnnotationMirror> c) { 240 for (final AnnotationMirror a : c) { 241 if (this.sq.primordialMetaQualifier(a)) { 242 return true; 243 } 244 } 245 return false; 246 } 247 248 private final boolean primordial(final AnnotationMirror a) { 249 return this.primordial(a.getAnnotationType().asElement().getAnnotationMirrors()); 250 } 251 252 private final AnnotationMirror findNormalScope(final Id id) { 253 AnnotationMirror scopeId = null; 254 for (final AnnotationMirror a : id.annotations()) { 255 if (this.bq.anyQualifier(a)) { 256 scopeId = this.scopes.findNormalScope(a.getAnnotationType().asElement().getAnnotationMirrors()); 257 break; 258 } 259 } 260 return scopeId; 261 } 262 263 private final AnnotationMirror findScope(final Id id) { 264 // Looks for an Any qualifier, which every bean must possess, and then looks on *it* for the scope. This allows us 265 // to "tunnel" scopes (which are Qualifiers in this implementation) without disrupting typesafe resolution, since 266 // meta-annotations are not part of an AnnotationMirror's equality computation. 267 AnnotationMirror scopeId = null; 268 for (final AnnotationMirror a : id.annotations()) { 269 if (this.bq.anyQualifier(a)) { 270 scopeId = this.scopes.findScope(a.getAnnotationType().asElement().getAnnotationMirrors()); 271 break; 272 } 273 } 274 if (scopeId == null) { 275 throw new IllegalArgumentException("id: " + id); 276 } 277 return scopeId; 278 } 279 280 private final Annotated<TypeMirror> annotatedScopeletType(final AnnotationMirror scopeId) { 281 return Annotated.of(new UniversalType(List.of(scopeId, this.considerActiveness), 282 this.scopeletType, 283 this.domain)); 284 } 285 286 private final boolean considerActiveness(final Collection<? extends AnnotationMirror> c) { 287 for (final AnnotationMirror a : c) { 288 if (sameAnnotation(this.considerActiveness, a)) { 289 return true; 290 } 291 } 292 return false; 293 } 294 295}