001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2025 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.annotation.Annotation; 017 018import java.lang.constant.ClassDesc; 019import java.lang.constant.Constable; 020import java.lang.constant.ConstantDesc; 021import java.lang.constant.DynamicConstantDesc; 022import java.lang.constant.MethodTypeDesc; 023 024import java.util.LinkedHashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.Map.Entry; 028import java.util.Optional; 029 030import java.util.function.Function; 031 032import javax.lang.model.element.AnnotationMirror; 033import javax.lang.model.element.AnnotationValue; 034import javax.lang.model.element.AnnotationValueVisitor; 035import javax.lang.model.element.Element; 036import javax.lang.model.element.ExecutableElement; 037import javax.lang.model.element.TypeElement; 038import javax.lang.model.element.VariableElement; 039 040import javax.lang.model.type.DeclaredType; 041import javax.lang.model.type.TypeMirror; 042 043import org.microbean.construct.constant.Constables; 044 045import static java.lang.constant.ConstantDescs.BSM_INVOKE; 046import static java.lang.constant.ConstantDescs.CD_Map; 047import static java.lang.constant.ConstantDescs.CD_Object; 048import static java.lang.constant.ConstantDescs.NULL; 049 050import static java.lang.constant.DirectMethodHandleDesc.Kind.INTERFACE_STATIC; 051 052import static java.lang.constant.MethodHandleDesc.ofConstructor; 053 054import static java.util.Arrays.fill; 055 056import static java.util.Collections.unmodifiableMap; 057 058import static java.util.LinkedHashMap.newLinkedHashMap; 059 060import static java.util.Objects.requireNonNull; 061 062import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE; 063import static javax.lang.model.element.ElementKind.METHOD; 064 065/** 066 * An <strong>experimental</strong> {@link AnnotationMirror} implementation that is partially or wholly synthetic. 067 * 068 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 069 */ 070public final class SyntheticAnnotationMirror implements AnnotationMirror, Constable { 071 072 private final TypeElement annotationTypeElement; 073 074 private final Map<ExecutableElement, AnnotationValue> elementValues; 075 076 /** 077 * Creates a new {@link SyntheticAnnotationMirror}. 078 * 079 * @param annotationTypeElement a {@link TypeElement} representing an annotation type; must not be {@code null}; must 080 * return {@link javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from its {@link 081 * Element#getKind() getKind()} method; {@link SyntheticAnnotationTypeElement} implementations are strongly preferred 082 * 083 * @param values a {@link Map} of annotation values indexed by annotation element name; must not be {@code null}; must 084 * contain only values that are permissible for annotation elements 085 * 086 * @exception NullPointerException if any argument is {@code null} 087 * 088 * @exception IllegalArgumentException if {@code annotationTypeElement} does not return {@link 089 * javax.lang.model.element.ElementKind#ANNOTATION_TYPE ANNOTATION_TYPE} from an invocation of its {@link 090 * Element#getKind() getKind()} method, or if {@code values} has more entries in it than {@code annotationTypeElement} 091 * has {@linkplain Element#getEnclosedElements() anotation elements} 092 */ 093 public SyntheticAnnotationMirror(final TypeElement annotationTypeElement, 094 final Map<? extends String, ?> values) { 095 super(); 096 if (annotationTypeElement.getKind() != ANNOTATION_TYPE) { 097 throw new IllegalArgumentException("annotationTypeElement: " + annotationTypeElement); 098 } 099 this.annotationTypeElement = annotationTypeElement; 100 final LinkedHashMap<ExecutableElement, AnnotationValue> m = newLinkedHashMap(values.size()); 101 for (final Element e : annotationTypeElement.getEnclosedElements()) { 102 if (e.getKind() == METHOD) { 103 final Object value = values.get(e.getSimpleName().toString()); 104 if (value != null) { 105 m.put((ExecutableElement)e, value instanceof AnnotationValue av ? av : new SyntheticAnnotationValue(value)); 106 } 107 } 108 } 109 if (values.size() > m.size()) { 110 throw new IllegalArgumentException("values: " + values); 111 } 112 this.elementValues = unmodifiableMap(m); 113 } 114 115 116 /* 117 * Instance methods. 118 */ 119 120 @Override // Constable 121 public final Optional<? extends ConstantDesc> describeConstable() { 122 return this.annotationTypeElement instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty() 123 .flatMap(elementDesc -> Constables.describe(this.elementValues, 124 SyntheticAnnotationMirror::describeExecutableElement, 125 SyntheticAnnotationMirror::describeAnnotationValue) 126 .map(valuesDesc -> DynamicConstantDesc.of(BSM_INVOKE, 127 ofConstructor(ClassDesc.of(this.getClass().getName()), 128 ClassDesc.of(TypeElement.class.getName()), 129 CD_Map), 130 elementDesc, 131 valuesDesc))); 132 } 133 134 135 @Override // AnnotationMirror 136 public final DeclaredType getAnnotationType() { 137 return (DeclaredType)this.annotationTypeElement.asType(); 138 } 139 140 @Override // AnnotationMirror 141 public final Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues() { 142 return this.elementValues; 143 } 144 145 /** 146 * An <strong>experimental</strong> {@link AnnotationValue} implementation that is partially or wholly synthetic. 147 * 148 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 149 */ 150 public static final class SyntheticAnnotationValue implements AnnotationValue, Constable { 151 152 // Will be one of: 153 // 154 // * AnnotationMirror 155 // * List<SyntheticAnnotationValue> 156 // * TypeMirror 157 // * VariableElement (ENUM_CONSTANT) 158 // * Boolean 159 // * Byte 160 // * Character 161 // * Double 162 // * Float 163 // * Integer 164 // * Long 165 // * Short 166 // * String 167 private final Object value; 168 169 /** 170 * Creates a new {@link SyntheticAnnotationValue}. 171 * 172 * @param value the value; must not be {@code null}; must be a legal {@link AnnotationValue} type 173 * 174 * @exception NullPointerException if {@code value} is {@code null} 175 * 176 * @exception IllegalArgumentException if {@code value} is not a legal {@link AnnotationValue} type 177 * 178 * @see AnnotationValue 179 */ 180 public SyntheticAnnotationValue(final Object value) { 181 super(); 182 this.value = value(value); 183 } 184 185 @Override // AnnotationValue 186 @SuppressWarnings("unchecked") 187 public final <R, P> R accept(final AnnotationValueVisitor<R, P> v, final P p) { 188 return switch (this.getValue()) { 189 case null -> v.visitUnknown(this, p); // ...or AssertionError? 190 case AnnotationMirror a -> v.visitAnnotation(a, p); 191 case List<?> l -> v.visitArray((List<? extends AnnotationValue>)l, p); 192 case TypeMirror t -> v.visitType(t, p); 193 case VariableElement e -> v.visitEnumConstant(e, p); 194 case Boolean b -> v.visitBoolean(b, p); 195 case Byte b -> v.visitByte(b, p); 196 case Character c -> v.visitChar(c, p); 197 case Double d -> v.visitDouble(d, p); 198 case Float f -> v.visitFloat(f, p); 199 case Integer i -> v.visitInt(i, p); 200 case Long l -> v.visitLong(l, p); 201 case Short s -> v.visitShort(s, p); 202 case String s -> v.visitString(s, p); 203 default -> v.visitUnknown(this, p); 204 }; 205 } 206 207 @Override // Constable 208 public final Optional<? extends ConstantDesc> describeConstable() { 209 return this.value instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty() 210 .map(valueDesc -> DynamicConstantDesc.of(BSM_INVOKE, 211 ofConstructor(ClassDesc.of(this.getClass().getName()), 212 CD_Object), 213 valueDesc)); 214 } 215 216 @Override // Object 217 public final boolean equals(final Object other) { 218 return this == other || switch (other) { 219 case null -> false; 220 case SyntheticAnnotationValue sav when this.getClass() == sav.getClass() -> this.value.equals(sav.value); 221 default -> false; 222 }; 223 } 224 225 @Override // AnnotationValue 226 public final Object getValue() { 227 return this.value; 228 } 229 230 @Override // Object 231 public final int hashCode() { 232 return this.value.hashCode(); 233 } 234 235 @Override // Object 236 public final String toString() { 237 return this.value.toString(); 238 } 239 240 private static final Object value(final Object value) { 241 return switch (value) { 242 case null -> throw new NullPointerException("value"); 243 244 case AnnotationValue av -> av.getValue(); // not part of the spec; just good hygiene 245 case List<?> l -> l.stream().map(SyntheticAnnotationValue::new).toList(); 246 247 case TypeMirror t -> switch (t.getKind()) { 248 case BOOLEAN, BYTE, CHAR, DECLARED, DOUBLE, FLOAT, INT, LONG, SHORT, VOID /* I think? */ -> t; 249 default -> throw new IllegalArgumentException("value: " + value); 250 }; 251 252 case VariableElement e -> switch (e.getKind()) { 253 case ENUM_CONSTANT -> e; 254 default -> throw new IllegalArgumentException("value: " + value); 255 }; 256 257 case AnnotationMirror a -> a; 258 case Boolean b -> b; 259 case Byte b -> b; 260 case Character c -> c; 261 case Double d -> d; 262 case Float f -> f; 263 case Integer i -> i; 264 case Long l -> l; 265 case Short s -> s; 266 case String s -> s; 267 default -> throw new IllegalArgumentException("value: " + value); 268 }; 269 } 270 271 } 272 273 private static final Optional<? extends ConstantDesc> describeAnnotationValue(final Object v) { 274 return switch (v) { 275 case null -> Optional.empty(); // deliberately not Optional.of(NULL); annotation values cannot be null 276 case Constable c -> c.describeConstable(); 277 case ConstantDesc cd -> Optional.of(cd); 278 case List<?> l -> Constables.describe(l, e -> e instanceof Constable c ? c.describeConstable() : Optional.empty()); 279 default -> Optional.empty(); 280 }; 281 } 282 283 private static final Optional<? extends ConstantDesc> describeExecutableElement(final ExecutableElement e) { 284 return switch (e) { 285 case null -> throw new IllegalStateException(); 286 case Constable c -> c.describeConstable(); 287 case ConstantDesc cd -> Optional.of(cd); 288 default -> Optional.empty(); 289 }; 290 } 291 292}