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.ArrayList; 017import java.util.Collection; 018import java.util.List; 019import java.util.Map; 020 021import javax.lang.model.element.AnnotationMirror; 022import javax.lang.model.element.Element; 023import javax.lang.model.element.Name; 024import javax.lang.model.element.VariableElement; 025 026// import org.microbean.bean.Qualifiers; 027 028import org.microbean.construct.Domain; 029 030import org.microbean.construct.element.SyntheticAnnotationMirror; 031import org.microbean.construct.element.SyntheticAnnotationTypeElement; 032import org.microbean.construct.element.SyntheticAnnotationValue; 033 034import static java.util.Objects.requireNonNull; 035 036import static java.util.function.Predicate.not; 037 038import static javax.lang.model.element.ElementKind.ENUM_CONSTANT; 039import static javax.lang.model.element.ElementKind.METHOD; 040 041import static org.microbean.construct.element.AnnotationMirrors.sameAnnotation; 042import static org.microbean.construct.element.AnnotationMirrors.streamBreadthFirst; 043 044/** 045 * A utility class for working with <dfn>scopes</dfn> and their identifiers. 046 * 047 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 048 */ 049public class Scopes { // deliberately not final 050 051 private final AnnotationMirror metaNormalScope; 052 053 private final AnnotationMirror metaPseudoScope; 054 055 private final AnnotationMirror noneScope; 056 057 private final AnnotationMirror singletonScope; 058 059 private final org.microbean.assign.Qualifiers aq; 060 061 private final Qualifiers sq; 062 063 /** 064 * Creates a new {@link Scopes}. 065 * 066 * @param d a non-{@code null} {@link Domain} 067 * 068 * @param aq a non-{@code null} {@link org.microbean.assign.Qualifiers} 069 * 070 * @param sq a non-{@code null} {@link Qualifiers} 071 * 072 * @exception NullPointerException if any argument is {@code null} 073 * 074 * @see #Scopes(Domain, org.microbean.assign.Qualifiers, Qualifiers, AnnotationMirror, AnnotationMirror, AnnotationMirror, 075 * AnnotationMirror) 076 */ 077 public Scopes(final Domain d, final org.microbean.assign.Qualifiers aq, final Qualifiers sq) { 078 this(d, aq, sq, null, null, null, null); 079 } 080 081 /** 082 * Creates a new {@link Scopes}. 083 * 084 * @param aq a non-{@code null} {@link org.microbean.assign.Qualifiers} 085 * 086 * @param sq a non-{@code null} {@link Qualifiers} 087 * 088 * @param metaNormalScope a non-{@code null} {@link AnnotationMirror} designating another {@link AnnotationMirror} as 089 * identifying a <dfn>normal scope</dfn> 090 * 091 * @param metaPseudoScope a non-{@code null} {@link AnnotationMirror} designating another {@link AnnotationMirror} as 092 * identifying a <dfn>pseudo scope</dfn> 093 * 094 * @param singletonScope a non-{@code null} {@link AnnotationMirror} identifying the <dfn>singleton scope</dfn> 095 * 096 * @param noneScope a non-{@code null} {@link AnnotationMirror} identifying the <dfn>none scope</dfn> 097 * 098 * @exception NullPointerException if any argument is {@code null} 099 * 100 * @see #Scopes(Domain, org.mcirobean.assign.Qualifiers, Qualifires, AnnotationMirror, AnnotationMirror, 101 * AnnotationMirror, AnnotationMirror) 102 */ 103 public Scopes(final org.microbean.assign.Qualifiers aq, 104 final Qualifiers sq, 105 final AnnotationMirror metaNormalScope, 106 final AnnotationMirror metaPseudoScope, 107 final AnnotationMirror singletonScope, 108 final AnnotationMirror noneScope) { 109 this(null, 110 aq, 111 sq, 112 requireNonNull(metaNormalScope, "metaNormalScope"), 113 requireNonNull(metaPseudoScope, "metaPseudoScope"), 114 requireNonNull(singletonScope, "singletonScope"), 115 requireNonNull(noneScope, "noneScope")); 116 } 117 118 /** 119 * Creates a new {@link Scopes}. 120 * 121 * @param d a {@link Domain}; if {@code null}, then {@code metaNormalScope}, {@code metaPseudoScope}, {@code 122 * singletonScope}, and {@code noneScope} must be non-{@code null} 123 * 124 * @param aq a non-{@code null} {@link org.microbean.assign.Qualifiers} 125 * 126 * @param sq a non-{@code null} {@link Qualifiers} 127 * 128 * @param metaNormalScope an {@link AnnotationMirror} designating another {@link AnnotationMirror} as identifying a 129 * <dfn>normal scope</dfn>; may be {@code null} in which case {@code d} must be non-{@code null} 130 * 131 * @param metaPseudoScope an {@link AnnotationMirror} designating another {@link AnnotationMirror} as identifying a 132 * <dfn>pseudo scope</dfn>; may be {@code null} in which case {@code d} must be non-{@code null} 133 * 134 * @param singletonScope an {@link AnnotationMirror} identifying the <dfn>singleton scope</dfn>; may be {@code null} 135 * in which case {@code d} must be non-{@code null} 136 * 137 * @param noneScope an {@link AnnotationMirror} identifying the <dfn>none scope</dfn>; may be {@code null} in which 138 * case {@code d} must be non-{@code null} 139 * 140 * @exception NullPointerException if {@code aq} or {@code sq} is {@code null}, or if {@code d} is {@code null} in 141 * certain situations 142 */ 143 public Scopes(final Domain d, 144 final org.microbean.assign.Qualifiers aq, 145 final Qualifiers sq, 146 final AnnotationMirror metaNormalScope, 147 final AnnotationMirror metaPseudoScope, 148 final AnnotationMirror singletonScope, 149 final AnnotationMirror noneScope) { 150 super(); 151 this.aq = requireNonNull(aq, "aq"); 152 this.sq = requireNonNull(sq, "sq"); 153 if (metaNormalScope == null || metaPseudoScope == null || singletonScope == null || noneScope == null) { 154 155 final List<? extends AnnotationMirror> as = d.typeElement("java.lang.annotation.Documented").getAnnotationMirrors(); 156 assert as.size() == 3; // @Documented, @Retention, @Target, in that order, all annotated in turn with each other 157 158 final AnnotationMirror documentedAnnotation = as.get(0); 159 final AnnotationMirror retentionAnnotation = as.get(1); 160 final List<AnnotationMirror> documentedRetentionTarget = 161 List.of(documentedAnnotation, // @Documented 162 retentionAnnotation, // @Retention(TargetType.RUNTIME) (happens fortuitously to be RUNTIME) 163 as.get(2)); // @Target(ANNOTATION_TYPE) 164 165 this.metaNormalScope = 166 metaNormalScope == null ? 167 new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(documentedRetentionTarget, "NormalScope")) : 168 metaNormalScope; 169 170 this.metaPseudoScope = 171 metaPseudoScope == null ? 172 new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(documentedRetentionTarget, "Scope")) : 173 metaPseudoScope; 174 175 final List<SyntheticAnnotationValue> savs = new ArrayList<>(4); 176 for (final Element e : d.typeElement("java.lang.annotation.ElementType").getEnclosedElements()) { 177 if (e.getKind() == ENUM_CONSTANT && e instanceof VariableElement ve) { 178 final Name n = e.getSimpleName(); 179 if (n.contentEquals("TYPE") || n.contentEquals("METHOD") || n.contentEquals("FIELD") || n.contentEquals("PARAMETER")) { 180 savs.add(new SyntheticAnnotationValue(ve)); 181 } 182 } 183 } 184 final List<AnnotationMirror> metaAnnotations = 185 List.of(aq.metaQualifier(), 186 this.metaPseudoScope, 187 sq.primordialMetaQualifier(), 188 retentionAnnotation, 189 new SyntheticAnnotationMirror(d.typeElement("java.lang.annotation.Target"), Map.of("value", savs)), 190 documentedAnnotation); 191 192 this.singletonScope = 193 singletonScope == null ? 194 new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(metaAnnotations, "Singleton")) : 195 singletonScope; 196 197 this.noneScope = 198 noneScope == null ? 199 new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(metaAnnotations, "None")) : 200 noneScope; 201 } else { 202 this.metaNormalScope = metaNormalScope; 203 this.metaPseudoScope = metaPseudoScope; 204 this.singletonScope = singletonScope; 205 this.noneScope = noneScope; 206 } 207 } 208 209 /** 210 * Returns the first {@link AnnotationMirror} that is present in the supplied {@link Collection} of {@link 211 * AnnotationMirror}s, and their {@linkplain Element#getAnnotationMirrors() meta-annotations}, for which an invocation 212 * of the {@link #normalScope(AnnotationMirror)} method returns {@code true}, or {@code null}, if no such {@link 213 * AnnotationMirror} exists. 214 * 215 * <p>The search is conducted in a breadth-first manner.</p> 216 * 217 * @param c a {@link Collection} of {@link AnnotationMirror}s; must not be {@code null} 218 * 219 * @return the first {@link AnnotationMirror} that is present in the supplied {@link Collection} of {@link 220 * AnnotationMirror}s, and their {@linkplain Element#getAnnotationMirrors() meta-annotations}, for which an invocation 221 * of the {@link #normalScope(AnnotationMirror)} method returns {@code true}, or {@code null}, if no such {@link 222 * AnnotationMirror} exists 223 * 224 * @exception NullPointerException if {@code c} is {@code null} 225 */ 226 public AnnotationMirror findNormalScope(final Collection<? extends AnnotationMirror> c) { 227 return c.isEmpty() ? null : streamBreadthFirst(c) 228 .dropWhile(not(this::normalScope)) 229 .findFirst() 230 .orElse(null); 231 } 232 233 /** 234 * Returns the first {@link AnnotationMirror} that is present in the supplied {@link Collection} of {@link 235 * AnnotationMirror}s, and their {@linkplain Element#getAnnotationMirrors() meta-annotations}, for which an invocation 236 * of the {@link #scope(AnnotationMirror)} method returns {@code true}, or {@code null}, if no such {@link 237 * AnnotationMirror} exists. 238 * 239 * <p>The search is conducted in a breadth-first manner.</p> 240 * 241 * @param c a {@link Collection} of {@link AnnotationMirror}s; must not be {@code null} 242 * 243 * @return the first {@link AnnotationMirror} that is present in the supplied {@link Collection} of {@link 244 * AnnotationMirror}s, and their {@linkplain Element#getAnnotationMirrors() meta-annotations}, for which an invocation 245 * of the {@link #scope(AnnotationMirror)} method returns {@code true}, or {@code null}, if no such {@link 246 * AnnotationMirror} exists 247 * 248 * @exception NullPointerException if {@code c} is {@code null} 249 */ 250 public AnnotationMirror findScope(final Collection<? extends AnnotationMirror> c) { 251 return c.isEmpty() ? null : streamBreadthFirst(c) 252 .dropWhile(not(this::scope)) 253 .findFirst() 254 .orElse(null); 255 } 256 257 /** 258 * Returns a non-{@code null}, determinate {@link AnnotationMirror} representing the identifier for the <dfn>none</dfn> 259 * scope. 260 * 261 * @return a non-{@code null}, determinate {@link AnnotationMirror} representing the identifier for the <dfn>none</dfn> 262 * scope 263 */ 264 public final AnnotationMirror noneScope() { 265 return this.noneScope; 266 } 267 268 /** 269 * Returns {@code true} if the supplied {@link AnnotationMirror} has elements that might be used to indicate that it 270 * is a <dfn>normal scope</dfn>. 271 * 272 * @param a a non-{@code null}, determinate {@link AnnotationMirror} 273 * 274 * @return {@code true} if the supplied {@link AnnotationMirror} has elements that might be used to indicate that it 275 * is a <dfn>normal scope</dfn> 276 * 277 * @exception NullPointerException if {@code a} is {@code null} 278 * 279 * @see #scope(AnnotationMirror) 280 */ 281 public boolean normal(final AnnotationMirror a) { 282 for (final Element ee : a.getAnnotationType().asElement().getEnclosedElements()) { 283 if (ee.getKind() == METHOD && ee.getSimpleName().contentEquals("normal")) { 284 return true; 285 } 286 } 287 return false; 288 } 289 290 /** 291 * Returns the non-{@code null}, determinate {@link AnnotationMirror} that can be used to designate other {@link 292 * AnnotationMirror}s as <dfn>normal scopes</dfn>. 293 * 294 * @return the non-{@code null}, determinate {@link AnnotationMirror} that can be used to designate other {@link 295 * AnnotationMirror}s as <dfn>normal scopes</dfn> 296 */ 297 public final AnnotationMirror metaNormalScope() { 298 return this.metaNormalScope; 299 } 300 301 /** 302 * Returns the non-{@code null}, determinate {@link AnnotationMirror} that can be used to designate other {@link 303 * AnnotationMirror}s as <dfn>scopes</dfn>. 304 * 305 * @return the non-{@code null}, determinate {@link AnnotationMirror} that can be used to designate other {@link 306 * AnnotationMirror}s as <dfn>scopes</dfn> 307 */ 308 public final AnnotationMirror metaPseudoScope() { 309 return this.metaPseudoScope; 310 } 311 312 /** 313 * Returns {@code true} if and only if the supplied {@link AnnotationMirror} is a <dfn>normal scope identifier</dfn>. 314 * 315 * @param a an {@link AnnotationMirror}; must not be {@code null} 316 * 317 * @return {@code true} if and only if the supplied {@link AnnotationMirror} is a <dfn>normal scope identifier</dfn> 318 * 319 * @exception NullPointerException if {@code a} is {@code null} 320 */ 321 public boolean normalScope(final AnnotationMirror a) { 322 // In this implementation, all scopes, normal or otherwise, must also be qualifiers. 323 boolean metaNormalScopeFound = false; 324 boolean metaQualifierFound = false; 325 for (final AnnotationMirror ma : a.getAnnotationType().asElement().getAnnotationMirrors()) { 326 if (!metaNormalScopeFound) { 327 if (sameAnnotation(ma, this.metaNormalScope())) { 328 metaNormalScopeFound = true; 329 continue; 330 } 331 } 332 333 if (!metaQualifierFound) { 334 if (sameAnnotation(ma, this.aq.metaQualifier())) { 335 metaQualifierFound = true; 336 continue; 337 } 338 } 339 340 if (sameAnnotation(ma, this.metaPseudoScope())) { 341 // Can't be a pseudo- and a normal scope at the same time. 342 return false; 343 } 344 } 345 return metaNormalScopeFound && metaQualifierFound; 346 } 347 348 /** 349 * Returns {@code true} if and only if the supplied {@link AnnotationMirror} is a <dfn>pseudo-scope identifier</dfn>. 350 * 351 * @param a an {@link AnnotationMirror}; must not be {@code null} 352 * 353 * @return {@code true} if and only if the supplied {@link AnnotationMirror} is a <dfn>pseudo-scope identifier</dfn> 354 * 355 * @exception NullPointerException if {@code a} is {@code null} 356 */ 357 public boolean pseudoScope(final AnnotationMirror a) { 358 // In this implementation, all scopes, pseudo- or otherwise, must also be qualifiers. 359 // TODO: should also check that normalScope annotation is not present 360 boolean metaPseudoScopeFound = false; 361 boolean metaQualifierFound = false; 362 for (final AnnotationMirror ma : a.getAnnotationType().asElement().getAnnotationMirrors()) { 363 if (!metaPseudoScopeFound) { 364 if (sameAnnotation(ma, this.metaPseudoScope())) { 365 metaPseudoScopeFound = true; 366 continue; 367 } 368 } 369 370 if (!metaQualifierFound) { 371 if (sameAnnotation(ma, this.aq.metaQualifier())) { 372 metaQualifierFound = true; 373 continue; 374 } 375 } 376 377 if (sameAnnotation(ma, this.metaNormalScope())) { 378 // Can't be a pseudo- and a normal scope at the same time. 379 return false; 380 } 381 } 382 return metaPseudoScopeFound && metaQualifierFound; 383 } 384 385 /** 386 * Returns {@code true} if and only if the supplied {@link AnnotationMirror} is a <dfn>scope identifier</dfn> (either 387 * a {@linkplain #pseudoScope(AnnotationMirror) <dfn>pseudo-scope identifier</dfn>} or a {@linkplain 388 * #normalScope(AnnotationMirror) <dfn>normal scope identifier</dfn>}. 389 * 390 * @param a an {@link AnnotationMirror}; must not be {@code null} 391 * 392 * @return {@code true} if and only if the supplied {@link AnnotationMirror} is a <dfn>scope identifier</dfn> (either 393 * a {@linkplain #pseudoScope(AnnotationMirror) <dfn>pseudo-scope identifier</dfn>} or a {@linkplain 394 * #normalScope(AnnotationMirror) <dfn>normal scope identifier</dfn>} 395 * 396 * @exception NullPointerException if {@code a} is {@code null} 397 */ 398 public final boolean scope(final AnnotationMirror a) { 399 return this.pseudoScope(a) || this.normalScope(a); 400 } 401 402 /** 403 * Returns a non-{@code null}, determinate {@link AnnotationMirror} representing the identifier for the 404 * <dfn>singleton</dfn> pseudo-scope. 405 * 406 * @return a non-{@code null}, determinate {@link AnnotationMirror} representing the identifier for the 407 * <dfn>singleton</dfn> pseudo-scope 408 */ 409 public final AnnotationMirror singletonScope() { 410 return this.singletonScope; 411 } 412 413}