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.bean; 015 016import java.lang.constant.ClassDesc; 017import java.lang.constant.Constable; 018import java.lang.constant.ConstantDesc; 019import java.lang.constant.DynamicConstantDesc; 020 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.List; 024import java.util.Map; 025import java.util.Optional; 026 027import java.util.function.Predicate; 028 029import javax.lang.model.element.AnnotationMirror; 030import javax.lang.model.element.Element; 031import javax.lang.model.element.ExecutableElement; 032import javax.lang.model.element.Name; 033import javax.lang.model.element.QualifiedNameable; 034import javax.lang.model.element.TypeElement; 035import javax.lang.model.element.VariableElement; 036 037import org.microbean.construct.Domain; 038 039import org.microbean.construct.element.SyntheticAnnotationMirror; 040import org.microbean.construct.element.SyntheticAnnotationTypeElement; 041import org.microbean.construct.element.SyntheticAnnotationValue; 042import org.microbean.construct.element.SyntheticName; 043import org.microbean.construct.element.UniversalElement; 044 045import static java.lang.constant.ConstantDescs.BSM_INVOKE; 046import static java.lang.constant.ConstantDescs.NULL; 047 048import static java.lang.constant.MethodHandleDesc.ofConstructor; 049 050import static java.util.Collections.unmodifiableList; 051 052import static java.util.Objects.requireNonNull; 053 054import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE; 055import static javax.lang.model.element.ElementKind.ENUM_CONSTANT; 056 057/** 058 * A utility class for working with <dfn>qualifiers</dfn>. 059 * 060 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 061 * 062 * @see org.microbean.assign.Qualifiers 063 */ 064// Experimental. First foray into replacing Attributes with (synthetic) AnnotationMirrors. 065public class Qualifiers implements Constable { 066 067 068 /* 069 * Instance fields. 070 */ 071 072 073 private final org.microbean.assign.Qualifiers aq; 074 075 private final AnnotationMirror anyQualifier; 076 077 private final List<AnnotationMirror> anyQualifiers; 078 079 private final AnnotationMirror defaultQualifier; 080 081 private final List<AnnotationMirror> defaultQualifiers; 082 083 private final List<AnnotationMirror> anyAndDefaultQualifiers; 084 085 086 /* 087 * Constructors. 088 */ 089 090 091 /** 092 * Creates a new {@link Qualifiers}. 093 * 094 * @param d a non-{@code null} {@link Domain} 095 * 096 * @param aq a non-{@code null} {@link org.microbean.assign.Qualifiers} 097 * 098 * @exception NullPointerException if {@code aq} is {@code null} 099 * 100 * @see #Qualifiers(Domain, org.microbean.assign.Qualifiers, AnnotationMirror, AnnotationMirror) 101 */ 102 public Qualifiers(final Domain d, final org.microbean.assign.Qualifiers aq) { 103 this(d, aq, null, null); 104 } 105 106 /** 107 * Creates a new {@link Qualifiers}. 108 * 109 * @param aq a non-{@code null} {@link org.microbean.assign.Qualifiers} 110 * 111 * @param anyQualifier a non-{@code null} {@link AnnotationMirror} representing the <dfn>any qualifier</dfn> 112 * 113 * @param defaultQualifier a non-{@code null} {@link AnnotationMirror} representing the <dfn>default qualifier</dfn> 114 * 115 * @exception NullPointerException if any argument is {@code null} 116 * 117 * @see org.microbean.assign.Qualifiers 118 */ 119 public Qualifiers(final org.microbean.assign.Qualifiers aq, 120 final AnnotationMirror anyQualifier, 121 final AnnotationMirror defaultQualifier) { 122 this(null, aq, requireNonNull(anyQualifier, "anyQualifier"), requireNonNull(defaultQualifier, "defaultQualifier")); 123 } 124 125 /** 126 * Creates a new {@link Qualifiers}. 127 * 128 * @param d a {@link Domain}; may be {@code null} if both {@code anyQualifier} and {@code defaultQualifier} are 129 * non-{@code null} 130 * 131 * @param aq a non-{@code null} {@link org.microbean.assign.Qualifiers} 132 * 133 * @param anyQualifier a (possibly {@code null}) {@link AnnotationMirror} representing the <dfn>any qualifier</dfn>; 134 * if {@code null}, then {@code d} must be non-{@code null} 135 * 136 * @param defaultQualifier a (possibly {@code null}) {@link AnnotationMirror} representing the <dfn>default 137 * qualifier</dfn>; if {@code null}, then {@code d} must be non-{@code null} 138 * 139 * @exception NullPointerException if {@code aq} is {@code null} or if {@code d} is {@code null} in certain situations 140 * 141 * @see org.microbean.assign.Qualifiers 142 */ 143 public Qualifiers(final Domain d, 144 final org.microbean.assign.Qualifiers aq, 145 final AnnotationMirror anyQualifier, 146 final AnnotationMirror defaultQualifier) { 147 super(); 148 this.aq = requireNonNull(aq, "aq"); 149 if (anyQualifier == null || defaultQualifier == null) { 150 final List<? extends AnnotationMirror> as = d.typeElement("java.lang.annotation.Documented").getAnnotationMirrors(); 151 assert as.size() == 3; // @Documented, @Retention, @Target, in that order, all annotated in turn with each other 152 final List<SyntheticAnnotationValue> savs = new ArrayList<>(4); 153 for (final Element e : d.typeElement("java.lang.annotation.ElementType").getEnclosedElements()) { 154 if (e.getKind() == ENUM_CONSTANT && e instanceof VariableElement ve) { 155 final Name n = e.getSimpleName(); 156 if (n.contentEquals("TYPE") || n.contentEquals("METHOD") || n.contentEquals("FIELD") || n.contentEquals("PARAMETER")) { 157 savs.add(new SyntheticAnnotationValue(ve)); 158 } 159 } 160 } 161 final AnnotationMirror targetAnnotation = 162 new SyntheticAnnotationMirror(d.typeElement("java.lang.annotation.Target"), Map.of("value", savs)); 163 final List<AnnotationMirror> metaAnnotations = 164 List.of(aq.metaQualifier(), 165 as.get(1), // @Retention 166 targetAnnotation, // @Target 167 as.get(0)); // @Documented 168 this.anyQualifier = 169 anyQualifier == null ? 170 new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(metaAnnotations, "Any")) : 171 anyQualifier; 172 this.defaultQualifier = 173 defaultQualifier == null ? 174 new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(metaAnnotations, "Default")) : 175 defaultQualifier; 176 } else { 177 this.anyQualifier = anyQualifier; 178 this.defaultQualifier = defaultQualifier; 179 } 180 this.anyQualifiers = List.of(this.anyQualifier); 181 this.defaultQualifiers = List.of(this.defaultQualifier); 182 this.anyAndDefaultQualifiers = List.of(this.anyQualifier, this.defaultQualifier); 183 } 184 185 186 /* 187 * Instance methods. 188 */ 189 190 191 /** 192 * Returns a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain #anyQualifier() 193 * <dfn>any qualifier</dfn>} and {@link #defaultQualifier() <dfn>default qualifier</dfn>}. 194 * 195 * @return a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain #anyQualifier() 196 * <dfn>any qualifier</dfn>} and {@linkplain #defaultQualifier() <dfn>default qualifier</dfn>} 197 */ 198 public final List<AnnotationMirror> anyAndDefaultQualifiers() { 199 return this.anyAndDefaultQualifiers; 200 } 201 202 /** 203 * Returns the non-{@code null}, determinate {@link AnnotationMirror} representing the <dfn>any qualifier</dfn>. 204 * 205 * @return the non-{@code null}, determinate {@link AnnotationMirror} representing the <dfn>any qualifier</dfn> 206 */ 207 public final AnnotationMirror anyQualifier() { 208 return this.anyQualifier; 209 } 210 211 /** 212 * Returns a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain #anyQualifier() 213 * <dfn>any qualifier</dfn>}. 214 * 215 * @return a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain #anyQualifier() 216 * <dfn>any qualifier</dfn>} 217 */ 218 public final List<AnnotationMirror> anyQualifiers() { 219 return this.anyQualifiers; 220 } 221 222 /** 223 * Returns {@code true} if and only if the supplied {@link AnnotationMirror} is {@linkplain 224 * org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror) the same} as the {@linkplain 225 * #anyQualifier() <dfn>any qualifier</dfn>}. 226 * 227 * @param a a non-{@code null} {@link AnnotationMirror} 228 * 229 * @return {@code true} if and only if the supplied {@link AnnotationMirror} is {@linkplain 230 * org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror) the same} as the {@linkplain 231 * #anyQualifier() <dfn>any qualifier</dfn>} 232 * 233 * @exception NullPointerException if {@code a} is {@code null} 234 * 235 * @see #anyQualifier() 236 * 237 * @see org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror) 238 */ 239 public final boolean anyQualifier(final AnnotationMirror a) { 240 return this.aq.sameAnnotation(this.anyQualifier, a); 241 } 242 243 /** 244 * Returns the non-{@code null}, determinate {@link AnnotationMirror} representing the <dfn>default qualifier</dfn>. 245 * 246 * @return the non-{@code null}, determinate {@link AnnotationMirror} representing the <dfn>default qualifier</dfn> 247 */ 248 public final AnnotationMirror defaultQualifier() { 249 return this.defaultQualifier; 250 } 251 252 /** 253 * Returns a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain #defaultQualifier() 254 * <dfn>default qualifier</dfn>}. 255 * 256 * @return a non-{@code null}, determinate, immutable {@link List} housing only the {@linkplain #defaultQualifier() 257 * <dfn>default qualifier</dfn>} 258 */ 259 public final List<AnnotationMirror> defaultQualifiers() { 260 return this.defaultQualifiers; 261 } 262 263 /** 264 * Returns {@code true} if and only if the supplied {@link AnnotationMirror} is {@linkplain 265 * org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror) the same} as the {@linkplain 266 * #defaultQualifier() <dfn>default qualifier</dfn>}. 267 * 268 * @param a a non-{@code null} {@link AnnotationMirror} 269 * 270 * @return {@code true} if and only if the supplied {@link AnnotationMirror} is {@linkplain 271 * org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror) the same} as the {@linkplain 272 * #defaultQualifier() <dfn>default qualifier</dfn>} 273 * 274 * @exception NullPointerException if {@code a} is {@code null} 275 * 276 * @see #defaultQualifier() 277 * 278 * @see org.microbean.assign.Qualifiers#sameAnnotation(AnnotationMirror, AnnotationMirror) 279 */ 280 public final boolean defaultQualifier(final AnnotationMirror a) { 281 return this.aq.sameAnnotation(this.defaultQualifier, a); 282 } 283 284 @Override // Constable 285 public Optional<? extends ConstantDesc> describeConstable() { 286 final ClassDesc cdAnnotationMirror = AnnotationMirror.class.describeConstable().orElseThrow(); 287 return this.aq.describeConstable() 288 .flatMap(aqDesc -> (this.anyQualifier instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty()) 289 .flatMap(anyQualifierDesc -> (this.defaultQualifier instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty()) 290 .map(defaultQualifierDesc -> DynamicConstantDesc.of(BSM_INVOKE, 291 ofConstructor(this.getClass().describeConstable().orElseThrow(), 292 Domain.class.describeConstable().orElseThrow(), 293 org.microbean.assign.Qualifiers.class.describeConstable().orElseThrow(), 294 cdAnnotationMirror, 295 cdAnnotationMirror), 296 NULL, 297 aqDesc, 298 anyQualifierDesc, 299 defaultQualifierDesc)))); 300 } 301 302}