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.LinkedHashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Optional; 025 026import javax.lang.model.element.AnnotationMirror; 027import javax.lang.model.element.AnnotationValue; 028import javax.lang.model.element.Element; 029import javax.lang.model.element.ExecutableElement; 030import javax.lang.model.element.TypeElement; 031 032import javax.lang.model.type.DeclaredType; 033 034import org.microbean.construct.constant.Constables; 035 036import static java.lang.constant.ConstantDescs.BSM_INVOKE; 037import static java.lang.constant.ConstantDescs.CD_Map; 038 039import static java.lang.constant.MethodHandleDesc.ofConstructor; 040 041import static java.util.Collections.unmodifiableMap; 042 043import static java.util.LinkedHashMap.newLinkedHashMap; 044 045import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE; 046 047import static javax.lang.model.util.ElementFilter.methodsIn; 048 049/** 050 * An <strong>experimental</strong> {@link AnnotationMirror} implementation that is partially or wholly synthetic. 051 * 052 * <p>It is possible to create {@link SyntheticAnnotationMirror} instances representing annotations that a Java compiler 053 * will not produce. For example, <a 054 * href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-9.html#jls-9.6.1">annotations cannot refer to each 055 * other, directly or indirectly</a>, but two {@link SyntheticAnnotationMirror}s may do so.</p> 056 * 057 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 058 * 059 * @spec https://docs.oracle.com/javase/specs/jls/se25/html/jls-9.html#jls-9.6.1 Java Language Specification, section 060 * 9.6.1 061 */ 062public final class SyntheticAnnotationMirror implements AnnotationMirror, Constable { 063 064 065 /* 066 * Instance fields. 067 */ 068 069 070 private final TypeElement annotationTypeElement; 071 072 private final Map<ExecutableElement, AnnotationValue> elementValues; 073 074 075 /* 076 * Constructors. 077 */ 078 079 080 /** 081 * Creates a new {@link SyntheticAnnotationMirror}. 082 * 083 * @param annotationTypeElement a {@link TypeElement} representing an annotation type; must not be {@code null}; must 084 * return {@link javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from its {@link 085 * Element#getKind() getKind()} method; {@link SyntheticAnnotationTypeElement} implementations are strongly preferred 086 * 087 * @exception NullPointerException if {@code annotationTypeElement} is {@code null} 088 * 089 * @exception IllegalArgumentException if {@code annotationTypeElement} does not return {@link 090 * javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from an invocation of its {@link 091 * Element#getKind() getKind()} method, or if {@code values} has more entries in it than {@code annotationTypeElement} 092 * has {@linkplain Element#getEnclosedElements() anotation elements} 093 * 094 * @see #SyntheticAnnotationMirror(TypeElement, Map) 095 */ 096 public SyntheticAnnotationMirror(final TypeElement annotationTypeElement) { 097 this(annotationTypeElement, Map.of()); 098 } 099 100 /** 101 * Creates a new {@link SyntheticAnnotationMirror}. 102 * 103 * @param annotationTypeElement a {@link TypeElement} representing an annotation type; must not be {@code null}; must 104 * return {@link javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from its {@link 105 * Element#getKind() getKind()} method; {@link SyntheticAnnotationTypeElement} implementations are strongly preferred 106 * 107 * @param values a {@link Map} of annotation values indexed by annotation element name; must not be {@code null}; must 108 * contain only values that are permissible for annotation elements 109 * 110 * @exception NullPointerException if any argument is {@code null} 111 * 112 * @exception IllegalArgumentException if {@code annotationTypeElement} does not return {@link 113 * javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from an invocation of its {@link 114 * Element#getKind() getKind()} method, or if {@code values} has more entries in it than {@code annotationTypeElement} 115 * has {@linkplain Element#getEnclosedElements() anotation elements} 116 */ 117 public SyntheticAnnotationMirror(final TypeElement annotationTypeElement, 118 final Map<? extends String, ?> values) { 119 super(); 120 if (annotationTypeElement.getKind() != ANNOTATION_TYPE) { 121 throw new IllegalArgumentException("annotationTypeElement: " + annotationTypeElement); 122 } 123 this.annotationTypeElement = annotationTypeElement; 124 final LinkedHashMap<ExecutableElement, AnnotationValue> m = newLinkedHashMap(values.size()); 125 final List<? extends ExecutableElement> methods = methodsIn(annotationTypeElement.getEnclosedElements()); 126 for (final ExecutableElement e : methods) { 127 final Object value = values.get(e.getSimpleName().toString()); // default value deliberately not included 128 if (value == null) { 129 if (e.getDefaultValue() == null) { 130 // There has to be a value somewhere, or annotationTypeElement or values is illegal 131 throw new IllegalArgumentException("annotationTypeElement: " + annotationTypeElement + "; values: " + values); 132 } 133 // Default values are excluded from the map on purpose, following the contract of 134 // AnnotationValue#getElementValues(). 135 } else { 136 m.put(e, value instanceof AnnotationValue av ? av : new SyntheticAnnotationValue(value)); 137 } 138 } 139 if (values.size() > m.size()) { 140 throw new IllegalArgumentException("values: " + values); 141 } 142 this.elementValues = m.isEmpty() ? Map.of() : unmodifiableMap(m); 143 } 144 145 146 /* 147 * Instance methods. 148 */ 149 150 151 @Override // Constable 152 public final Optional<? extends ConstantDesc> describeConstable() { 153 return this.annotationTypeElement instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty() 154 .flatMap(elementDesc -> Constables.describe(this.elementValues, 155 SyntheticAnnotationMirror::describeExecutableElement, 156 SyntheticAnnotationMirror::describeAnnotationValue) 157 .map(valuesDesc -> DynamicConstantDesc.of(BSM_INVOKE, 158 ofConstructor(ClassDesc.of(this.getClass().getName()), 159 ClassDesc.of(TypeElement.class.getName()), 160 CD_Map), 161 elementDesc, 162 valuesDesc))); 163 } 164 165 @Override // AnnotationMirror 166 public final DeclaredType getAnnotationType() { 167 return (DeclaredType)this.annotationTypeElement.asType(); 168 } 169 170 @Override // AnnotationMirror 171 public final Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues() { 172 return this.elementValues; 173 } 174 175 176 /* 177 * Static methods. 178 */ 179 180 181 private static final Optional<? extends ConstantDesc> describeAnnotationValue(final Object v) { 182 return switch (v) { 183 case null -> Optional.empty(); // deliberately not Optional.of(NULL); annotation values cannot be null 184 case Constable c -> c.describeConstable(); 185 case ConstantDesc cd -> Optional.of(cd); 186 case List<?> l -> Constables.describe(l, e -> e instanceof Constable c ? c.describeConstable() : Optional.empty()); 187 default -> Optional.empty(); 188 }; 189 } 190 191 private static final Optional<? extends ConstantDesc> describeExecutableElement(final ExecutableElement e) { 192 return switch (e) { 193 case null -> throw new IllegalStateException(); 194 case Constable c -> c.describeConstable(); 195 case ConstantDesc cd -> Optional.of(cd); 196 default -> Optional.empty(); 197 }; 198 } 199 200}