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.construct.element; 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.Iterator; 022import java.util.LinkedHashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Map.Entry; 026import java.util.Optional; 027 028import javax.lang.model.element.AnnotationMirror; 029import javax.lang.model.element.AnnotationValue; 030import javax.lang.model.element.Element; 031import javax.lang.model.element.ExecutableElement; 032import javax.lang.model.element.QualifiedNameable; 033import javax.lang.model.element.TypeElement; 034 035import javax.lang.model.type.DeclaredType; 036 037import org.microbean.construct.constant.Constables; 038 039import static java.lang.constant.ConstantDescs.BSM_INVOKE; 040import static java.lang.constant.ConstantDescs.CD_Map; 041 042import static java.lang.constant.MethodHandleDesc.ofConstructor; 043 044import static java.util.Collections.unmodifiableMap; 045 046import static java.util.HashMap.newHashMap; 047 048import static java.util.LinkedHashMap.newLinkedHashMap; 049 050import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE; 051 052import static javax.lang.model.util.ElementFilter.methodsIn; 053 054/** 055 * An <strong>experimental</strong> {@link AnnotationMirror} implementation that is partially or wholly synthetic. 056 * 057 * <p>It is possible to create {@link SyntheticAnnotationMirror} instances representing annotations that a Java compiler 058 * will never produce. For example, <a 059 * href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-9.html#jls-9.6.1">annotations cannot refer to each 060 * other, directly or indirectly</a>, but two {@link SyntheticAnnotationMirror}s may do so.</p> 061 * 062 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 063 * 064 * @spec https://docs.oracle.com/javase/specs/jls/se25/html/jls-9.html#jls-9.6.1 Java Language Specification, section 065 * 9.6.1 066 */ 067public final class SyntheticAnnotationMirror implements AnnotationMirror, Constable { 068 069 070 /* 071 * Instance fields. 072 */ 073 074 075 private final TypeElement annotationTypeElement; 076 077 private final Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues; 078 079 080 /* 081 * Constructors. 082 */ 083 084 085 /** 086 * Creates a new {@link SyntheticAnnotationMirror}. 087 * 088 * @param annotationTypeElement a {@link TypeElement} representing an annotation type; must not be {@code null}; must 089 * return {@link javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from its {@link 090 * Element#getKind() getKind()} method; {@link SyntheticAnnotationTypeElement} implementations are strongly preferred 091 * 092 * @exception NullPointerException if {@code annotationTypeElement} is {@code null} 093 * 094 * @exception IllegalArgumentException if {@code annotationTypeElement} does not return {@link 095 * javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from an invocation of its {@link 096 * Element#getKind() getKind()} method, or if {@code values} has more entries in it than {@code annotationTypeElement} 097 * has {@linkplain Element#getEnclosedElements() anotation elements} 098 * 099 * @see #SyntheticAnnotationMirror(TypeElement, Map) 100 */ 101 public SyntheticAnnotationMirror(final TypeElement annotationTypeElement) { 102 this(annotationTypeElement, Map.of()); 103 } 104 105 /** 106 * Creates a new {@link SyntheticAnnotationMirror}. 107 * 108 * @param annotationTypeElement a {@link TypeElement} representing an annotation type; must not be {@code null}; must 109 * return {@link javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from its {@link 110 * Element#getKind() getKind()} method; {@link SyntheticAnnotationTypeElement} implementations are strongly preferred 111 * 112 * @param values a {@link Map} of annotation values indexed by annotation element name; must not be {@code null}; must 113 * contain only values that are permissible for annotation elements 114 * 115 * @exception NullPointerException if any argument is {@code null} 116 * 117 * @exception IllegalArgumentException if {@code annotationTypeElement} does not return {@link 118 * javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from an invocation of its {@link 119 * Element#getKind() getKind()} method, or if {@code values} has more entries in it than {@code annotationTypeElement} 120 * has {@linkplain Element#getEnclosedElements() anotation elements} 121 */ 122 public SyntheticAnnotationMirror(final TypeElement annotationTypeElement, 123 final Map<? extends String, ?> values) { 124 super(); 125 if (annotationTypeElement.getKind() != ANNOTATION_TYPE) { 126 throw new IllegalArgumentException("annotationTypeElement: " + annotationTypeElement); 127 } 128 this.annotationTypeElement = annotationTypeElement; 129 final LinkedHashMap<ExecutableElement, AnnotationValue> m = newLinkedHashMap(values.size()); 130 final List<? extends ExecutableElement> methods = methodsIn(annotationTypeElement.getEnclosedElements()); 131 for (final ExecutableElement e : methods) { 132 final Object value = values.get(e.getSimpleName().toString()); // default value deliberately not included 133 if (value == null) { 134 if (e.getDefaultValue() == null) { 135 // There has to be a value somewhere, or annotationTypeElement or values is illegal 136 throw new IllegalArgumentException("annotationTypeElement: " + annotationTypeElement + "; values: " + values); 137 } 138 // Default values are excluded from the map on purpose, following the contract of 139 // AnnotationValue#getElementValues(). 140 } else { 141 m.put(e, value instanceof AnnotationValue av ? av : new SyntheticAnnotationValue(value)); 142 } 143 } 144 if (values.size() > m.size()) { 145 throw new IllegalArgumentException("values: " + values); 146 } 147 this.elementValues = m.isEmpty() ? Map.of() : unmodifiableMap(m); 148 } 149 150 /** 151 * Creates a new {@link SyntheticAnnotationMirror} that is an effective <dfn>copy</dfn> of the supplied {@link 152 * AnnotationMirror}. 153 * 154 * @param a a non-{@code null} {@link AnnotationMirror} to semantically copy 155 * 156 * @exception NullPointerException if {@code a} is {@code null} 157 */ 158 // (Copy constructor.) 159 public SyntheticAnnotationMirror(final AnnotationMirror a) { 160 super(); 161 this.annotationTypeElement = new SyntheticAnnotationTypeElement((TypeElement)a.getAnnotationType().asElement()); 162 final Map<? extends ExecutableElement, ? extends AnnotationValue> originalElementValues = a.getElementValues(); 163 if (originalElementValues.isEmpty()) { 164 // If there are no explicit values, then...there are no explicit values whether the annotation interface type 165 // contains/encloses any elements at all. 166 this.elementValues = Map.of(); 167 } else { 168 // There are explicit values. That also means that the annotation interface type contains/encloses at least one 169 // element. 170 final List<ExecutableElement> syntheticElements = methodsIn(this.annotationTypeElement.getEnclosedElements()); 171 assert !syntheticElements.isEmpty(); 172 final Map<ExecutableElement, AnnotationValue> newElementValues = newLinkedHashMap(originalElementValues.size()); 173 for (final Entry<? extends ExecutableElement, ? extends AnnotationValue> originalEntry : originalElementValues.entrySet()) { 174 final ExecutableElement originalElement = originalEntry.getKey(); 175 final ExecutableElement syntheticElement = element(syntheticElements, originalElement.getSimpleName()); 176 if (syntheticElement != null) { 177 newElementValues.put(syntheticElement, originalEntry.getValue()); 178 } 179 } 180 this.elementValues = unmodifiableMap(newElementValues); 181 } 182 } 183 184 185 /* 186 * Instance methods. 187 */ 188 189 190 @Override // Constable 191 public final Optional<DynamicConstantDesc<SyntheticAnnotationMirror>> describeConstable() { 192 return (this.annotationTypeElement instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty()) 193 .flatMap(elementDesc -> Constables.describe(this.toSyntheticValues(), 194 String::describeConstable, 195 SyntheticAnnotationMirror::describeAnnotationValue) 196 .map(valuesDesc -> DynamicConstantDesc.ofNamed(BSM_INVOKE, 197 this.getAnnotationType().asElement().getSimpleName().toString(), // supposed to be unqualified, I guess 198 this.getClass().describeConstable().orElseThrow(), 199 ofConstructor(this.getClass().describeConstable().orElseThrow(), 200 TypeElement.class.describeConstable().orElseThrow(), 201 CD_Map), 202 elementDesc, 203 valuesDesc))); 204 } 205 206 @Override // AnnotationMirror 207 public final DeclaredType getAnnotationType() { 208 return (DeclaredType)this.annotationTypeElement.asType(); 209 } 210 211 @Override // AnnotationMirror 212 public final Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues() { 213 return this.elementValues; 214 } 215 216 @Override 217 public final String toString() { 218 return "@" + this.annotationTypeElement.toString(); // TODO: not anywhere near good enough 219 } 220 221 // Called by describeConstable(). 222 private final Map<? extends String, ?> toSyntheticValues() { 223 if (this.elementValues.isEmpty()) { 224 return Map.of(); 225 } 226 final Map<String, Object> rv = newHashMap(this.elementValues.size()); 227 for (final Entry<? extends ExecutableElement, ? extends AnnotationValue> e : this.elementValues.entrySet()) { 228 rv.put(e.getKey().getSimpleName().toString(), e.getValue().getValue()); 229 } 230 return rv; 231 } 232 233 234 /* 235 * Static methods. 236 */ 237 238 239 // Called by describeConstable(). 240 private static final Optional<? extends ConstantDesc> describeAnnotationValue(final Object v) { 241 return switch (v) { 242 case null -> Optional.empty(); // deliberately not Optional.of(NULL); annotation values cannot be null 243 case Constable c -> c.describeConstable(); 244 case ConstantDesc cd -> Optional.of(cd); 245 case List<?> l -> Constables.describe(l, e -> e instanceof Constable c ? c.describeConstable() : Optional.empty()); 246 default -> Optional.empty(); 247 }; 248 } 249 250 private static final <E extends Element> E element(final Iterable<? extends E> elements, final CharSequence simpleName) { 251 for (final E e : elements) { 252 if (e.getSimpleName().contentEquals(simpleName)) { 253 return e; 254 } 255 } 256 return null; 257 } 258 259}